Come concatenare le stringhe di un campo stringa in una query PostgreSQL "raggruppa per"?
-
09-06-2019 - |
Domanda
Sto cercando un modo per concatenare le stringhe di un campo all'interno di un gruppo tramite 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 farlo gruppo_concat
Soluzione
PostgreSQL 9.0 o successivo:
Le versioni recenti di Postgres (dalla fine del 2010) hanno l'estensione string_agg(expression, delimiter)
funzione che farà esattamente ciò che la domanda richiedeva, permettendoti anche di specificare la stringa delimitatrice:
SELECT company_id, string_agg(employee, ', ')
FROM mytable
GROUP BY company_id;
Postgres 9.0 ha anche aggiunto la possibilità di specificare un file ORDER BY
clausola 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 addirittura:
SELECT string_agg(actor_name, ', ' ORDER BY first_appearance)
PostgreSQL 8.4 o successivo:
PostgreSQL 8.4 (nel 2009) introdotto la funzione aggregata array_agg(expression)
che concatena i valori in un array.Poi array_to_string()
può essere utilizzato per ottenere il risultato desiderato:
SELECT company_id, array_to_string(array_agg(employee), ', ')
FROM mytable
GROUP BY company_id;
string_agg
per le versioni precedenti alla 9.0:
Nel caso qualcuno si imbattesse in questo cercando uno shim di compatibilità per i database precedenti alla 9.0, è possibile implementare tutto in string_agg
tranne il ORDER BY
clausola.
Quindi con la definizione seguente dovrebbe funzionare come in un DB Postgres 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 versione 9.0, non esisteva alcuna funzione di aggregazione incorporata per concatenare le stringhe.L'implementazione personalizzata più semplice (suggerito da Vajda Gabo in questo post della mailing list, tra molti altri) è utilizzare il file built-in textcat
funzione (che sta dietro il ||
operatore):
CREATE AGGREGATE textcat_all(
basetype = text,
sfunc = textcat,
stype = text,
initcond = ''
);
Ecco il CREATE AGGREGATE
documentazione.
Questo incolla semplicemente tutte le corde insieme, senza separatore.Per inserire un ", " tra di loro senza averlo alla fine, potresti voler creare la tua funzione di concatenazione e sostituirla al "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 genererà una virgola anche se il valore nella riga è null o vuoto, quindi otterrai un output come questo:
a, b, c, , e, , g
Se preferisci rimuovere le virgole extra per ottenere questo risultato:
a, b, c, e, g
Quindi aggiungi un ELSIF
controlla la 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;
Altri suggerimenti
Che ne dici di utilizzare 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;
A partire da PostgreSQL 9.0 è possibile utilizzare la funzione di aggregazione chiamata stringa_agg.Il tuo nuovo SQL dovrebbe assomigliare a questo:
SELECT company_id, string_agg(employee, ', ')
FROM mytable
GROUP BY company_id;
Non rivendico alcun merito 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 CREA AGGREGATO
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;
Come già accennato, creare la propria funzione aggregata è la cosa giusta da fare.Ecco la mia funzione di aggregazione di concatenazione (puoi trovare 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 usarlo come:
SELECT company_id, concatenate(employee) AS employees FROM ...
Questo ultimo snippet dell'elenco degli annunci potrebbe interessarti se esegui l'aggiornamento alla versione 8.4:
Fino a 8.4 non esce con uno nativo super-effetti, è possibile aggiungere la funzione Array_Accum () nella documentazione PostgreSQL per farle fare qualsiasi colonna in un array, che può quindi essere utilizzato dal codice dell'applicazione o combinata con Array_to_String () per formattare come un elenco:
Mi collegherei ai documenti di sviluppo 8.4 ma non sembrano ancora elencare questa funzionalità.
In seguito alla risposta di Kev, utilizzando i documenti Postgres:
Innanzitutto, crea un array di elementi, quindi utilizza il built-in array_to_string
funzione.
CREATE AGGREGATE array_accum (anyelement)
(
sfunc = array_append,
stype = anyarray,
initcond = '{}'
);
select array_to_string(array_accum(name),'|') from table group by id;
Seguendo ancora una volta l'uso di una funzione aggregata personalizzata di concatenazione di stringhe:devi ricordare che l'istruzione select posizionerà le righe in qualsiasi ordine, quindi dovrai fare un sub Selezionare nel da dichiarazione con un ordinato da clausola, e poi un esterno Selezionare con un raggruppa per clausola 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
Ho trovato utile questa documentazione PostgreSQL: http://www.postgresql.org/docs/8.0/interactive/functions-conditional.html.
Nel mio caso, ho cercato un semplice SQL per concatenare un campo racchiuso tra parentesi, se il campo non è vuoto.
select itemid,
CASE
itemdescription WHEN '' THEN itemname
ELSE itemname || ' (' || itemdescription || ')'
END
from items;
Utilizzo STRING_AGG
funzione per PostgreSQL E Google BigQuerySQL:
SELECT company_id, STRING_AGG(employee, ', ')
FROM employees
GROUP BY company_id;
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;
Puoi anche utilizzare la funzione di formattazione.Che può anche occuparsi implicitamente della conversione del tipo di text, int, ecc.
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
Sto utilizzando Jetbrains Rider ed è stata una seccatura copiare i risultati degli esempi precedenti per rieseguirli perché sembrava racchiudere tutto in JSON.Questo li unisce in un'unica istruzione più facile da eseguire
select string_agg('drop table if exists "' || tablename || '" cascade', ';')
from pg_tables where schemaname != $$pg_catalog$$ and tableName like $$rm_%$$
Se utilizzi 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;