PostgreSQL 'grubundaki dize alanının dizelerini' sorgu ile nasıl birleştirebilirim?


351

Sorgu ile bir grup içindeki bir alanın dizeleri bitiştirmek için bir yol arıyorum. Örneğin, bir masam var:

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

ve böyle bir şey almak için company_id tarafından gruplandırmak istedim:

COMPANY_ID   EMPLOYEE
1            Anna, Bill
2            Carol, Dave

MySQL'de bu group_concat'i yapmak için yerleşik bir fonksiyon var


1
Markus Döring'in yanıtı teknik olarak daha iyi.
pstanton

@pstanton, Döring'in cevabı sadece 8.4 ve altı için daha iyidir.
Jared Beck

Bu soru dba.stackexchange.com için daha uygun görünüyor .
Dave Jarvis

Bu şimdi geçerli bir yanıt olmalı stackoverflow.com/a/47638417/243233
Jus12

Yanıtlar:


542

PostgreSQL 9.0 veya üstü:

Postgres'in son sürümleri (2010'un sonlarından beri), string_agg(expression, delimiter)sorulan soruyu tam olarak yapacak ve hatta sınırlayıcı dizesini belirtmenize izin veren bir işleve sahiptir:

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

Postgres 9.0 ayrıca herhangi bir toplu ifadede bir ORDER BYcümle belirtme yeteneğini de ekledi ; aksi takdirde sipariş tanımsızdır. Şimdi yazabilirsiniz:

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

Ya da gerçekten:

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

PostgreSQL 8.4 veya üzeri:

PostgreSQL 8.4 (2009'da) , değerleri bir diziye birleştiren toplama işleviniarray_agg(expression) tanıttı . Daha sonra array_to_string()istenen sonucu vermek için kullanılabilir:

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

string_agg 8.4 öncesi sürümler için:

Herkesin 9.0 öncesi veritabanları için uyumluluk şim aramasıyla karşılaşması durumunda string_agg, ORDER BYmadde hariç her şeyi uygulamak mümkündür .

Aşağıdaki tanım ile bu 9.x Postgres DB ile aynı çalışmalıdır:

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

Ancak bu bir sözdizimi hatası olacaktır:

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

PostgreSQL 8.3 üzerinde test edilmiştir.

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

Özel varyasyonlar (tüm Postgres sürümleri)

9.0'dan önce, dizeleri birleştirmek için yerleşik bir toplama işlevi yoktu. En basit özel uygulama ( Vajda Gabo tarafından bu posta listesi postasında , diğerleri arasında önerilmektedir ) yerleşik textcatişlevi kullanmaktır ( ||operatörün arkasında bulunur ):

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

İşte CREATE AGGREGATEbelgeler.

Bu, tüm dizeleri ayırıcı olmadan birleştirir. Sonuna sahip olmadan aralarına bir "," eklemek için, kendi birleştirme işlevinizi yapmak ve yukarıdaki "textcat" yerine koymak isteyebilirsiniz. İşte 8.3.12 üzerinde bir araya getirip test ettiğim:

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;

Bu sürüm, satırdaki değer null veya boş olsa bile virgül üretir, böylece şöyle çıktı alırsınız:

a, b, c, , e, , g

Bunun çıktısını almak için fazladan virgül kullanmayı tercih ediyorsanız:

a, b, c, e, g

Ardından ELSIF, işleve şu şekilde bir kontrol ekleyin :

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
S & R varchar metin (son pgsql kararlı) vardı ama bu harika!
Kev

1
Fonksiyonu sadece SQL'de yazabilirsiniz, bu kurulum için daha kolaydır (plpgsql süper kullanıcı tarafından kurulmalıdır). Örnek için yazıma bakın.
bortzmeyer

11
"Dizeleri birleştirmek için yerleşik toplama işlevi yok" - neden kullanmıyorsunuz array_to_string(array_agg(employee), ',')?
pstanton

2
PostgreSQL 9.0 işlevi için +1. 9.0 öncesi hakkında endişelenmeniz gerekiyorsa, Markus'un cevabı daha iyidir.
Brad Koch

7
Postgres'in son sürümlerinin Order Bytoplama işlevi içinde bir string_agg(employee, ',' Order By employee)
cümleye

99

Postgres yerleşik dizi işlevlerini kullanmaya ne dersiniz? En azından 8.4'te bu kutudan çıkar çıkmaz çalışır:

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

ne yazık ki bu bizim için Greenplum'da işe yaramaz (v8.2). + 1 aynı
ekkis

Greenplum 4.3.4.1'de (PostgreSQL 8.2.15 üzerine kurulu) benim için iyi çalışıyor.
PhilHibbs

19

PostgreSQL 9.0'dan itibaren string_agg adlı toplama işlevini kullanabilirsiniz . Yeni SQL'iniz şöyle görünmelidir:

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


13

Bazı aramalardan sonra bulduğum için cevap için kredi talep etmiyorum:

Ne bilmiyordum ki PostgreSQL, CREATE AGGREGATE ile kendi toplama işlevlerinizi tanımlamanıza izin veriyor

PostgreSQL listesindeki bu yazı , gerekli olanı yapmak için bir işlev oluşturmanın ne kadar önemsiz olduğunu gösterir:

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

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

7

Daha önce de belirtildiği gibi, kendi toplama işlevinizi oluşturmak doğru olan şeydir. İşte benim birleştirme toplama işlevi ( Fransızca olarak ayrıntıları bulabilirsiniz ):

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

);

