Làm cách nào để nối các chuỗi của trường chuỗi trong truy vấn 'nhóm' của PostgreQuery?


351

Tôi đang tìm cách nối chuỗi các trường trong một nhóm bằng truy vấn. Vì vậy, ví dụ, tôi có một bảng:

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

và tôi muốn nhóm theo company_id để có được cái gì đó như:

COMPANY_ID   EMPLOYEE
1            Anna, Bill
2            Carol, Dave

Có một hàm dựng sẵn trong myQuery để thực hiện nhóm_concat này


1
Câu trả lời của Markus Döring là tốt hơn về mặt kỹ thuật.
pstanton

@pstanton, câu trả lời của Döring chỉ tốt hơn cho 8.4 trở xuống.
Jared Beck

Câu hỏi này dường như phù hợp hơn với dba.stackexchange.com .
Dave Jarvis

Đây phải là câu trả lời hợp lệ ngay bây giờ stackoverflow.com/a/47638417/243233
Jus12

Câu trả lời:


542

PostgreSQL 9.0 trở lên:

Các phiên bản gần đây của Postgres (từ cuối năm 2010) có string_agg(expression, delimiter)chức năng sẽ thực hiện chính xác những gì câu hỏi yêu cầu, thậm chí cho phép bạn chỉ định chuỗi phân cách:

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

Postgres 9.0 cũng đã thêm khả năng chỉ định một ORDER BYmệnh đề trong bất kỳ biểu thức tổng hợp nào ; mặt khác, thứ tự là không xác định. Vì vậy, bây giờ bạn có thể viết:

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

Hoặc thực sự:

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

PostgreSQL 8.4 trở lên:

PostgreQuery 8.4 (năm 2009) đã giới thiệu hàm tổng hợparray_agg(expression) nối các giá trị thành một mảng. Sau đó array_to_string()có thể được sử dụng để đưa ra kết quả mong muốn:

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

string_agg cho các phiên bản trước 8.4:

Trong trường hợp bất kỳ ai đi qua điều này đang tìm kiếm một shim tương thích cho cơ sở dữ liệu trước 9.0, có thể thực hiện mọi thứ string_aggtrừ ORDER BYđiều khoản.

Vì vậy, với định nghĩa dưới đây, điều này sẽ hoạt động giống như trong DB Postgres 9.x:

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

Nhưng đây sẽ là một lỗi cú pháp:

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

Đã thử nghiệm trên 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
);

Biến thể tùy chỉnh (tất cả các phiên bản Postgres)

Trước 9.0, không có hàm tổng hợp tích hợp để nối chuỗi. Việc thực hiện tùy chỉnh đơn giản nhất (được đề xuất bởi Vajda Gabo trong bài đăng danh sách gửi thư này , trong số nhiều người khác) là sử dụng textcatchức năng tích hợp (nằm phía sau ||toán tử):

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

Đây là CREATE AGGREGATEtài liệu.

Điều này chỉ đơn giản là dán tất cả các chuỗi với nhau, không có dấu phân cách. Để có được một "," được chèn vào giữa chúng mà không có nó ở cuối, bạn có thể muốn tạo chức năng nối riêng của mình và thay thế nó cho "textcat" ở trên. Đây là một cái tôi ghép lại và thử nghiệm vào ngày 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;

Phiên bản này sẽ xuất ra dấu phẩy ngay cả khi giá trị trong hàng là null hoặc trống, vì vậy bạn nhận được đầu ra như thế này:

a, b, c, , e, , g

Nếu bạn muốn xóa dấu phẩy thừa để xuất kết quả này:

a, b, c, e, g

Sau đó thêm một ELSIFkiểm tra cho chức năng như thế này:

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
Tôi đã phải S & R varchar để nhắn tin (pssql ổn định mới nhất) nhưng điều này thật tuyệt!
Kev

1
Bạn chỉ có thể viết hàm bằng SQL, việc cài đặt dễ dàng hơn (plpgsql phải được cài đặt bởi superuser). Xem bài viết của tôi cho một ví dụ.
bortzmeyer

11
"Không có chức năng tổng hợp tích hợp để nối chuỗi" - tại sao bạn không sử dụng array_to_string(array_agg(employee), ',')?
pstanton

2
+1 cho chức năng PostgreSQL 9.0. Nếu bạn cần quan tâm về tiền 9.0, câu trả lời của Markus sẽ tốt hơn.
Brad Koch

