如何在PostgreSQL“分组依据”查询中串联字符串字段的字符串?


351

我正在寻找一种通过查询来连接一个组内字段字符串的方法。例如,我有一张桌子:

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

我想按company_id分组以获取类似信息:

COMPANY_ID   EMPLOYEE
1            Anna, Bill
2            Carol, Dave

mySQL中有一个内置函数来执行此group_concat


1
MarkusDöring的答案在技术上更好。
pstanton 2011年

@ pstanton,Döring的答案仅适用于8.4及以下。
杰里德·贝克

这个问题似乎更适合dba.stackexchange.com
戴夫·贾维斯

现在应该是有效的答案stackoverflow.com/a/47638417/243233
Jus12'4 Dec4

Answers:


542

PostgreSQL 9.0或更高版本:

Postgres的最新版本(自2010年末开始)具有string_agg(expression, delimiter)可以完全满足问题要求的功能,甚至允许您指定分隔符字符串:

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

Postgres 9.0还增加了在任何聚合表达式中指定ORDER BY子句的功能;否则,顺序是不确定的。因此,您现在可以编写:

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

或者确实是:

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

PostgreSQL 8.4或更高版本:

PostgreSQL 8.4(2009年)引入了聚合函数array_agg(expression),该函数将值连接成一个数组。然后array_to_string()可以用来给出期望的结果:

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

string_agg 对于8.4之前的版本:

如果有人遇到这种情况,希望为9.0之前版本的数据库提供兼容的填充程序,则可以实现string_aggORDER BY子句以外的所有内容。

因此,使用以下定义,该方法应与9.x Postgres DB中的相同:

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

但这将是语法错误:

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

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

自定义版本(所有Postgres版本)

在9.0之前,没有内置的聚合函数来连接字符串。最简单的自定义实现(由Vajda Gabo在此邮件列表中的建议,以及其他许多方面)是使用内置textcat函数(位于||运算符后面):

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

这是CREATE AGGREGATE文档。

这只是将所有琴弦粘在一起,没有分隔符。为了使它们之间没有插入“,”,您可能想要创建自己的串联函数,并将其替换为上面的“ textcat”。这是我整理并在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;

即使该行中的值为null或为空,此版本也会输出逗号,因此您将获得如下输出:

a, b, c, , e, , g

如果您希望删除多余的逗号以输出此内容:

a, b, c, e, g

然后将ELSIF检查添加到这样的函数中:

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转换为文本(最新的pgsql稳定),但这很棒!
Kev

1
您只能使用SQL编写该函数,该函数易于安装(超级用户必须安装plpgsql)。请参阅我的帖子中的示例。
bortzmeyer

11
“没有内置的聚合函数来连接字符串”-为什么不使用array_to_string(array_agg(employee), ',')
pstanton 2011年

2
PostgreSQL 9.0函数的+1。如果您需要关注9.0之前的版本,Markus的答案会更好。
布拉德·科赫

7
请注意,最新版本的Postgres还允许Order By在聚合函数内添加子句,例如string_agg(employee, ',' Order By employee)
IMSoP 2013年

98

如何使用Postgres内置数组函数?至少在8.4上可以立即使用:

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

遗憾的是,这对于Greenplum(v8.2)来说不起作用。+1都一样
ekkis

在Greenplum 4.3.4.1(建立在PostgreSQL 8.2.15上)上,对我来说工作正常。
PhilHibbs

19

从PostgreSQL 9.0开始,您可以使用称为string_agg的聚合函数。您的新SQL应该看起来像这样:

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


13

我对这个答案不屑一顾,因为我经过一番搜索发现了它:

我不知道的是PostgreSQL允许您使用CREATE AGGREGATE定义自己的聚合函数

PostgreSQL列表上的该帖子显示了创建一个函数来执行所需的操作是多么简单:

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

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

7

如前所述,创建自己的聚合函数是正确的事情。这是我的串联聚合函数(您可以在法语中找到详细信息):

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

);

然后将其用作:

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

5

如果您要升级到8.4,则可能需要关注最新的公告列表片段:

在8.4发行超高效本机代码之前,您可以在PostgreSQL文档中添加array_accum()函数以将任何列汇总到数组中,然后可由应用程序代码使用,或与array_to_string()组合以进行格式化作为清单:

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

我将链接到8.4开发文档,但他们似乎还没有列出此功能。


5

使用Postgres文档跟踪Kev的答案:

首先,创建元素数组,然后使用内置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

再次使用字符串连接的自定义聚合函数:您需要记住,select语句将以任何顺序放置行,因此您需要在from语句中使用order by子句进行子选择,并且然后使用带有group by子句的外部select来聚合字符串,因此:

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及更高版本,您可以使用称为string_agg的聚合函数。您的新SQL应该看起来像这样:

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

0

您也可以使用格式化功能。它本身也可以隐式地处理文本,int等的类型转换。

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
这与使用聚合来连接字符串值有什么关系?
a_horse_with_no_name

0

我正在使用Jetbrains Rider,将上述示例中的结果复制到重新执行很麻烦,因为它似乎都将其包装在JSON中。这将它们合并为一个更易于运行的语句

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

0

如果您在不支持string_agg的Amazon Redshift上,请尝试使用listagg。

SELECT company_id, listagg(EMPLOYEE, ', ') as employees
FROM EMPLOYEE_table
GROUP BY company_id;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.