В SQL есть некоторые вещи, которые мы просто принимаем как должное, не задумываясь о них должным образом.
Одной из этих вещей являются предложения GROUP BY и менее популярные предложения HAVING . Давайте посмотрим на простой пример. В этом примере мы повторим пример базы данных, которую мы видели в предыдущей статье о потрясающих функциях LEAD (), LAG (), FIRST_VALUE (), LAST_VALUE () :
|
1
2
3
4
5
6
|
CREATE TABLE countries ( code CHAR(2) NOT NULL, year INT NOT NULL, gdp_per_capita DECIMAL(10, 2) NOT NULL, govt_debt DECIMAL(10, 2) NOT NULL); |
До появления оконных функций агрегации производились только с помощью GROUP BY . Типичный вопрос, который мы могли бы задать нашей базе данных с использованием SQL:
Каковы топ-3 средних государственных долгов в процентах от ВВП для тех стран, чей ВВП на душу населения составлял более 40 000 долларов в год в течение последних четырех лет.
Уф. Некоторые (академические) бизнес-требования.
В SQL (диалект PostgreSQL) мы написали бы:
|
1
2
3
4
5
6
7
|
select code, avg(govt_debt)from countrieswhere year > 2010group by codehaving min(gdp_per_capita) >= 40000order by 2 desclimit 3 |
Или со встроенными комментариями
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
-- The average government debtselect code, avg(govt_debt)-- for those countriesfrom countries-- in the last four yearswhere year > 2010-- yepp, for the countriesgroup by code-- whose GDP p.c. was over 40'000 in every yearhaving min(gdp_per_capita) >= 40000-- The top 3order by 2 desclimit 3 |
Результат:
|
1
2
3
4
5
|
code avg------------JP 193.00US 91.95DE 56.00 |
Запомните 10 простых шагов для полного понимания SQL :
-
FROMгенерирует набор данных -
WHEREуменьшает сгенерированный набор данных -
GROUP BYагрегирует сокращенный набор данных -
HAVINGуменьшает агрегированный набор данных -
SELECTпреобразует сокращенный набор агрегированных данных -
ORDER BYсортирует преобразованный набор данных -
LIMIT .. OFFSETсоздает отсортированный набор данных
… Где LIMIT .. OFFSET может быть очень разных вкусов .
Пустое предложение GROUP BY
Весьма частным случаем GROUP BY является явное или неявное пустое предложение GROUP BY . Вот вопрос, который мы могли бы задать нашей базе данных:
Есть ли вообще страны с ВВП на душу населения более 50 000 долларов?
А в SQL мы написали бы:
|
1
2
3
|
select true answerfrom countrieshaving max(gdp_per_capita) >= 50000 |
В результате
|
1
2
3
|
answer------t |
Конечно, вы могли бы вместо этого использовать предложение EXISTS ( пожалуйста, не используйте COUNT(*) в этих случаях ):
|
1
2
3
4
5
|
select exists( select 1 from countries where gdp_per_capita >= 50000); |
И мы снова получим:
|
1
2
3
|
answer------t |
… Но давайте сосредоточимся на простом предложении HAVING .
Не все знают, что HAVING может использоваться сам по себе или что даже означает иметь HAVING сам по себе. Уже стандарт SQL 1992 позволял использовать HAVING без GROUP BY , но это было до введения GROUPING SETS в SQL: 1999 , когда семантика этого синтаксиса была задним числом однозначно определена:
7.10 <пункт>
1<having clause> ::= HAVING <search condition>Синтаксические правила
1) Пусть HC будет <имеющим предложение>. Пусть TE будет <табличным выражением>, которое сразу содержит
HC. Если TE не содержит сразу <group by предложение>, то GROUP BY () является неявным.
Это интересно. Существует неявная GROUP BY ( ) , если мы опускаем явное предложение GROUP BY . Если вы захотите немного углубиться в стандарт SQL, вы найдете:
010203040506070809101112131415161718<groupby clause> ::=GROUP BY <groupingspecification><groupingspecification> ::=<groupingcolumn reference>| <rolluplist>| <cubelist>| <groupingsets list>| <grandtotal>| <concatenatedgrouping><groupingset> ::=<ordinarygrouping set>| <rolluplist>| <cubelist>| <grandtotal><grandtotal> ::= <leftparen> <rightparen>
Итак, GROUP BY ( ) по сути группируется по «общему итогу», что и происходит интуитивно, если мы просто посмотрим на самый высокий в истории ВВП на душу населения:
|
1
2
|
select max(gdp_per_capita)from countries; |
Который дает:
|
1
2
3
|
max--------52409.00 |
Вышеуказанный запрос также неявно такой же, как этот (который не поддерживается PostgreSQL):
|
1
2
3
|
select max(gdp_per_capita)from countriesgroup by (); |
Потрясающие ГРУППОВЫЕ КОМПЛЕКТЫ
В этом разделе статьи мы покидаем землю PostgreSQL, входя в землю SQL Server, поскольку PostgreSQL постыдно не реализует ничего из следующего (пока).
Теперь мы не можем понять общий итог (пустое предложение GROUP BY ( ) ) без краткого рассмотрения стандартных GROUPING SETS SQL: 1999. Некоторые из вас, возможно, слышали о функциях группировки CUBE() или ROLLUP() , которые являются просто синтаксическим сахаром для часто используемых GROUPING SETS . Давайте попробуем ответить на этот вопрос в одном запросе:
Каковы самые высокие значения ВВП на душу населения в год ИЛИ на страну
В SQL мы напишем:
|
1
2
3
|
select code, year, max(gdp_per_capita)from countriesgroup by grouping sets ((code), (year)) |
Что дает два сцепленных набора записей:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
code year max------------------------NULL 2009 46999.00 <- grouped by yearNULL 2010 48358.00NULL 2011 51791.00NULL 2012 52409.00CA NULL 52409.00 <- grouped by codeDE NULL 44355.00FR NULL 42578.00GB NULL 38927.00IT NULL 36988.00JP NULL 46548.00RU NULL 14091.00US NULL 51755.00 |
Это мило, не правда ли? По сути, это то же самое, что этот запрос с UNION ALL
|
1
2
3
4
5
6
7
|
select code, null, max(gdp_per_capita)from countriesgroup by codeunion allselect null, year, max(gdp_per_capita)from countriesgroup by year; |
На самом деле, это абсолютно то же самое, так как последний явно объединяет два набора сгруппированных записей … то есть два набора группирования. Эта страница документации по SQL Server также очень хорошо объясняет это .
И самый мощный из них: CUBE ()
Теперь представьте, что вы хотели бы добавить «общий итог», а также наивысшее значение для страны и года, производя четыре различных составных набора. Чтобы ограничить результаты, мы также отфильтруем ВВП менее 48000 для этого примера:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
select code, year, max(gdp_per_capita), grouping_id(code, year) grpfrom countrieswhere gdp_per_capita >= 48000group by grouping sets ( (), (code), (year), (code, year))order by grp desc; |
Этот красивый запрос теперь произведет все возможные комбинации группировки, которые мы можем себе представить, включая итоговую сумму, чтобы произвести:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
code year max grp---------------------------------NULL NULL 52409.00 3 <- grand totalNULL 2012 52409.00 2 <- group by yearNULL 2010 48358.00 2NULL 2011 51791.00 2CA NULL 52409.00 1 <- group by codeUS NULL 51755.00 1US 2010 48358.00 0 <- group by code and yearCA 2012 52409.00 0US 2012 51755.00 0CA 2011 51791.00 0US 2011 49855.00 0 |
И поскольку это довольно распространенная операция в отчетах и в OLAP, мы можем просто написать то же самое, используя функцию CUBE() :
|
1
2
3
4
5
6
7
|
select code, year, max(gdp_per_capita), grouping_id(code, year) grpfrom countrieswhere gdp_per_capita >= 48000group by cube(code, year)order by grp desc; |
Совместимость
Хотя первая пара запросов также работала с PostgreSQL, те, которые используют GROUPING SETS будут работать только на 4 из 17 RDBMS, которые в настоящее время поддерживаются jOOQ . Эти:
- DB2
- оракул
- SQL Server
- Sybase SQL Anywhere
jOOQ также полностью поддерживает ранее упомянутые синтаксисы. Вариант GROUPING SETS можно записать так:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
// Countries is an object generated by the jOOQ// code generator for the COUNTRIES table.Countries c = COUNTRIES;ctx.select( c.CODE, c.YEAR, max(c.GDP_PER_CAPITA), groupingId(c.CODE, c.YEAR).as("grp")) .from(c) .where(c.GDP_PER_CAPITA.ge(new BigDecimal("48000"))) .groupBy(groupingSets(new Field[][] { {}, { c.CODE }, { c.YEAR }, { c.CODE, c.YEAR } })) .orderBy(fieldByName("grp").desc()) .fetch(); |
… Или версия CUBE() :
|
01
02
03
04
05
06
07
08
09
10
|
ctx.select( c.CODE, c.YEAR, max(c.GDP_PER_CAPITA), groupingId(c.CODE, c.YEAR).as("grp")) .from(c) .where(c.GDP_PER_CAPITA.ge(new BigDecimal("48000"))) .groupBy(cube(c.CODE, c.YEAR)) .orderBy(fieldByName("grp").desc()) .fetch(); |
… И в будущем мы будем эмулировать GROUPING SETS посредством их эквивалентных запросов UNION ALL в тех базах данных, которые изначально не поддерживают GROUPING SETS .
| Ссылка: | Вы действительно понимаете SQL-предложения GROUP BY и HAVING? от нашего партнера JCG Лукаса Эдера в блоге JAVA, SQL и JOOQ . |
