CASE
Hvis din sag er så enkel som vist, en CASE
erklæring vil gøre:
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;
Det er ligegyldigt, om du bruger sum()
, max()
eller min()
som aggregeret funktion i den ydre forespørgsel. De resulterer alle i den samme værdi i dette tilfælde.
crosstab()
Med flere kategorier vil det være nemmere med en crosstab()
forespørgsel. Dette burde også være hurtigere for større borde .
Du skal installere det ekstra modul tablefunc (én gang pr. database). Siden Postgres 9.1 er det så simpelt som:
CREATE EXTENSION tablefunc;
Detaljer i dette relaterede svar:
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);
Ingen sqlfiddle for denne, fordi webstedet ikke tillader yderligere moduler.
Benchmark
For at verificere mine påstande kørte jeg en hurtig benchmark med tæt på reelle data i min lille testdatabase. PostgreSQL 9.1.6. Test med EXPLAIN ANALYZE
, bedst af 10:
Testopsætning med 10020 rækker:
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;
Resultater:
@bluefeet
Samlet køretid:95.401 ms
@wildplasser
(forskellige resultater, inkluderer rækker med count <= 3
)
Samlet køretid:64.497 ms
@Andreiy
(+ ORDER BY
)
&@Erwin1 - CASE
(begge yder omtrent det samme)
Samlet kørselstid:39.105 ms
@Erwin2 - crosstab()
Samlet køretid:17.644 ms
Stort set proportionale (men irrelevante) resultater med kun 20 rækker. Kun @wildplassers CTE har mere overhead og spikes lidt.
Med mere end en håndfuld rækker, crosstab()
tager hurtigt føringen.@Andreiys forespørgsel udfører omtrent det samme som min forenklede version, aggregatfunktion i ydre SELECT
(min()
, max()
, sum()
) gør ingen målbar forskel (kun to rækker pr. gruppe).
Alt som forventet, ingen overraskelser, tag mit setup og prøv det @home.