Wie verkette ich Zeichenfolgen eines Zeichenfolgenfelds in einer PostgreSQL-Abfrage "Gruppe nach"?

351

Ich suche nach einer Möglichkeit, die Zeichenfolgen eines Feldes innerhalb einer Gruppe durch Abfrage zu verketten. So habe ich zum Beispiel eine Tabelle:

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

und ich wollte mich nach company_id gruppieren, um so etwas wie:

COMPANY_ID   EMPLOYEE
1            Anna, Bill
2            Carol, Dave

In mySQL gibt es eine integrierte Funktion, um diese group_concat auszuführen

Guy C.
quelle
1
Markus Dörings Antwort ist technisch besser.
Pstanton
@pstanton, Dörings Antwort ist nur für 8.4 und darunter besser.
Jared Beck
Diese Frage scheint besser für dba.stackexchange.com geeignet zu sein .
Dave Jarvis
Dies sollte jetzt die gültige Antwort sein stackoverflow.com/a/47638417/243233
Jus12

Antworten:

542

PostgreSQL 9.0 oder höher:

Neuere Versionen von Postgres (seit Ende 2010) verfügen über die string_agg(expression, delimiter)Funktion, die genau das tut, wonach die Frage gestellt wurde, und Sie können sogar die Trennzeichenfolge angeben:

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

Postgres 9.0 hat außerdem die Möglichkeit hinzugefügt, eine ORDER BYKlausel in einem beliebigen aggregierten Ausdruck anzugeben . Andernfalls ist die Reihenfolge undefiniert. So können Sie jetzt schreiben:

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

Oder in der Tat:

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

PostgreSQL 8.4 oder höher:

PostgreSQL 8.4 (2009) führte die Aggregatfunktion ein,array_agg(expression) die die Werte zu einem Array verkettet. Dann array_to_string()kann das gewünschte Ergebnis erzielt werden:

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

string_agg für Versionen vor 8.4:

Falls jemand auf der Suche nach einem kompatiblen Shim für Datenbanken vor 9.0 darauf stößt, ist es möglich, alles string_aggaußer der ORDER BYKlausel zu implementieren .

Mit der folgenden Definition sollte dies also genauso funktionieren wie in einer 9.x Postgres-Datenbank:

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

Dies ist jedoch ein Syntaxfehler:

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

Getestet auf 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
);

Benutzerdefinierte Variationen (alle Postgres-Versionen)

Vor 9.0 gab es keine integrierte Aggregatfunktion zum Verketten von Zeichenfolgen. Die einfachste benutzerdefinierte Implementierung (unter anderem von Vajda Gabo in diesem Mailinglistenbeitrag vorgeschlagen ) ist die Verwendung der integrierten textcatFunktion (die hinter dem ||Operator liegt):

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

Hier ist die CREATE AGGREGATEDokumentation.

Dadurch werden einfach alle Saiten ohne Trennzeichen zusammengeklebt. Um ein "," zwischen ihnen einzufügen, ohne es am Ende zu haben, möchten Sie möglicherweise Ihre eigene Verkettungsfunktion erstellen und die oben stehende "Textkatze" ersetzen. Hier ist eine, die ich am 8.3.12 zusammengestellt und getestet habe:

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;

Diese Version gibt ein Komma aus, auch wenn der Wert in der Zeile null oder leer ist. Sie erhalten also die folgende Ausgabe:

a, b, c, , e, , g

Wenn Sie zusätzliche Kommas entfernen möchten, um dies auszugeben:

a, b, c, e, g

Fügen ELSIFSie dann der Funktion einen Häkchen wie folgt hinzu:

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;
Neall
quelle
1
Ich musste S & R varchar zu Text (neueste pgsql stabil), aber das ist großartig!
Kev
1
Sie können die Funktion nur in SQL schreiben, was für die Installation einfacher ist (plpgsql muss vom Superuser installiert werden). Ein Beispiel finden Sie in meinem Beitrag.
Bortzmeyer
11
"Es gibt keine integrierte Aggregatfunktion zum Verketten von Zeichenfolgen" - warum sollten Sie diese nicht verwenden array_to_string(array_agg(employee), ',')?
Pstanton
2
+1 für die PostgreSQL 9.0-Funktion. Wenn Sie sich Sorgen um Pre-9.0 machen müssen, ist Markus 'Antwort besser.
Brad Koch
7
Beachten Sie, dass neuere Versionen von Postgres auch eine Order ByKlausel innerhalb der Aggregatfunktion zulassen , z. B.string_agg(employee, ',' Order By employee)
IMSoP
99

