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

时间:2020-03-05 18:47:38  来源:igfitidea点击:

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

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

解决方案

回答

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

我不知道的是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;

回答

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;

9.0以下版本的string_agg

如果有人遇到这种情况,希望为9.0之前版本的数据库提供兼容性,则可以在string_agg中实现除ORDER 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  IS NULL THEN
                RETURN ;
            ELSE
                RETURN  ||  || ;
            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;

回答

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

CREATE OR REPLACE FUNCTION concat2(text, text) RETURNS text AS '
    SELECT CASE WHEN  IS NULL OR  = \'\' THEN 
            WHEN  IS NULL OR  = \'\' THEN 
            ELSE  || \' / \' || 
            END; 
'
 LANGUAGE SQL;

CREATE AGGREGATE concatenate (
  sfunc = concat2,
  basetype = text,
  stype = text,
  initcond = ''

);

然后将其用作:

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

回答

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

Until 8.4 comes out with a
  super-effient native one, you can add
  the array_accum() function in the
  PostgreSQL documentation for rolling
  up any column into an array, which can
  then be used by application code, or
  combined with array_to_string() to
  format it as a list:
  
  http://www.postgresql.org/docs/current/static/xaggr.html

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

回答

我发现此PostgreSQL文档很有帮助:http://www.postgresql.org/docs/8.0/interactive/functions-conditional.html。

就我而言,如果该字段不为空,我希望使用普通的SQL将一个带有方括号的字段连接起来。

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

回答

使用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;

回答

再次使用字符串连接的自定义聚合函数:我们需要记住,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

回答

如何使用Postgres内置数组函数?至少在8.4上这是开箱即用的:

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