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.