Comment concaténer les chaînes d'un champ de chaîne dans une requête PostgreSQL « grouper par » ?

StackOverflow https://stackoverflow.com/questions/43870

Question

Je cherche un moyen de concaténer les chaînes d'un champ au sein d'un groupe par requête.Par exemple, j'ai un tableau :

ID   COMPANY_ID   EMPLOYEE
1    1            Anna
2    1            Bill
3    2            Carol
4    2            Dave

et je voulais regrouper par company_id pour obtenir quelque chose comme :

COMPANY_ID   EMPLOYEE
1            Anna, Bill
2            Carol, Dave

Il existe une fonction intégrée dans MySQL pour ce faire groupe_concat

Était-ce utile?

La solution

PostgreSQL 9.0 ou version ultérieure :

Les versions récentes de Postgres (depuis fin 2010) ont le string_agg(expression, delimiter) fonction qui fera exactement ce que la question demandait, vous permettant même de spécifier la chaîne délimitatrice :

SELECT company_id, string_agg(employee, ', ')
FROM mytable
GROUP BY company_id;

Postgres 9.0 a également ajouté la possibilité de spécifier un ORDER BY clause dans toute expression globale;sinon, l'ordre n'est pas défini.Vous pouvez donc maintenant écrire :

SELECT company_id, string_agg(employee, ', ' ORDER BY employee)
FROM mytable
GROUP BY company_id;

Ou bien :

SELECT string_agg(actor_name, ', ' ORDER BY first_appearance)

PostgreSQL 8.4 ou version ultérieure :

PostgreSQL 8.4 (en 2009) introduit la fonction d'agrégation array_agg(expression) qui concatène les valeurs dans un tableau.Alors array_to_string() peut être utilisé pour donner le résultat souhaité:

SELECT company_id, array_to_string(array_agg(employee), ', ')
FROM mytable
GROUP BY company_id;

string_agg pour les versions antérieures à 9.0 :

Au cas où quelqu'un rencontrerait cela à la recherche d'une cale de compatibilité pour les bases de données antérieures à la version 9.0, il est possible de tout implémenter dans string_agg sauf le ORDER BY clause.

Ainsi, avec la définition ci-dessous, cela devrait fonctionner de la même manière que dans une base de données Postgres 9.x :

SELECT string_agg(name, '; ') AS semi_colon_separated_names FROM things;

Mais ce sera une erreur de syntaxe :

SELECT string_agg(name, '; ' ORDER BY name) AS semi_colon_separated_names FROM things;
--> ERROR: syntax error at or near "ORDER"

Testé sur PostgreSQL 8.3.

CREATE FUNCTION string_agg_transfn(text, text, text)
    RETURNS text AS 
    $$
        BEGIN
            IF $1 IS NULL THEN
                RETURN $2;
            ELSE
                RETURN $1 || $3 || $2;
            END IF;
        END;
    $$
    LANGUAGE plpgsql IMMUTABLE
COST 1;

CREATE AGGREGATE string_agg(text, text) (
    SFUNC=string_agg_transfn,
    STYPE=text
);

Variations personnalisées (toutes les versions de Postgres)

Avant la version 9.0, il n'existait pas de fonction d'agrégation intégrée pour concaténer des chaînes.L'implémentation personnalisée la plus simple (suggéré par Vajda Gabo dans ce message de liste de diffusion, parmi tant d'autres) consiste à utiliser le textcat fonction (qui se cache derrière la || opérateur):

CREATE AGGREGATE textcat_all(
  basetype    = text,
  sfunc       = textcat,
  stype       = text,
  initcond    = ''
);

Voici la CREATE AGGREGATE Documentation.

Cela colle simplement toutes les cordes ensemble, sans séparateur.Afin d'insérer un ", " entre eux sans l'avoir à la fin, vous souhaiterez peut-être créer votre propre fonction de concaténation et la remplacer par le "textcat" ci-dessus.En voici un que j'ai mis en place et testé le 8.3.12 :

CREATE FUNCTION commacat(acc text, instr text) RETURNS text AS $$
  BEGIN
    IF acc IS NULL OR acc = '' THEN
      RETURN instr;
    ELSE
      RETURN acc || ', ' || instr;
    END IF;
  END;
$$ LANGUAGE plpgsql;

Cette version affichera une virgule même si la valeur dans la ligne est nulle ou vide, vous obtenez donc un résultat comme ceci :

a, b, c, , e, , g

Si vous préférez supprimer les virgules supplémentaires pour afficher ceci :

a, b, c, e, g

Ajoutez ensuite un ELSIF vérifiez la fonction comme ceci :

CREATE FUNCTION commacat_ignore_nulls(acc text, instr text) RETURNS text AS $$
  BEGIN
    IF acc IS NULL OR acc = '' THEN
      RETURN instr;
    ELSIF instr IS NULL OR instr = '' THEN
      RETURN acc;
    ELSE
      RETURN acc || ', ' || instr;
    END IF;
  END;
$$ LANGUAGE plpgsql;

Autres conseils

Que diriez-vous d'utiliser les fonctions de tableau intégrées à Postgres ?Au moins sur 8.4, cela fonctionne immédiatement :

SELECT company_id, array_to_string(array_agg(employee), ',')
FROM mytable
GROUP BY company_id;

