Подтвердить что ты не робот

Есть ли что-то вроде функции zip() в PostgreSQL, которая объединяет два массива?

У меня есть два значения массива одинаковой длины в PostgreSQL:

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

и я хотел бы объединить их в

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

Есть ли способ сделать это?

4b9b3361

Ответ 1

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() для многомерного массива:

Теперь, если вы хотите заполнить этот новый набор массивов в один массив 2-dimenstional, он становится более сложным.

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):

ОШИБКА: не удалось найти тип массива для текста типа данных []

Но есть способ обойти это, как мы разработали в этот близкий вопрос.
 Создайте настраиваемую функцию агрегации:

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}}

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.5 +

отправляет array_agg(array expression):

 
Function                Argument Type(s)   Return Type
array_agg(expression)   any array type     same as argument data type  

Description
input arrays concatenated into array of one higher dimension
(inputs must all have same dimensionality, and cannot be empty or NULL)

Это замена для моей настраиваемой агрегатной функции array_agg_mult(), реализованной на C, которая значительно быстрее. Используйте его.

Ответ 2

Здесь другой подход, безопасный для массивов различной длины, с использованием мультиагрегации массива, упомянутой Эрвином:

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

что будет намного приятнее, особенно если оптимизация для сканирования функций вместе, а не для сортировки и объединения.