Wie wäre es mit den in Postgres integrierten Array-Funktionen? Zumindest unter 8.4 funktioniert dies sofort:

SELECT company_id, array_to_string(array_agg(employee), ',')
FROM mytable
GROUP BY company_id;
Markus Döring
quelle
Leider funktioniert dies bei Greenplum (v8.2) nicht. +1
egal
Funktioniert gut für mich unter Greenplum 4.3.4.1 (basierend auf PostgreSQL 8.2.15).
PhilHibbs
19

Ab PostgreSQL 9.0 können Sie die Aggregatfunktion string_agg verwenden . Ihr neues SQL sollte ungefähr so ​​aussehen:

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

dirbacke
quelle
13

Ich beanspruche keine Gutschrift für die Antwort, weil ich sie nach einiger Suche gefunden habe:

Was ich nicht wusste ist, dass Sie mit PostgreSQL Ihre eigenen Aggregatfunktionen mit CREATE AGGREGATE definieren können

Dieser Beitrag in der PostgreSQL-Liste zeigt, wie trivial es ist, eine Funktion zu erstellen, um das zu tun, was erforderlich ist:

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

SELECT company_id, textcat_all(employee || ', ')
FROM mytable
GROUP BY company_id;
Guy C.
quelle
7

Wie bereits erwähnt, ist das Erstellen einer eigenen Aggregatfunktion das Richtige. Hier ist meine Verkettungsaggregatfunktion ( Details finden Sie auf Französisch ):

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 = ''

);

Und dann benutze es als:

SELECT company_id, concatenate(employee) AS employees FROM ...
bortzmeyer
quelle
5

Dieses neueste Ankündigungslisten-Snippet könnte von Interesse sein, wenn Sie ein Upgrade auf 8.4 durchführen:

Bis 8.4 eine sehr effiziente native Version herausbringt, können Sie die Funktion array_accum () in der PostgreSQL-Dokumentation hinzufügen, um eine beliebige Spalte in ein Array zu rollen, das dann vom Anwendungscode verwendet oder mit array_to_string () zum Formatieren kombiniert werden kann es als Liste:

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

Ich würde auf die 8.4-Entwicklungsdokumente verlinken, aber sie scheinen diese Funktion noch nicht aufzulisten.

Kev
quelle
5

Weiterverfolgung von Kevs Antwort anhand der Postgres-Dokumente:

Erstellen Sie zuerst ein Array der Elemente und verwenden Sie dann die integrierte array_to_stringFunktion.

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

select array_to_string(array_accum(name),'|') from table group by id;
Brad Koch
quelle
5

Im Folgenden wird noch einmal auf die Verwendung einer benutzerdefinierten Aggregatfunktion für die Verkettung von Zeichenfolgen eingegangen: Sie müssen sich daran erinnern, dass die select-Anweisung Zeilen in beliebiger Reihenfolge platziert. Daher müssen Sie in der from- Anweisung eine Unterauswahl mit einer order by- Klausel und treffen dann eine äußere Auswahl mit einer group by- Klausel, um die Zeichenfolgen zu aggregieren, also:

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
Brad Koch
quelle
2

Verwenden Sie die STRING_AGGFunktion für PostgreSQL und Google BigQuery SQL :

SELECT company_id, STRING_AGG(employee, ', ')
FROM employees
GROUP BY company_id;
Valentin Podkamennyi
quelle
0

Ab Version PostgreSQL 9.0 können Sie die Aggregatfunktion string_agg verwenden. Ihr neues SQL sollte ungefähr so ​​aussehen:

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

Sie können auch die Formatierungsfunktion verwenden. Dies kann sich auch implizit um die Typkonvertierung von Text, int usw. selbst kümmern.

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
Sandip Debnath
quelle
1
Wie hängt dies mit der Verwendung eines Aggregats zum Verketten von Zeichenfolgenwerten zusammen?
a_horse_with_no_name
0

Ich verwende Jetbrains Rider und es war mühsam, die Ergebnisse aus den obigen Beispielen zu kopieren, um sie erneut auszuführen, da alles in JSON zu verpacken schien. Dies verbindet sie zu einer einzigen Anweisung, die einfacher auszuführen war

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

Wenn Sie sich in Amazon Redshift befinden und string_agg nicht unterstützt wird, verwenden Sie listagg.

SELECT company_id, listagg(EMPLOYEE, ', ') as employees
FROM EMPLOYEE_table
GROUP BY company_id;
Gapp
quelle