CASE
Jeśli Twoja sprawa jest tak prosta, jak pokazano, CASE
oświadczenie zrobi:
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;
Nie ma znaczenia, czy używasz sum()
, max()
lub min()
jako funkcja agregująca w zapytaniu zewnętrznym. Wszystkie dają w tym przypadku tę samą wartość.
crosstab()
Przy większej liczbie kategorii łatwiej będzie dzięki crosstab()
zapytanie. Powinno to być również szybsze przy większych stołach .
Musisz zainstalować dodatkowy moduł tablefunc (raz na bazę danych). Od Postgres 9.1 jest to tak proste, jak:
CREATE EXTENSION tablefunc;
Szczegóły w tej powiązanej odpowiedzi:
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);
Brak sqlfiddle dla tego, ponieważ strona nie zezwala na dodatkowe moduły.
Wzorzec
Aby zweryfikować moje twierdzenia, przeprowadziłem szybki test porównawczy z danymi zbliżonymi do rzeczywistych w mojej małej testowej bazie danych. PostgreSQL 9.1.6. Przetestuj za pomocą EXPLAIN ANALYZE
, najlepsze z 10:
Konfiguracja testowa z 10020 wierszami:
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;
Wyniki:
@bluefeet
Całkowity czas działania:95,401 ms
@wildplasser
(różne wyniki, zawiera wiersze z count <= 3
)
Całkowity czas działania:64,497 ms
@Andreiy
(+ ORDER BY
)
&@Erwin1 - CASE
(oba działają mniej więcej tak samo)
Całkowity czas działania:39,105 ms
@Erwin2 - crosstab()
Całkowity czas działania:17,644 ms
Wyniki w dużej mierze proporcjonalne (ale nieistotne) z zaledwie 20 wierszami. Tylko CTE @wildplassera ma więcej kosztów ogólnych i trochę wzrasta.
Mając więcej niż kilka wierszy, crosstab()
szybko przejmuje prowadzenie.@ Zapytanie Andreiy działa mniej więcej tak samo jak moja uproszczona wersja, funkcja agregująca w zewnętrznym SELECT
(min()
, max()
, sum()
) nie ma wymiernej różnicy (tylko dwa wiersze na grupę).
Wszystko zgodnie z oczekiwaniami, bez niespodzianek, weź moją konfigurację i wypróbuj ją w @home.