Depuis PostgreSQL 9.0, vous pouvez utiliser la fonction d'agrégation appelée chaîne_agg.Votre nouveau SQL devrait ressembler à ceci :

SELECT company_id, string_agg(employee, ', ')
FROM mytable
GROUP BY company_id;

Je ne revendique aucun mérite pour la réponse car je l'ai trouvée après quelques recherches :

Ce que je ne savais pas, c'est que PostgreSQL vous permet de définir vos propres fonctions d'agrégation avec CRÉER UN AGRÉGAT

Ce post sur la liste PostgreSQL montre à quel point il est trivial de créer une fonction pour faire ce qui est requis :

CREATE AGGREGATE textcat_all(
  basetype    = text,
  sfunc       = textcat,
  stype       = text,
  initcond    = ''
);

SELECT company_id, textcat_all(employee || ', ')
FROM mytable
GROUP BY company_id;

Comme déjà mentionné, créer votre propre fonction d'agrégation est la bonne chose à faire.Voici ma fonction d'agrégation de concaténation (vous pouvez trouver détails en anglais):

CREATE OR REPLACE FUNCTION concat2(text, text) RETURNS text AS '
    SELECT CASE WHEN $1 IS NULL OR $1 = \'\' THEN $2
            WHEN $2 IS NULL OR $2 = \'\' THEN $1
            ELSE $1 || \' / \' || $2
            END; 
'
 LANGUAGE SQL;

CREATE AGGREGATE concatenate (
  sfunc = concat2,
  basetype = text,
  stype = text,
  initcond = ''

);

Et puis utilisez-le comme :

SELECT company_id, concatenate(employee) AS employees FROM ...

Ce dernier extrait de liste d'annonces pourrait être intéressant si vous effectuez une mise à niveau vers la version 8.4 :

Jusqu'à 8.4 sort d'une fonction native super efficace, vous pouvez ajouter la fonction array_accum () dans la documentation PostgreSQL pour enrouler n'importe quelle colonne dans un tableau, qui peut ensuite être utilisé par code d'application, ou combinée avec array_to_string pour format pour format c'est une liste:

http://www.postgresql.org/docs/current/static/xaggr.html

Je créerais un lien vers les documents de développement 8.4, mais ils ne semblent pas encore répertorier cette fonctionnalité.

Suite à la réponse de Kev, en utilisant la documentation Postgres :

Commencez par créer un tableau d’éléments, puis utilisez le array_to_string fonction.

CREATE AGGREGATE array_accum (anyelement)
(
 sfunc = array_append,
 stype = anyarray,
 initcond = '{}'
);

select array_to_string(array_accum(name),'|') from table group by id;

Suite encore une fois à l'utilisation d'une fonction d'agrégation personnalisée de concaténation de chaînes :vous devez vous rappeler que l'instruction select placera les lignes dans n'importe quel ordre, vous devrez donc faire un sous sélectionner dans le depuis déclaration avec un commandé par clause, puis une clause externe sélectionner avec un par groupe clause pour agréger les chaînes, ainsi :

SELECT custom_aggregate(MY.special_strings)
FROM (SELECT special_strings, grouping_column 
        FROM a_table 
        ORDER BY ordering_column) MY
GROUP BY MY.grouping_column

J'ai trouvé cette documentation PostgreSQL utile : http://www.postgresql.org/docs/8.0/interactive/functions-conditional.html.

Dans mon cas, j'ai cherché du SQL simple pour concaténer un champ entouré de crochets, si le champ n'est pas vide.

select itemid, 
  CASE 
    itemdescription WHEN '' THEN itemname 
    ELSE itemname || ' (' || itemdescription || ')' 
  END 
from items;

Utiliser STRING_AGG fonction pour PostgreSQL et Google BigQuery SQL:

SELECT company_id, STRING_AGG(employee, ', ')
FROM employees
GROUP BY company_id;

Selon la version PostgreSQL 9.0 et supérieure, vous pouvez utiliser la fonction d'agrégation appelée string_agg.Votre nouveau SQL devrait ressembler à ceci :

SELECT company_id, string_agg(employee, ', ')
    FROM mytable GROUP BY company_id;

Vous pouvez également utiliser la fonction de format.Ce qui peut également implicitement prendre en charge la conversion de type de texte, int, etc.

create or replace function concat_return_row_count(tbl_name text, column_name text, value int)
returns integer as $row_count$
declare
total integer;
begin
    EXECUTE format('select count(*) from %s WHERE %s = %s', tbl_name, column_name, value) INTO total;
    return total;
end;
$row_count$ language plpgsql;


postgres=# select concat_return_row_count('tbl_name','column_name',2); --2 is the value

J'utilise Jetbrains Rider et c'était compliqué de copier les résultats des exemples ci-dessus pour les réexécuter car cela semblait tout envelopper en JSON.Cela les regroupe en une seule instruction plus facile à exécuter

select string_agg('drop table if exists "' || tablename || '" cascade', ';') 
from pg_tables where schemaname != $$pg_catalog$$ and tableName like $$rm_%$$

Si vous êtes sur Amazon Redshift, où string_agg n'est pas pris en charge, essayez d'utiliser listagg.

SELECT company_id, listagg(EMPLOYEE, ', ') as employees
FROM EMPLOYEE_table
GROUP BY company_id;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top