【问题标题】:Joining arrays within group by clause在 group by 子句中连接数组
【发布时间】:2014-08-24 19:01:02
【问题描述】:

我们在将数组分组为单个数组时遇到了问题。 我们希望将两列中的值连接到一个数组中,并将这些数组聚合为多行。

给定以下输入:

| id | name | col_1 | col_2 |
| 1  |  a   |   1   |   2   |
| 2  |  a   |   3   |   4   |
| 4  |  b   |   7   |   8   |
| 3  |  b   |   5   |   6   |

我们想要以下输出:

| a | { 1, 2, 3, 4 } |
| b | { 5, 6, 7, 8 } |

元素的顺序很重要,应该与聚合行的 id 相关。

我们尝试了array_agg() 函数:

SELECT array_agg(ARRAY[col_1, col_2]) FROM mytable GROUP BY name;

很遗憾,此语句引发错误:

ERROR: could not find array type for data type character varying[]

似乎不可能使用array_agg() 合并group by 子句中的数组。

有什么想法吗?

【问题讨论】:

    标签: sql arrays postgresql group-by postgresql-9.1


    【解决方案1】:

    UNION ALL

    你可以先用UNION ALL“反击”:

    SELECT name, array_agg(c) AS c_arr
    FROM  (
       SELECT name, id, 1 AS rnk, col1 AS c FROM tbl
       UNION ALL
       SELECT name, id, 2, col2 FROM tbl
       ORDER  BY name, id, rnk
       ) sub
    GROUP  BY 1;
    

    适用于生成您稍后请求的值的顺序。 The manual:

    聚合函数array_aggjson_aggstring_aggxmlagg, 以及类似的用户定义的聚合函数,产生 有意义的不同结果值取决于 输入值。默认情况下未指定此顺序,但可以 通过在聚合调用中编写 ORDER BY 子句来控制,如 如第 4.2.7 节所示。或者,从 排序的子查询通常会起作用。

    我的大胆强调。

    LATERAL subqueryVALUES 表达式

    LATERAL 需要 Postgres 9.3 或更高版本。

    SELECT t.name, array_agg(c) AS c_arr
    FROM  (SELECT * FROM tbl ORDER BY name, id) t
    CROSS  JOIN LATERAL (VALUES (t.col1), (t.col2)) v(c)
    GROUP  BY 1;
    

    同样的结果。只需要一次通过桌子。

    自定义聚合函数

    或者您可以创建一个自定义聚合函数,就像这些相关答案中讨论的那样:

    CREATE AGGREGATE array_agg_mult (anyarray)  (
        SFUNC     = array_cat
      , STYPE     = anyarray
      , INITCOND  = '{}'
    );
    

    那么你可以:

    SELECT name, array_agg_mult(ARRAY[col1, col2] ORDER BY id) AS c_arr
    FROM   tbl
    GROUP  BY 1
    ORDER  BY 1;
    

    或者,通常更快,但不是标准 SQL:

    SELECT name, array_agg_mult(ARRAY[col1, col2]) AS c_arr
    FROM  (SELECT * FROM tbl ORDER BY name, id) t
    GROUP  BY 1;
    

    添加的ORDER BY id(可以附加到此类聚合函数)保证了您想要的结果:

    a | {1,2,3,4}
    b | {5,6,7,8}
    

    或者您可能对此替代方案感兴趣:

    SELECT name, array_agg_mult(ARRAY[ARRAY[col1, col2]] ORDER BY id) AS c_arr
    FROM   tbl
    GROUP  BY 1
    ORDER  BY 1;
    

    生成二维数组:

    a | {{1,2},{3,4}}
    b | {{5,6},{7,8}}
    

    最后一个可以用 Postgres 9.5 或更高版本中的内置 array_agg() 替换(并且应该是,因为它更快!) - 增加了聚合数组的功能:

    SELECT name, array_agg(ARRAY[col1, col2] ORDER BY id) AS c_arr
    FROM   tbl
    GROUP  BY 1
    ORDER  BY 1;
    

    同样的结果。 The manual:

    输入数组连接成一维数组(输入 必须都具有相同的维度,并且不能为空或 null)

    所以和我们自定义的聚合函数array_agg_mult()不完全一样;

    【讨论】:

    • 我想避免创建自定义函数并尽可能仅使用内置函数。尽管如此,使用自定义函数可以解决我的问题。
    • @tbz:所有解决方案现在都应该可以工作了,还有 Clodoaldo 提供的并行取消嵌套。
    • UNION ALL 解决方案是我一直在寻找的解决方案。为什么子查询速度更快的不是 SQL 标准?
    • @tbz:这只是 Postgres 中使用排序输入的一个实现细节。这在基于集合的逻辑中没有位置,但是对输入进行一次排序通常比在聚合输出中按组排序更有效。这就是为什么手册只说“通常会起作用”......
    • @strider:有多种方法。您需要准确定义符合“重复”的条件。确切的数据类型也可能是相关的。在任何情况下,您都不能简单地将关键字 DISTINCT 添加到聚合调用中以删除重复的基本元素。而且您可能需要特殊情况的 NULL 值。请提出一个包含所有相关详细信息的新问题。您始终可以链接到此链接以获取上下文,并在此处添加另一条评论以链接回(并引起我的注意)。我在这里时应用了上面的一些更新。
    【解决方案2】:
    select n, array_agg(c) as c
    from (
        select n, unnest(array[c1, c2]) as c
        from t
    ) s
    group by n
    

    或者更简单

    select
        n,
        array_agg(c1) || array_agg(c2) as c
    from t
    group by n
    

    解决新的订购要求:

    select n, array_agg(c order by id, o) as c
    from (
        select
            id, n,
            unnest(array[c1, c2]) as c,
            unnest(array[1, 2]) as o
        from t
    ) s
    group by n
    

    【讨论】:

    • 您的解决方案创建了两种不同顺序的数组元素。不幸的是,我忘了提到元素的顺序很重要。我编辑了我的问题以澄清这一点。
    • +1 声明管道在数组较小时连接数组,您只需将所有值传递给另一个函数并且不需要排序
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-17
    • 2019-07-13
    • 2018-03-23
    • 2015-05-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多