Se você não instalou o módulo adicional tablefunc , execute este comando uma vez por banco de dados:
CREATE EXTENSION tablefunc;
Resposta à pergunta
Uma solução de crosstab muito básica para o seu caso:
SELECT * FROM crosstab(
'SELECT bar, 1 AS cat, feh
FROM tbl_org
ORDER BY bar, feh')
AS ct (bar text, val1 int, val2 int, val3 int); -- more columns?
A dificuldade especial aqui está, que não há categoria (
cat
) na tabela base. Para o formulário de 1 parâmetro básico podemos apenas fornecer uma coluna fictícia com um valor fictício servindo como categoria. O valor é ignorado de qualquer maneira. Este é um dos casos raros onde o segundo parâmetro para o
crosstab()
função não é necessária , porque todos os NULL
os valores só aparecem em colunas pendentes à direita por definição deste problema. E o pedido pode ser determinado pelo valor . Se tivéssemos uma categoria real coluna com nomes determinando a ordem dos valores no resultado, precisaríamos do formulário de 2 parâmetros de
crosstab()
. Aqui eu sintetizo uma coluna de categoria com a ajuda da função de janela row_number()
, para basear crosstab()
em:SELECT * FROM crosstab(
$$
SELECT bar, val, feh
FROM (
SELECT *, 'val' || row_number() OVER (PARTITION BY bar ORDER BY feh) AS val
FROM tbl_org
) x
ORDER BY 1, 2
$$
, $$VALUES ('val1'), ('val2'), ('val3')$$ -- more columns?
) AS ct (bar text, val1 int, val2 int, val3 int); -- more columns?
O resto é muito comum. Encontre mais explicações e links nestas respostas intimamente relacionadas.
Noções básicas:
Leia isto primeiro se você não estiver familiarizado com o
crosstab()
função! - Consulta de tabela cruzada PostgreSQL
Avançado:
- Pivotar em várias colunas usando Tablefunc
- Mesclar uma tabela e um log de alterações em uma visualização no PostgreSQL
Configuração de teste adequada
É assim que você deve fornecer um caso de teste para começar:
CREATE TEMP TABLE tbl_org (id int, feh int, bar text);
INSERT INTO tbl_org (id, feh, bar) VALUES
(1, 10, 'A')
, (2, 20, 'A')
, (3, 3, 'B')
, (4, 4, 'B')
, (5, 5, 'C')
, (6, 6, 'D')
, (7, 7, 'D')
, (8, 8, 'D');
Reta cruzada dinâmica?
Não muito dinâmico , ainda, como comentou @Clodoaldo. Tipos de retorno dinâmico são difíceis de alcançar com plpgsql. Mas existem há maneiras de contornar isso - com algumas limitações .
Então, para não complicar ainda mais o resto, demonstro com um método mais simples caso de teste:
CREATE TEMP TABLE tbl (row_name text, attrib text, val int);
INSERT INTO tbl (row_name, attrib, val) VALUES
('A', 'val1', 10)
, ('A', 'val2', 20)
, ('B', 'val1', 3)
, ('B', 'val2', 4)
, ('C', 'val1', 5)
, ('D', 'val3', 8)
, ('D', 'val1', 6)
, ('D', 'val2', 7);
Ligar:
SELECT * FROM crosstab('SELECT row_name, attrib, val FROM tbl ORDER BY 1,2')
AS ct (row_name text, val1 int, val2 int, val3 int);
Devoluções:
row_name | val1 | val2 | val3
----------+------+------+------
A | 10 | 20 |
B | 3 | 4 |
C | 5 | |
D | 6 | 7 | 8
Recurso integrado de tablefunc
módulo
O módulo tablefunc fornece uma infraestrutura simples para
crosstab()
genérico chamadas sem fornecer uma lista de definição de coluna. Várias funções escritas em C
(normalmente muito rápido):crosstabN()
crosstab1()
- crosstab4()
são pré-definidos. Um ponto menor:eles exigem e retornam todo o text
. Então, precisamos converter nosso integer
valores. Mas simplifica a chamada:SELECT * FROM crosstab4('SELECT row_name, attrib, val::text -- cast!
FROM tbl ORDER BY 1,2')
Resultado:
row_name | category_1 | category_2 | category_3 | category_4
----------+------------+------------+------------+------------
A | 10 | 20 | |
B | 3 | 4 | |
C | 5 | | |
D | 6 | 7 | 8 |
crosstab()
personalizado função
Para mais colunas ou outros tipos de dados , criamos nosso próprio tipo composto e função (uma vez).
Tipo:
CREATE TYPE tablefunc_crosstab_int_5 AS (
row_name text, val1 int, val2 int, val3 int, val4 int, val5 int);
Função:
CREATE OR REPLACE FUNCTION crosstab_int_5(text)
RETURNS SETOF tablefunc_crosstab_int_5
AS '$libdir/tablefunc', 'crosstab' LANGUAGE c STABLE STRICT;
Ligar:
SELECT * FROM crosstab_int_5('SELECT row_name, attrib, val -- no cast!
FROM tbl ORDER BY 1,2');
Resultado:
row_name | val1 | val2 | val3 | val4 | val5
----------+------+------+------+------+------
A | 10 | 20 | | |
B | 3 | 4 | | |
C | 5 | | | |
D | 6 | 7 | 8 | |
Um função polimórfica e dinâmica para todos
Isso vai além do que é coberto pelo
tablefunc
módulo. Para tornar o tipo de retorno dinâmico, uso um tipo polimórfico com uma técnica detalhada nesta resposta relacionada:
- Refatorar uma função PL/pgSQL para retornar a saída de várias consultas SELECT
Formulário de 1 parâmetro:
CREATE OR REPLACE FUNCTION crosstab_n(_qry text, _rowtype anyelement)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE
(SELECT format('SELECT * FROM crosstab(%L) t(%s)'
, _qry
, string_agg(quote_ident(attname) || ' ' || atttypid::regtype
, ', ' ORDER BY attnum))
FROM pg_attribute
WHERE attrelid = pg_typeof(_rowtype)::text::regclass
AND attnum > 0
AND NOT attisdropped);
END
$func$ LANGUAGE plpgsql;
Sobrecarregue com esta variante para o formulário de 2 parâmetros:
CREATE OR REPLACE FUNCTION crosstab_n(_qry text, _cat_qry text, _rowtype anyelement)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE
(SELECT format('SELECT * FROM crosstab(%L, %L) t(%s)'
, _qry, _cat_qry
, string_agg(quote_ident(attname) || ' ' || atttypid::regtype
, ', ' ORDER BY attnum))
FROM pg_attribute
WHERE attrelid = pg_typeof(_rowtype)::text::regclass
AND attnum > 0
AND NOT attisdropped);
END
$func$ LANGUAGE plpgsql;
pg_typeof(_rowtype)::text::regclass
:Existe um tipo de linha definido para cada tipo composto definido pelo usuário, para que os atributos (colunas) sejam listados no catálogo do sistema pg_attribute
. O caminho mais rápido para obtê-lo:converta o tipo registrado (regtype
) para text
e converta este text
para regclass
. Crie tipos compostos uma vez:
Você precisa definir uma vez cada tipo de retorno que você vai usar:
CREATE TYPE tablefunc_crosstab_int_3 AS (
row_name text, val1 int, val2 int, val3 int);
CREATE TYPE tablefunc_crosstab_int_4 AS (
row_name text, val1 int, val2 int, val3 int, val4 int);
...
Para chamadas ad hoc, você também pode criar uma tabela temporária para o mesmo efeito (temporário):
CREATE TEMP TABLE temp_xtype7 AS (
row_name text, x1 int, x2 int, x3 int, x4 int, x5 int, x6 int, x7 int);
Ou use o tipo de tabela, visualização ou visualização materializada existente, se disponível.
Ligar
Usando os tipos de linha acima:
Formulário de 1 parâmetro (sem valores ausentes):
SELECT * FROM crosstab_n(
'SELECT row_name, attrib, val FROM tbl ORDER BY 1,2'
, NULL::tablefunc_crosstab_int_3);
Formulário de 2 parâmetros (alguns valores podem estar faltando):
SELECT * FROM crosstab_n(
'SELECT row_name, attrib, val FROM tbl ORDER BY 1'
, $$VALUES ('val1'), ('val2'), ('val3')$$
, NULL::tablefunc_crosstab_int_3);
Esta uma função funciona para todos os tipos de retorno, enquanto o
crosstabN()
framework fornecido pelo tablefunc
O módulo precisa de uma função separada para cada um.Se você nomeou seus tipos em sequência como demonstrado acima, você só precisa substituir o número em negrito. Para encontrar o número máximo de categorias na tabela base:
SELECT max(count(*)) OVER () FROM tbl -- returns 3
GROUP BY row_name
LIMIT 1;
Isso é o mais dinâmico possível se você quiser colunas individuais . Arrays como demonstrado por @Clocoaldo ou uma representação de texto simples ou o resultado envolvido em um tipo de documento como
json
ou hstore
pode trabalhar para qualquer número de categorias dinamicamente. Isenção de responsabilidade:
É sempre potencialmente perigoso quando a entrada do usuário é convertida em código. Certifique-se de que isso não pode ser usado para injeção de SQL. Não aceite entrada de usuários não confiáveis (diretamente).
Chamada para pergunta original:
SELECT * FROM crosstab_n('SELECT bar, 1, feh FROM tbl_org ORDER BY 1,2'
, NULL::tablefunc_crosstab_int_3);