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

Transpor linhas e colunas (também conhecido como pivô) apenas com um COUNT() mínimo?

CASE


Se o seu caso for tão simples quanto demonstrado, um CASE declaração fará:
SELECT year
     , sum(CASE WHEN animal = 'kittens' THEN price END) AS kittens
     , sum(CASE WHEN animal = 'puppies' THEN price END) AS puppies
FROM  (
   SELECT year, animal, avg(price) AS price
   FROM   tab_test
   GROUP  BY year, animal
   HAVING count(*) > 2
   ) t
GROUP  BY year
ORDER  BY year;

Não importa se você usa sum() , max() ou min() como função agregada na consulta externa. Todos eles resultam no mesmo valor neste caso.

SQL Fiddle

crosstab()


Com mais categorias, será mais simples com um crosstab() consulta. Isso também deve ser mais rápido para tabelas maiores .

Você precisa instalar o módulo adicional tablefunc (uma vez por banco de dados). Desde o Postgres 9.1, isso é tão simples quanto:
CREATE EXTENSION tablefunc;

Detalhes nesta resposta relacionada:
SELECT * FROM crosstab(
      'SELECT year, animal, avg(price) AS price
       FROM   tab_test
       GROUP  BY animal, year
       HAVING count(*) > 2
       ORDER  BY 1,2'

      ,$$VALUES ('kittens'::text), ('puppies')$$)
AS ct ("year" text, "kittens" numeric, "puppies" numeric);

Nenhum sqlfiddle para este porque o site não permite módulos adicionais.

Referência


Para verificar minhas afirmações, executei um benchmark rápido com dados quase reais em meu pequeno banco de dados de teste. PostgreSQL 9.1.6. Teste com EXPLAIN ANALYZE , melhor de 10:

Configuração de teste com 10.020 linhas:
CREATE TABLE tab_test (year int, animal text, price numeric);

-- years with lots of rows
INSERT INTO tab_test
SELECT 2000 + ((g + random() * 300))::int/1000 
     , CASE WHEN (g + (random() * 1.5)::int) %2 = 0 THEN 'kittens' ELSE 'puppies' END
     , (random() * 200)::numeric
FROM   generate_series(1,10000) g;

-- .. and some years with only few rows to include cases with count < 3
INSERT INTO tab_test
SELECT 2010 + ((g + random() * 10))::int/2
     , CASE WHEN (g + (random() * 1.5)::int) %2 = 0 THEN 'kittens' ELSE 'puppies' END
     , (random() * 200)::numeric
FROM   generate_series(1,20) g;

Resultados:

@bluefeet
Tempo de execução total:95,401 ms

@wildplasser (resultados diferentes, inclui linhas com count <= 3 )
Tempo de execução total:64,497 ms

@Andreiy (+ ORDER BY )
&@Erwin1 - CASE (ambos executam o mesmo)
Tempo de execução total:39,105 ms

@Erwin2 - crosstab()
Tempo de execução total:17,644 ms

Resultados amplamente proporcionais (mas irrelevantes) com apenas 20 linhas. Apenas o CTE do @wildplasser tem mais sobrecarga e picos um pouco.

Com mais de um punhado de linhas, crosstab() rapidamente assume a liderança. A consulta de @Andreiy executa aproximadamente o mesmo que minha versão simplificada, função agregada no SELECT externo (min() , max() , sum() ) não faz diferença mensurável (apenas duas linhas por grupo).

Tudo como esperado, sem surpresas, pegue minha configuração e experimente @home.