7
Lưu ý rằng các phiên bản gần đây của Postgres cũng cho phép một Order Bykhoản bên trong hàm tổng hợp, ví dụ nhưstring_agg(employee, ',' Order By employee)
IMSoP

99

Làm thế nào về việc sử dụng các hàm mảng tích hợp Postgres? Ít nhất là trên 8.4 điều này hoạt động ra khỏi hộp:

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

thật đáng buồn khi điều này không hiệu quả với chúng tôi trên Greenplum (v8.2). +1 tất cả giống nhau
ekkis

Hoạt động tốt với tôi trên Greenplum 4.3.4.1 (được xây dựng trên PostgreQuery 8.2.15).
PhilHibbs

19

Kể từ PostgreSQL 9.0, bạn có thể sử dụng hàm tổng hợp có tên là string_agg . SQL mới của bạn sẽ trông giống như thế này:

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


13

Tôi khẳng định không có tín dụng cho câu trả lời vì tôi đã tìm thấy nó sau một số tìm kiếm:

Điều tôi không biết là PostgreSQL cho phép bạn xác định các hàm tổng hợp của riêng mình với CREATE AGGREGATE

Bài đăng này trong danh sách PostgreSQL cho thấy việc tạo ra một chức năng để thực hiện những gì được yêu cầu là tầm thường:

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

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

7

Như đã đề cập, tạo chức năng tổng hợp của riêng bạn là điều nên làm. Đây là hàm tổng hợp nối của tôi (bạn có thể tìm thấy chi tiết bằng tiếng Pháp ):

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

);

Và sau đó sử dụng nó như:

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

5

Đoạn danh sách thông báo mới nhất này có thể được quan tâm nếu bạn sẽ nâng cấp lên 8.4:

Cho đến khi 8.4 xuất hiện với một nguồn gốc siêu hiệu quả, bạn có thể thêm hàm Array_accum () trong tài liệu PostgreQuery để cuộn bất kỳ cột nào thành một mảng, sau đó có thể được sử dụng bởi mã ứng dụng hoặc kết hợp với mảng_to_opes () để định dạng nó như một danh sách:

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

Tôi muốn liên kết đến các tài liệu phát triển 8.4 nhưng dường như họ chưa liệt kê tính năng này.


5

Theo dõi câu trả lời của Kev, sử dụng tài liệu Postgres:

Đầu tiên, tạo một mảng các phần tử, sau đó sử dụng hàm dựng sẵn array_to_string.

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

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

5

Tiếp theo một lần nữa về việc sử dụng hàm tổng hợp tùy chỉnh của nối chuỗi: bạn cần nhớ rằng câu lệnh select sẽ đặt các hàng theo bất kỳ thứ tự nào, vì vậy bạn sẽ cần thực hiện một lựa chọn phụ trong câu lệnh from với một mệnh đề theo mệnh đề và sau đó chọn bên ngoài với một nhóm theo mệnh đề để tổng hợp các chuỗi, do đó:

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

Tôi thấy tài liệu PostgreSQL này hữu ích: http://www.postgresql.org/docs/8.0/interactive/fifts-conditable.html .

Trong trường hợp của tôi, tôi đã tìm kiếm SQL đơn giản để nối một trường có dấu ngoặc quanh nó, nếu trường không trống.

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


0

Theo phiên bản PostgreSQL 9.0 trở lên, bạn có thể sử dụng hàm tổng hợp có tên là string_agg. SQL mới của bạn sẽ trông giống như thế này:

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

0

Bạn cũng có thể sử dụng chức năng định dạng. Mà cũng có thể tự chăm sóc chuyển đổi loại văn bản, int, vv.

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
Làm thế nào điều này có liên quan đến việc sử dụng một tổng hợp để nối các giá trị chuỗi?
a_horse_with_no_name

0

Tôi đang sử dụng Jetbrains Rider và thật rắc rối khi sao chép các kết quả từ các ví dụ trên để thực thi lại bởi vì nó dường như bao bọc tất cả trong JSON. Điều này kết hợp chúng thành một câu lệnh dễ chạy hơn

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

0

Nếu bạn đang ở trên Amazon Redshift, nơi chuỗi_agg không được hỗ trợ, hãy thử sử dụng listagg.

SELECT company_id, listagg(EMPLOYEE, ', ') as employees
FROM EMPLOYEE_table
GROUP BY company_id;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.