Come concatenare le stringhe di un campo stringa in una query "group" di PostgreSQL?


351

Sto cercando un modo per concatenare le stringhe di un campo all'interno di un gruppo per query. Quindi, ad esempio, ho una tabella:

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

e volevo raggruppare per company_id per ottenere qualcosa del tipo:

COMPANY_ID   EMPLOYEE
1            Anna, Bill
2            Carol, Dave

C'è una funzione integrata in mySQL per fare questo group_concat


1
La risposta di Markus Döring è tecnicamente migliore.
Pstanton,

@pstanton, la risposta di Döring è migliore solo per 8.4 e precedenti.
Jared Beck,

Questa domanda sembra essere più adatta a dba.stackexchange.com .
Dave Jarvis,

Questa dovrebbe essere la risposta valida ora stackoverflow.com/a/47638417/243233
Jus12

Risposte:


542

PostgreSQL 9.0 o successivo:

Le versioni recenti di Postgres (dalla fine del 2010) hanno la string_agg(expression, delimiter)funzione che farà esattamente quello che la domanda ha posto, anche permettendoti di specificare la stringa del delimitatore:

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

Postgres 9.0 ha anche aggiunto la possibilità di specificare una ORDER BYclausola in qualsiasi espressione aggregata ; in caso contrario, l'ordine non è definito. Quindi ora puoi scrivere:

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

O davvero:

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

PostgreSQL 8.4 o successivo:

PostgreSQL 8.4 (nel 2009) ha introdotto la funzione aggregataarray_agg(expression) che concatena i valori in un array. Quindi array_to_string()può essere utilizzato per dare il risultato desiderato:

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

string_agg per le versioni pre-8.4:

Nel caso in cui qualcuno si trovi in ​​cerca di uno shim di compatibilità per database pre-9.0, è possibile implementare tutto string_aggtranne la ORDER BYclausola.

Quindi, con la definizione che segue, dovrebbe funzionare come in un DB Postx 9.x:

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

Ma questo sarà un errore di sintassi:

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

Testato su 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
);

Variazioni personalizzate (tutte le versioni di Postgres)

Prima della 9.0, non c'era alcuna funzione aggregata incorporata per concatenare le stringhe. L'implementazione personalizzata più semplice ( suggerita da Vajda Gabo in questo post della mailing list , tra molti altri) è quella di utilizzare la textcatfunzione integrata (che si trova dietro l' ||operatore):

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

Ecco la CREATE AGGREGATEdocumentazione

Questo semplicemente incolla tutte le stringhe insieme, senza separatore. Per ottenere un "," inserito tra loro senza averlo alla fine, potresti voler creare la tua funzione di concatenazione e sostituirla con il "textcat" sopra. Eccone uno che ho messo insieme e testato l'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;

Questa versione produrrà una virgola anche se il valore nella riga è nullo o vuoto, quindi ottieni un output in questo modo:

a, b, c, , e, , g

Se preferisci rimuovere le virgole aggiuntive per generare questo:

a, b, c, e, g

Quindi aggiungere un ELSIFsegno di spunta alla funzione in questo modo:

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;

1
Ho dovuto S&R varchar al testo (ultima stabile pgsql) ma questo è fantastico!
Kev,

1
Puoi scrivere la funzione solo in SQL, che è più facile per l'installazione (plpgsql deve essere installato dal superutente). Vedi il mio post per un esempio.
Bortzmeyer,

11
"Non esiste una funzione aggregata integrata per concatenare le stringhe" - perché non dovresti usare array_to_string(array_agg(employee), ',')?
Pstanton,

2
+1 per la funzione PostgreSQL 9.0. Se devi preoccuparti della pre-9.0, la risposta di Markus è migliore.
Brad Koch,

7
Si noti che le versioni recenti di Postgres consentono anche una Order Byclausola all'interno della funzione aggregata, ad es.string_agg(employee, ',' Order By employee)
IMSoP

99

Che ne dici di usare le funzioni di array integrate di Postgres? Almeno su 8.4 funziona immediatamente:

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

purtroppo questo non funziona per noi su Greenplum (v8.2). +1 lo stesso
ekkis

Funziona bene per me su Greenplum 4.3.4.1 (costruito su PostgreSQL 8.2.15).
PhilHibbs,

19

A partire da PostgreSQL 9.0 puoi usare la funzione aggregata chiamata string_agg . Il tuo nuovo SQL dovrebbe assomigliare a questo:

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


13

Non rivendico credito per la risposta perché l'ho trovata dopo alcune ricerche:

Quello che non sapevo è che PostgreSQL ti consente di definire le tue funzioni aggregate con CREATE AGGREGATE

Questo post nell'elenco PostgreSQL mostra quanto sia banale creare una funzione per fare ciò che è richiesto:

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

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

7

Come già accennato, la creazione della propria funzione aggregata è la cosa giusta da fare. Ecco la mia funzione aggregata di concatenazione (puoi trovare i dettagli in francese ):

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

);

E poi usalo come:

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

5

Quest'ultimo frammento dell'elenco degli annunci potrebbe essere interessante se eseguirai l'upgrade a 8.4:

Fino alla versione 8.4 con una nativa super efficiente, è possibile aggiungere la funzione array_accum () nella documentazione PostgreSQL per il rollup di qualsiasi colonna in una matrice, che può quindi essere utilizzata dal codice dell'applicazione o combinata con array_to_string () per formattare come un elenco:

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

Mi collegherei ai documenti di sviluppo 8.4 ma non sembrano ancora elencare questa funzione.


5

Seguendo la risposta di Kev, usando i documenti di Postgres:

Innanzitutto, crea un array di elementi, quindi usa la array_to_stringfunzione integrata.

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

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

5

In seguito ancora una volta l'uso di una funzione di aggregazione personalizzata di concatenazione di stringhe: è necessario ricordare che l'istruzione select metterà righe in qualsiasi ordine, quindi sarà necessario fare un sub selezionare nella dalla dichiarazione con un ordine da clausola e quindi una selezione esterna con una clausola group by per aggregare le stringhe, quindi:

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

3

Ho trovato utile questa documentazione di PostgreSQL: http://www.postgresql.org/docs/8.0/interactive/functions-conditional.html .

Nel mio caso, ho cercato SQL semplice per concatenare un campo con parentesi attorno ad esso, se il campo non è vuoto.

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


0

Secondo la versione PostgreSQL 9.0 e successive è possibile utilizzare la funzione aggregata chiamata string_agg. Il tuo nuovo SQL dovrebbe assomigliare a questo:

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

0

È inoltre possibile utilizzare la funzione di formattazione. Che può anche implicitamente occuparsi della conversione del tipo di testo, int, ecc da solo.

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

1
In che modo ciò è correlato all'uso di un aggregato per concatenare i valori di stringa?
a_horse_with_no_name

0

Sto usando Jetbrains Rider ed è stato una seccatura copiare i risultati degli esempi precedenti per rieseguirli perché sembrava avvolgere tutto in JSON. Questo li unisce in una singola istruzione che era più facile da eseguire

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

0

Se sei su Amazon Redshift, dove string_agg non è supportato, prova a utilizzare listagg.

SELECT company_id, listagg(EMPLOYEE, ', ') as employees
FROM EMPLOYEE_table
GROUP BY company_id;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.