Instale o módulo adicional
tablefunc
uma vez por banco de dados, que fornece a função crosstab()
. Desde o Postgres 9.1 você pode usar CREATE EXTENSION
por isso:CREATE EXTENSION IF NOT EXISTS tablefunc;
Caso de teste aprimorado
CREATE TABLE tbl (
section text
, status text
, ct integer -- "count" is a reserved word in standard SQL
);
INSERT INTO tbl VALUES
('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
, ('C', 'Inactive', 7); -- ('C', 'Active') is missing
Formulário simples - não adequado para atributos ausentes
crosstab(text)
com 1 parâmetro de entrada:SELECT *
FROM crosstab(
'SELECT section, status, ct
FROM tbl
ORDER BY 1,2' -- needs to be "ORDER BY 1,2" here
) AS ct ("Section" text, "Active" int, "Inactive" int);
Devoluções:
Section | Active | Inactive ---------+--------+---------- A | 1 | 2 B | 4 | 5 C | 7 | -- !!
- Não há necessidade de transmissão e renomeação.
- Observe o incorreto resultado para
C
:o valor7
é preenchido para a primeira coluna. Às vezes, esse comportamento é desejável, mas não para este caso de uso. - O formulário simples também está limitado a exatamente três colunas na consulta de entrada fornecida:row_name , categoria , valor . Não há espaço para colunas extras como na alternativa de 2 parâmetros abaixo.
Formulário seguro
crosstab(text, text)
com 2 parâmetros de entrada:SELECT *
FROM crosstab(
'SELECT section, status, ct
FROM tbl
ORDER BY 1,2' -- could also just be "ORDER BY 1" here
, $$VALUES ('Active'::text), ('Inactive')$$
) AS ct ("Section" text, "Active" int, "Inactive" int);
Devoluções:
Section | Active | Inactive ---------+--------+---------- A | 1 | 2 B | 4 | 5 C | | 7 -- !!
-
Observe o resultado correto paraC
.
-
O segundo parâmetro pode ser qualquer consulta que retorne uma linha por atributo que corresponde à ordem da definição da coluna no final. Muitas vezes, você desejará consultar atributos distintos da tabela subjacente como esta:
'SELECT DISTINCT attribute FROM tbl ORDER BY 1'
Isso está no manual.
Uma vez que você tem que soletrar todas as colunas em uma lista de definição de coluna de qualquer maneira (exceto para
crosstabN()
variantes), normalmente é mais eficiente fornecer uma lista curta em um VALUES
expressão como demonstrado: $$VALUES ('Active'::text), ('Inactive')$$)
Ou (não no manual):
$$SELECT unnest('{Active,Inactive}'::text[])$$ -- short syntax for long lists
-
Usei cotações em dólares para facilitar a cotação.
-
Você pode até produzir colunas com diferentes tipos de dados comcrosstab(text, text)
- desde que a representação de texto da coluna de valor seja uma entrada válida para o tipo de destino. Dessa forma, você pode ter atributos de diferentes tipos e produzirtext
,date
,numeric
etc. para os respectivos atributos. Há um exemplo de código no final do capítulocrosstab(text, text)
no manual.
db<>mexa aqui
Efeito do excesso de linhas de entrada
As linhas de entrada em excesso são tratadas de forma diferente - linhas duplicadas para a mesma combinação ("row_name", "category") -
(section, status)
no exemplo acima. O 1 parâmetro formulário preenche as colunas de valor disponíveis da esquerda para a direita. Os valores em excesso são descartados.
As linhas de entrada anteriores vencem.
Os 2 parâmetros form atribui cada valor de entrada à sua coluna dedicada, substituindo qualquer atribuição anterior.
As linhas de entrada posteriores vencem.
Normalmente, você não tem duplicatas para começar. Mas se você fizer isso, ajuste cuidadosamente a ordem de classificação de acordo com seus requisitos - e documente o que está acontecendo.
Ou obtenha resultados rápidos e arbitrários se você não se importar. Basta estar ciente do efeito.
Exemplos avançados
-
Pivô em várias colunas usando Tablefunc - também demonstrando as "colunas extras" mencionadas
-
Alternativa dinâmica para pivotar com CASE e GROUP BY
\crosstabview
em psql
Postgres 9.6 adicionou este meta-comando ao seu terminal interativo padrão psql. Você pode executar a consulta que você usaria como primeiro
crosstab()
parâmetro e alimente-o para \crosstabview
(imediatamente ou na próxima etapa). Como:db=> SELECT section, status, ct FROM tbl \crosstabview
Resultado semelhante ao acima, mas é um recurso de representação no lado do cliente exclusivamente. As linhas de entrada são tratadas de forma ligeiramente diferente, portanto,
ORDER BY
não é necessário. Detalhes para \crosstabview
no manual. Há mais exemplos de código na parte inferior dessa página. Resposta relacionada no dba.SE por Daniel Vérité (o autor do recurso psql):
- Como gerar um CROSS JOIN dinâmico onde a definição da tabela resultante é desconhecida?