CASE
Als uw zaak zo eenvoudig is als aangetoond, een CASE
verklaring zal doen:
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;
Het maakt niet uit of je sum()
. gebruikt , max()
of min()
als aggregatiefunctie in de buitenste query. In dit geval resulteren ze allemaal in dezelfde waarde.
crosstab()
Met meer categorieën zal het eenvoudiger zijn met een crosstab()
vraag. Dit zou ook sneller moeten zijn voor grotere tafels .
U moet de extra module tablefunc installeren (eenmaal per databank). Sinds Postgres 9.1 is dat zo simpel als:
CREATE EXTENSION tablefunc;
Details in dit gerelateerde antwoord:
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);
Geen sqlfiddle voor deze omdat de site geen extra modules toestaat.
Benchmark
Om mijn beweringen te verifiëren, heb ik een snelle benchmark uitgevoerd met bijna echte gegevens in mijn kleine testdatabase. PostgreSQL 9.1.6. Test met EXPLAIN ANALYZE
, beste van 10:
Testopstelling met 10020 rijen:
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;
Resultaten:
@bluefeet
Totale looptijd:95,401 ms
@wildplasser
(verschillende resultaten, inclusief rijen met count <= 3
)
Totale looptijd:64,497 ms
@Andreiy
(+ ORDER BY
)
&@Erwin1 - CASE
(beiden presteren ongeveer hetzelfde)
Totale looptijd:39,105 ms
@Erwin2 - crosstab()
Totale looptijd:17,644 ms
Grotendeels proportionele (maar irrelevante) resultaten met slechts 20 rijen. Alleen de CTE van @wildplasser heeft meer overhead en piekt een beetje.
Met meer dan een handvol rijen, crosstab()
neemt snel de leiding.@Andreiy's query presteert ongeveer hetzelfde als mijn vereenvoudigde versie, de aggregatiefunctie in buitenste SELECT
(min()
, max()
, sum()
) maakt geen meetbaar verschil (slechts twee rijen per groep).
Alles zoals verwacht, geen verrassingen, neem mijn setup en probeer het @home.