CASE
Ако вашият случай е толкова прост, колкото е показано, CASE
ще свърши работа:
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;
Няма значение дали използвате sum()
, max()
или min()
като агрегатна функция във външната заявка. Всички те водят до една и съща стойност в този случай.
crosstab()
С повече категории ще бъде по-лесно с crosstab()
заявка. Това също трябва да бъде по-бързо за по-големи маси .
Трябва да инсталирате допълнителния модул tablefunc (веднъж на база данни). След Postgres 9.1 това е толкова просто, колкото:
CREATE EXTENSION tablefunc;
Подробности в този свързан отговор:
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);
Няма sqlfiddle за този, защото сайтът не позволява допълнителни модули.
Бенчмарк
За да проверя твърденията си, направих бърз бенчмарк с близки до реалните данни в моята малка тестова база данни. PostgreSQL 9.1.6. Тествайте с EXPLAIN ANALYZE
, най-доброто от 10:
Тестова настройка с 10020 реда:
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;
Резултати:
@bluefeet
Общо време на изпълнение:95,401 ms
@wildplasser
(различни резултати, включва редове с count <= 3
)
Общо време на работа:64,497 ms
@Andreiy
(+ ORDER BY
)
&@Erwin1 - CASE
(и двете работят приблизително еднакво)
Общо време на работа:39,105 ms
@Erwin2 - crosstab()
Общо време на работа:17,644 ms
До голяма степен пропорционални (но неуместни) резултати само с 20 реда. Само CTE на @wildplasser има повече режийни разходи и леко скокове.
С повече от шепа редове, crosstab()
бързо поема водеща роля. Заявката на @Andreiy изпълнява приблизително същото като моята опростена версия, агрегатна функция във външния SELECT
(min()
, max()
, sum()
) не прави измерима разлика (само два реда на група).
Всичко според очакванията, без изненади, вземете моята настройка и опитайте @home.