Ve sonra şu şekilde kullanın:

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

5

8.4'e yükseltirseniz, bu son duyuru listesi snippet'i ilgi çekici olabilir:

8.4'e kadar süper verimli bir yerel kod ortaya çıkana kadar, herhangi bir sütunu bir diziye yuvarlamak için PostgreSQL belgelerine array_accum () işlevini ekleyebilirsiniz; bu sütun daha sonra uygulama kodu tarafından kullanılabilir veya formatlamak için array_to_string () bir liste olarak:

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

8.4 geliştirme belgelerine bağlanırdım ama henüz bu özelliği listelemiyorlar.


5

Kev'nun cevabını takip ederek Postgres belgelerini kullanarak:

İlk olarak, elemanlardan oluşan bir dizi oluşturun, ardından yerleşik array_to_stringişlevi kullanın.

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

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

5

Dize birleştirme özel birleştirilmiş işlevinin kullanımına yine İzleme: select deyimi Bir alt yapmanız gerekir, böylece herhangi bir sırada satırları yerleştirir hatırlamak gerekmez seçmek üzere gelen bir ile açıklamaya göre sırayla maddesi ve Daha sonra bir dış seçmek bir ile göre gruplandırmak böylece dizeleri toplamak için fıkra:

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



0

PostgreSQL 9.0 ve üzeri sürümlere göre string_agg adlı toplama işlevini kullanabilirsiniz. Yeni SQL'iniz şöyle görünmelidir:

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

0

Biçim işlevini de kullanabilirsiniz. Bu aynı zamanda, metin, int vb.

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
Bu, dize değerlerini birleştirmek için bir toplama kullanmakla nasıl ilişkilidir?
a_horse_with_no_name

0

Jetbrains Rider kullanıyorum ve tüm örnekleri JSON içinde sarmak gibi görünüyordu çünkü yeniden yürütmek için yukarıdaki örnekleri kopyalamak bir güçlük oldu. Bu onları çalıştırmak daha kolay tek bir ifadeye birleştirir

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

0

String_agg'in desteklenmediği Amazon Redshift kullanıyorsanız, listagg'ı kullanmayı deneyin.

SELECT company_id, listagg(EMPLOYEE, ', ') as employees
FROM EMPLOYEE_table
GROUP BY company_id;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.