28

我在 PostgreSQL 中有两个长度相同的数组值:

{a,b,c}{d,e,f}

我想把它们组合成

{{a,d},{b,e},{c,f}}

有没有办法做到这一点?

4

2 回答 2

41

Postgres 9.5 或更高版本

array_agg(array expression)

array_agg( anyarray) →anyarray

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

array_agg_mult()这是下面演示的我的自定义聚合函数的直接替换。它是用 C 语言实现的,而且速度要快得多。用它。

Postgres 9.4

使用将多个数组并行取消嵌套的ROWS FROM构造或更新。unnest()每个都可以有不同的长度。你得到(根据文档):

[...] 在这种情况下,结果行数是最大函数结果的行数,较小的结果用空值填充以匹配。

使用这个更简洁的变体:

SELECT ARRAY[a,b] AS ab
FROM   unnest('{a,b,c}'::text[] 
            , '{d,e,f}'::text[]) x(a,b);

Postgres 9.3 或更高版本

简单的 zip()

考虑以下 Postgres 9.3 或更早版本的演示:

SELECT ARRAY[a,b] AS ab
FROM  (
   SELECT unnest('{a,b,c}'::text[]) AS a
        , unnest('{d,e,f}'::text[]) AS b
    ) x;

结果:

  ab
-------
 {a,d}
 {b,e}
 {c,f}

请注意,两个数组必须具有相同数量的元素才能并行取消嵌套,否则您将获得交叉连接。

如果您愿意,可以将其包装成一个函数:

CREATE OR REPLACE FUNCTION zip(anyarray, anyarray)
  RETURNS SETOF anyarray LANGUAGE SQL AS
$func$
SELECT ARRAY[a,b] FROM (SELECT unnest($1) AS a, unnest($2) AS b) x;
$func$;

称呼:

SELECT zip('{a,b,c}'::text[],'{d,e,f}'::text[]);

结果相同。

zip() 到多维数组:

现在,如果你想将一组新数组聚合成一个二维数组,它会变得更加复杂。

SELECT ARRAY (SELECT ...)

或者:

SELECT array_agg(ARRAY[a,b]) AS ab
FROM  (
   SELECT unnest('{a,b,c}'::text[]) AS a
         ,unnest('{d,e,f}'::text[]) AS b
    ) x

或者:

SELECT array_agg(ARRAY[ARRAY[a,b]]) AS ab
FROM  ...

都会导致相同的错误消息(使用 pg 9.1.5 测试):

错误:找不到数据类型 text[] 的数组类型

但是有一种方法可以解决这个问题,正如我们在这个密切相关的问题下所解决的那样。
创建自定义聚合函数:

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

并像这样使用它:

SELECT array_agg_mult(ARRAY[ARRAY[a,b]]) AS ab
FROM  (
   SELECT unnest('{a,b,c}'::text[]) AS a
        , unnest('{d,e,f}'::text[]) AS b
    ) x

结果:

{{a,d},{b,e},{c,f}}

注意附加ARRAY[]层!没有它,只是:

SELECT array_agg_mult(ARRAY[a,b]) AS ab
FROM ...

你得到:

{a,d,b,e,c,f}

这可能对其他目的有用。

滚动另一个函数:

CREATE OR REPLACE FUNCTION zip2(anyarray, anyarray)
  RETURNS SETOF anyarray LANGUAGE SQL AS
$func$
SELECT array_agg_mult(ARRAY[ARRAY[a,b]])
FROM (SELECT unnest($1) AS a, unnest($2) AS b) x;
$func$;

称呼:

SELECT zip2('{a,b,c}'::text[],'{d,e,f}'::text[]); -- or any other array type

结果:

{{a,d},{b,e},{c,f}}
于 2012-09-13T21:14:12.313 回答
8

这是另一种对不同长度的数组安全的方法,使用 Erwin 提到的数组多重聚合:

CREATE OR REPLACE FUNCTION zip(array1 anyarray, array2 anyarray) RETURNS text[]
AS $$
SELECT array_agg_mult(ARRAY[ARRAY[array1[i],array2[i]]])
FROM generate_subscripts(
  CASE WHEN array_length(array1,1) >= array_length(array2,1) THEN array1 ELSE array2 END,
  1
) AS subscripts(i)
$$ LANGUAGE sql;

regress=> SELECT zip('{a,b,c}'::text[],'{d,e,f}'::text[]);
         zip         
---------------------
 {{a,d},{b,e},{c,f}}
(1 row)


regress=> SELECT zip('{a,b,c}'::text[],'{d,e,f,g}'::text[]);
             zip              
------------------------------
 {{a,d},{b,e},{c,f},{NULL,g}}
(1 row)

regress=> SELECT zip('{a,b,c,z}'::text[],'{d,e,f}'::text[]);
             zip              
------------------------------
 {{a,d},{b,e},{c,f},{z,NULL}}
(1 row)

如果你想去掉多余的而不是空填充,只需将>=长度测试<=改为。

这个函数不能处理相当奇怪的 PostgreSQL 特性,即数组可能有一个非 1 的声明元素,但实际上没有人真正使用该特性。例如,使用零索引的 3 元素数组:

regress=> SELECT zip('{a,b,c}'::text[], array_fill('z'::text, ARRAY[3], ARRAY[0]));
          zip           
------------------------
 {{a,z},{b,z},{c,NULL}}
(1 row)

而 Erwin 的代码确实适用于此类数组,甚至适用于多维数组(通过展平它们),但不适用于不同长度的数组。

数组在 PostgreSQL 中有点特殊,它们对于多维数组、可配置的原点索引等有点过于灵活。

在 9.4 中,您将能够编写:

SELECT array_agg_mult(ARRAY[ARRAY[a,b])
FROM unnest(array1) WITH ORDINALITY as (o,a)
NATURAL FULL OUTER JOIN
unnest(array2) WITH ORDINALITY as (o,b);

这会更好,特别是如果将函数一起扫描而不是进行排序和连接的优化进入。

于 2013-07-29T06:03:39.940 回答