Bagaimana cara menggabungkan string bidang string dalam kueri 'grup dengan PostgreSQL?

351

Saya mencari cara untuk menggabungkan string bidang dalam grup dengan kueri. Jadi misalnya, saya punya tabel:

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

dan saya ingin mengelompokkan berdasarkan company_id untuk mendapatkan sesuatu seperti:

COMPANY_ID   EMPLOYEE
1            Anna, Bill
2            Carol, Dave

Ada fungsi bawaan di mySQL untuk melakukan group_concat ini

Guy C
sumber
1
Jawaban Markus Döring secara teknis lebih baik.
pstanton
@pstanton, jawaban Döring hanya lebih baik untuk 8,4 dan di bawahnya.
Jared Beck
Pertanyaan ini tampaknya lebih cocok untuk dba.stackexchange.com .
Dave Jarvis
Ini harus menjadi jawaban yang valid sekarang stackoverflow.com/a/47638417/243233
Jus12

Jawaban:

542

PostgreSQL 9.0 atau lebih baru:

Versi terbaru Postgres (sejak akhir 2010) memiliki string_agg(expression, delimiter)fungsi yang akan melakukan persis apa yang ditanyakan, bahkan memungkinkan Anda menentukan string pembatas:

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

Postgres 9.0 juga menambahkan kemampuan untuk menentukan ORDER BYklausa dalam ekspresi agregat apa pun ; jika tidak, pesanan tidak ditentukan. Jadi sekarang Anda dapat menulis:

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

Atau memang:

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

PostgreSQL 8.4 atau lebih baru:

PostgreSQL 8.4 (pada 2009) memperkenalkan fungsi agregatarray_agg(expression) yang menggabungkan nilai-nilai ke dalam array. Kemudian array_to_string()dapat digunakan untuk memberikan hasil yang diinginkan:

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

string_agg untuk versi pra-8.4:

Jika ada orang yang menemukan ini mencari shim yang kompatibel untuk pra-9.0 database, adalah mungkin untuk mengimplementasikan semuanya string_aggkecuali ORDER BYklausa.

Jadi dengan definisi di bawah ini harus berfungsi sama seperti pada 9.x Postgres DB:

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

Tetapi ini akan menjadi kesalahan sintaksis:

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

Diuji pada 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
);

Variasi khusus (semua versi Postgres)

Sebelum 9.0, tidak ada fungsi agregat bawaan untuk merangkai string. Implementasi kustom yang paling sederhana ( disarankan oleh Vajda Gabo dalam posting milis ini , di antara banyak lainnya) adalah dengan menggunakan textcatfungsi bawaan (yang ada di belakang ||operator):

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

Ini CREATE AGGREGATEdokumentasinya.

Ini hanya menempelkan semua string bersama, tanpa pemisah. Untuk mendapatkan "," yang disisipkan di antara mereka tanpa di bagian akhir, Anda mungkin ingin membuat fungsi penggabungan Anda sendiri dan menggantinya dengan "textcat" di atas. Inilah yang saya kumpulkan dan uji pada 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;

Versi ini akan menampilkan koma bahkan jika nilai di baris adalah nol atau kosong, sehingga Anda mendapatkan output seperti ini:

a, b, c, , e, , g

Jika Anda lebih suka menghapus koma ekstra untuk menghasilkan ini:

a, b, c, e, g

Kemudian tambahkan tanda ELSIFcentang ke fungsi seperti ini:

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
sumber
1
Saya harus S & R varchar ke teks (pgsql stable terbaru) tapi ini hebat!
Kev
1
Anda dapat menulis fungsi hanya dalam SQL, yang lebih mudah untuk instalasi (plpgsql harus diinstal oleh superuser). Lihat posting saya untuk contoh.
bortzmeyer
11
"Tidak ada fungsi agregat bawaan untuk menyatukan string" - mengapa Anda tidak menggunakannya array_to_string(array_agg(employee), ',')?
pstanton
2
+1 untuk fungsi PostgreSQL 9.0. Jika Anda perlu khawatir tentang pra-9.0, jawaban Markus lebih baik.
Brad Koch
7
Perhatikan bahwa versi terbaru Postgres juga memungkinkan Order Byklausa di dalam fungsi agregat, misalnyastring_agg(employee, ',' Order By employee)
IMSoP
99

