UNION ALL
Você pode "contra-pivot" com
UNION ALL
primeiro: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;
Adaptado para produzir a ordem de valores que você solicitou posteriormente. O manual:
Minha ênfase em negrito.
LATERAL
subconsulta
com VALUES
expressão
LATERAL
requer o Postgres 9.3 ou mais tarde. 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;
Mesmo resultado. Só precisa de uma única passagem sobre a mesa.
Função de agregação personalizada
Ou você pode criar uma função de agregação personalizada, conforme discutido nestas respostas relacionadas:
- Selecionar dados em um Matriz Postgres
- Existe algo como uma função zip() no PostgreSQL que combina dois arrays?
CREATE AGGREGATE array_agg_mult (anyarray) (
SFUNC = array_cat
, STYPE = anyarray
, INITCOND = '{}'
);
Então você pode:
SELECT name, array_agg_mult(ARRAY[col1, col2] ORDER BY id) AS c_arr
FROM tbl
GROUP BY 1
ORDER BY 1;
Ou, normalmente mais rápido, embora não seja SQL padrão:
SELECT name, array_agg_mult(ARRAY[col1, col2]) AS c_arr
FROM (SELECT * FROM tbl ORDER BY name, id) t
GROUP BY 1;
O
ORDER BY id
adicionado (que pode ser anexado a essas funções agregadas) garante o resultado desejado:a | {1,2,3,4}
b | {5,6,7,8}
Ou você pode estar interessado nesta alternativa:
SELECT name, array_agg_mult(ARRAY[ARRAY[col1, col2]] ORDER BY id) AS c_arr
FROM tbl
GROUP BY 1
ORDER BY 1;
Que produz arrays bidimensionais:
a | {{1,2},{3,4}}
b | {{5,6},{7,8}}
O último pode ser substituído (e deve ser, pois é mais rápido!) pelo
array_agg()
embutido no Postgres 9.5 ou posterior - com sua capacidade adicional de agregar arrays:SELECT name, array_agg(ARRAY[col1, col2] ORDER BY id) AS c_arr
FROM tbl
GROUP BY 1
ORDER BY 1;
Mesmo resultado. O manual:
Portanto, não é exatamente o mesmo que nossa função de agregação personalizada
array_agg_mult()
;