CASE
Si su caso es tan simple como se muestra, un CASE
declaración hará:
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;
No importa si usa sum()
, max()
o min()
como función agregada en la consulta externa. Todos resultan en el mismo valor en este caso.
crosstab()
Con más categorías será más sencillo con una crosstab()
consulta. Esto también debería ser más rápido para tablas más grandes .
Debe instalar el módulo adicional tablefunc (una vez por base de datos). Desde Postgres 9.1 eso es tan simple como:
CREATE EXTENSION tablefunc;
Detalles en esta respuesta 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);
No hay sqlfiddle para este porque el sitio no permite módulos adicionales.
Valor de referencia
Para verificar mis afirmaciones, ejecuté un punto de referencia rápido con datos casi reales en mi pequeña base de datos de prueba. PostgreSQL 9.1.6. Prueba con EXPLAIN ANALYZE
, al mejor de 10:
Configuración de prueba con 10020 filas:
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
Tiempo de ejecución total:95,401 ms
@wildplasser
(diferentes resultados, incluye filas con count <= 3
)
Tiempo de ejecución total:64.497 ms
@Andreiy
(+ ORDER BY
)
&@Erwin1 - CASE
(ambos funcionan casi igual)
Tiempo de ejecución total:39,105 ms
@Erwin2 - crosstab()
Tiempo de ejecución total:17,644 ms
Resultados en gran parte proporcionales (pero irrelevantes) con solo 20 filas. Solo el CTE de @wildplasser tiene más gastos generales y aumenta un poco.
Con más de un puñado de filas, crosstab()
rápidamente toma la delantera. La consulta de @Andreiy funciona casi igual que mi versión simplificada, función agregada en SELECT
externo (min()
, max()
, sum()
) no hace ninguna diferencia medible (solo dos filas por grupo).
Todo como se esperaba, sin sorpresas, tome mi configuración y pruébela @home.