PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Consulta de tabela cruzada do PostgreSQL


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 valor 7 é 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 para C .

  • 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 com crosstab(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 produzir text , date , numeric etc. para os respectivos atributos. Há um exemplo de código no final do capítulo crosstab(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?