Bagaimana dengan menggunakan fungsi-fungsi array bawaan Postgres? Setidaknya pada 8,4 ini berhasil di luar kotak:

SELECT company_id, array_to_string(array_agg(employee), ',')
FROM mytable
GROUP BY company_id;
Markus Döring
sumber
sayangnya ini tidak bekerja untuk kita di Greenplum (v8.2). +1 semuanya sama
ekkis
Bekerja dengan baik untuk saya di Greenplum 4.3.4.1 (dibangun di atas PostgreSQL 8.2.15).
PhilHibbs
19

Sejak PostgreSQL 9.0 Anda dapat menggunakan fungsi agregat yang disebut string_agg . SQL baru Anda akan terlihat seperti ini:

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

dirbacke
sumber
13

Saya tidak mengklaim pujian untuk jawabannya karena saya menemukannya setelah beberapa pencarian:

Apa yang saya tidak tahu adalah bahwa PostgreSQL memungkinkan Anda untuk mendefinisikan fungsi agregat Anda sendiri dengan CREATE AGGREGATE

Posting ini pada daftar PostgreSQL menunjukkan betapa sepele membuat fungsi untuk melakukan apa yang diperlukan:

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
sumber
7

Seperti yang telah disebutkan, membuat fungsi agregat Anda sendiri adalah hal yang benar untuk dilakukan. Berikut ini adalah fungsi agregat gabungan saya (Anda dapat menemukan detail dalam bahasa Prancis ):

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

);

Dan kemudian menggunakannya sebagai:

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

Cuplikan daftar pengumuman terbaru ini mungkin menarik jika Anda akan meningkatkan ke 8.4:

Sampai 8.4 keluar dengan yang asli super-efisien, Anda dapat menambahkan fungsi array_accum () dalam dokumentasi PostgreSQL untuk menggulung setiap kolom ke dalam array, yang kemudian dapat digunakan oleh kode aplikasi, atau dikombinasikan dengan array_to_string () ke format sebagai daftar:

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

Saya akan menautkan ke dokumen pengembangan 8,4 tetapi mereka tampaknya belum mencantumkan fitur ini.

Kev
sumber
5

Menindaklanjuti jawaban Kev, menggunakan dokumen Postgres:

Pertama, buat array elemen, lalu gunakan array_to_stringfungsi bawaan.

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
sumber
5

Mengikuti lagi tentang penggunaan fungsi agregat khusus dari rangkaian string: Anda harus ingat bahwa pernyataan pilih akan menempatkan baris dalam urutan apa pun, jadi Anda perlu melakukan sub pilih dalam pernyataan dari dengan perintah dengan klausa, dan kemudian pilih luar dengan grup dengan klausa untuk mengagregasi string, dengan demikian:

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
sumber
3

Saya menemukan dokumentasi PostgreSQL ini bermanfaat: http://www.postgresql.org/docs/8.0/interactive/functions-conditional.html .

Dalam kasus saya, saya mencari SQL biasa untuk menggabungkan bidang dengan tanda kurung di sekitarnya, jika bidang tersebut tidak kosong.

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

sumber
2

Gunakan STRING_AGGfungsi untuk PostgreSQL dan Google BigQuery SQL :

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

Menurut versi PostgreSQL 9.0 dan di atasnya, Anda dapat menggunakan fungsi agregat yang disebut string_agg. SQL baru Anda akan terlihat seperti ini:

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

Anda juga dapat menggunakan fungsi format. Yang juga dapat secara implisit menangani konversi jenis teks, int, dll dengan sendirinya.

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
sumber
1
Bagaimana ini terkait dengan penggunaan agregat untuk menyatukan nilai string?
a_horse_with_no_name
0

Saya menggunakan Jetbrains Rider dan itu merepotkan menyalin hasil dari contoh di atas untuk mengeksekusi kembali karena sepertinya membungkus semuanya dalam JSON. Ini menggabungkan mereka ke dalam satu pernyataan yang lebih mudah dijalankan

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

Jika Anda menggunakan Amazon Redshift, di mana string_agg tidak didukung, coba gunakan listagg.

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