成对数组和聚合函数?

时间:2014-07-28 14:08:43

标签: postgresql aggregate-functions

我有一个数组作为一列的表,我想将数组元素加在一起:

> create table regres(a int[] not null);
> insert into regres values ('{1,2,3}'), ('{9, 12, 13}');
> select * from regres;
     a
-----------
 {1,2,3}
 {9,12,13}

我希望结果是:

{10, 14, 16}

即:{1 + 9, 2 + 12, 3 + 13}

这样的功能在某处已经存在吗?插入扩展看起来是一个很好的候选者,但这样的功能还不存在。

数组的长度预计在24到31个元素之间,所有元素都是NOT NULL,数组本身也总是NOT NULL。所有元素都是基本的int。每个聚合将有两行以上。在查询中,所有数组都将具有相同数量的元素。不同的查询将具有不同数量的元素。

我的实现目标是:x86_64-unknown-linux-gnu上的PostgreSQL 9.1.13,由gcc编译(Ubuntu / Linaro 4.6.3-1ubuntu5)4.6.3,64位

3 个答案:

答案 0 :(得分:13)

Postgres 9.3 + 中的一般解决方案,适用于任意数量的元素阵列。
单个元素或整个数组也可以为NULL:

SELECT ARRAY (
   SELECT sum(arr[rn])
   FROM  tbl t
       , generate_subscripts(t.arr, 1) AS rn
   GROUP BY rn
   ORDER BY rn
   );

这使用了隐含的LATERAL JOIN(Postgres 9.3+) 使用您的示例值:

SELECT ARRAY (
   SELECT sum(arr[rn])
   FROM  (
      VALUES
        ('{1,2,3}'::int[])
       ,('{9,12,13}')
      ) t(arr)
    , generate_subscripts(t.arr, 1) AS rn
   GROUP BY rn
   ORDER BY rn
   );

非平凡的例子:

SELECT ARRAY (
   SELECT sum(arr[rn])
   FROM  (
      VALUES
        ('{1,2,3}'::int[])
       ,('{9,12,13}')
       ,('{1,1,1, 33}')
       ,('{NULL,NULL}')
       ,(NULL)
      ) t(arr)
    , generate_subscripts(t.arr, 1) AS rn
   GROUP BY rn
   ORDER BY rn
   );

使用WITH ORDINALITY

在9.4+中更简单
SELECT ARRAY (
   SELECT sum(elem)
   FROM  tbl t
       , unnest(t.arr) WITH ORDINALITY x(elem, rn)
   GROUP BY rn
   ORDER BY rn
   )

Postgres 9.1

SELECT ARRAY (
   SELECT sum(arr[rn])
   FROM  (
      SELECT arr, generate_subscripts(arr, 1) AS rn
      FROM   tbl t
      ) sub
   GROUP BY rn
   ORDER BY rn
   );

在更高版本中也是如此,但SELECT列表中的 set-returns函数不是标准SQL,而且有些人不赞成。因此,请使用上述替代品与当前的Postgres。

SQL Fiddle.

相关答案以及更多解释:

答案 1 :(得分:1)

我知道最初的问题和答案已经很旧了,但是对于发现此问题的其他人...我发现的最优雅、最灵活的解决方案是创建自定义聚合函数。如果您只需要单个结果数组,Erwin 的回答提供了一些很好的简单解决方案,但不会转换为可能包含其他表列和聚合的解决方案,例如在 GROUP BY 中。

使用自定义 array_add 函数和 array_sum 聚合函数:

CREATE OR REPLACE FUNCTION array_add(_a numeric[], _b numeric[])
  RETURNS numeric[]
AS
$$
BEGIN
  RETURN ARRAY(
    SELECT coalesce(a, 0) + coalesce(b, 0)
    FROM unnest(_a, _b) WITH ORDINALITY AS x(a, b, n)
    ORDER BY n
  );
END
$$ LANGUAGE plpgsql;

CREATE AGGREGATE array_sum(numeric[])
(
  sfunc = array_add,
  stype = numeric[],
  initcond = '{}'
);

然后(使用您示例中的名称):

SELECT array_sum(a) a_sums
FROM regres;

返回您的总和数组,它也可以用于任何可以使用其他聚合函数的地方,所以如果您的表也有一个列 name 您想要分组,以及另一个数字数组,列b

SELECT name, array_sum(a) a_sums, array_sum(b) b_sums
FROM regres
GROUP BY name;

您不会获得从内置 sum 函数中获得的性能,只需选择 sum(a[1]), sum(a[2]), sum(a[3]),您必须将 array_add 函数实现为一个编译的 C 函数来获得它。但是,如果您无法添加自定义 C 函数(例如托管云数据库,例如 AWS RDS),或者您没有聚合大量行,则可能不会注意到差异。

答案 2 :(得分:0)

如果您需要更好的性能并可以安装Postgres扩展,则agg_for_vecs C扩展提供了vec_to_sum函数,该函数应满足您的需求。它还提供了各种聚合函数,例如minmaxavgvar_samp,它们在数组而不是标量上运行。