Группировка
Предложение GROUP BY
соединяет записи, имеющие одинаковую комбинацию значений
полей, указанных в его списке, в одну запись. Агрегатные функции в списке выбора
применяются к каждой группе индивидуально, а не для всего набора в целом.
Если список выборки содержит только агрегатные столбцы или столбцы, значения которых не
зависит от отдельных строк основного множества, то предложение GROUP BY
необязательно.
Когда предложение GROUP BY
пропущено, результирующее множество будет состоять из одной
строки (при условии, что хотя бы один агрегатный столбец присутствует).
Если в списке выборки содержатся как агрегатные столбцы, так и столбцы, чьи значения
зависит от выбираемых строк, то предложение GROUP BY
становится обязательным.
Синтаксис:
SELECT ... FROM ...
GROUP BY <grouping-item> [, <grouping-item> ...]
[HAVING <grouped-row-condition>] ...
<grouping-item> ::= <non-aggr-select-item> | <non-aggr-expression>
<non-aggr-select-item> ::= column-copy | column-alias | column-position
, где
non-aggr-expression - Любое не агрегатное выражение, которое не включено в список выборки, т.е. невыбираемые столбцы из набора источника или выражения, которые не зависит от набора данных вообще.
column-copy - Дословная копия выражения из списка выбора, не содержащего агрегатной функции.
column-alias - Псевдоним выражения (столбца) из списка выбора, не содержащего агрегатной функции.
column-position - Номер позиции выражения (столбца) из списка выбора, не содержащего агрегатной функции.
Общее правило гласит, что каждый не агрегированный столбец в SELECT
списке, должен быть
так же включён в GROUP BY
список. Вы можете это сделать тремя способами:
Копировать выражение дословно из списка выбора;
Указать псевдоним, если он существует;
Задать положение столбца в виде целого числа, которое находится в диапазоне от 1 до количества столбцов в списке
SELECT
.
В дополнении к требуемым элементам, список группировки так же может содержать:
Столбцы исходной таблицы, которые не включены в список выборки
SELECT
, или неагрегатные выражения, основанные на таких столбцах. Добавление таких столбцов может дополнительно разбить группы. Но так как эти столбцы не в списке выборкиSELECT
, вы не можете сказать, какому значению столбца соответствует значение агрегированной строки. Таким образом, если вы заинтересованы в этой информации, вы так же должны включить этот столбец или выражение в список выборкиSELECT
, что возвращает вас к правилу «каждый не агрегированный столбце в списке выборкиSELECT
должен быть включён в список группировкиGROUP BY
».Выражения, которые не зависят от данных из основного набора, т.е. константы, контекстные переменные, некоррелированные подзапросы, возвращающие единственное значение и т.д. Это упоминается только для полноты картины, т.к. добавление этих элементов является абсолютно бессмысленным, поскольку они не повлияют на группировку вообще. «Безвредные, но бесполезные» элементы так же могут фигурировать в списке выбора
SELECT
без их копирования в список группировкиGROUP BY
.
Рассмотрим несколько примеров использования группировки.
Для этого допустим у нас есть таблица сотрудников (WORKERS
).
NAME |
PROJECT_ID |
AGE |
GENDER |
SHIFT |
---|---|---|---|---|
Иванов |
1 |
20 |
М |
1 |
Петров |
1 |
22 |
М |
2 |
Виноградова |
1 |
21 |
Ж |
1 |
Сидоров |
2 |
18 |
М |
2 |
Кузнецова |
2 |
19 |
Ж |
2 |
Дмитриева |
3 |
21 |
Ж |
1 |
Ершова |
3 |
21 |
Ж |
2 |
Когда в списке выбора SELECT
содержатся только агрегатные столбцы, предложение GROUP
BY
необязательно:
SELECT COUNT(*), AVG(age) FROM workers WHERE gender = 'M'
Этот запрос вернёт одну строку с указанием количества сотрудников мужского пола и их средний возраст.
COUNT |
AVG |
---|---|
3 |
20 |
Добавление выражения, которое не зависит от строк таблицы WORKERS
, ничего не
меняет:
SELECT COUNT(*), AVG(age), current_date FROM workers WHERE gender = 'M'
COUNT |
AVG |
CORRENT_DATE |
---|---|---|
3 |
20 |
2022-05-29 |
Теперь строка результата будет иметь дополнительный столбец, отображающий текущую дату, но кроме этого, ничего фундаментального не изменилось. Группировка по-прежнему не требуется.
Тем не менее, в обоих приведённых выше примерах это разрешено. Например, Можно добавить группировку по группам:
SELECT COUNT(*), AVG(age) FROM workers WHERE gender = 'M' GROUP BY project_id
Этот запрос вернёт результат для каждого проекта, в котором есть сотрудники мужского пола, перечисляя количество сотрудников и их средний возраст в этой конкретном проекте.
COUNT |
AVG |
---|---|
2 |
21 |
1 |
18 |
Обратите внимание, что значения агрегатных функций тоже изменились, т.к. они вычисляются уже по другим наборам данных.
Такой запрос имеет не говорит вам, какая запись к какому проекту относится.
Для того чтобы получить эту дополнительную часть информации, не агрегатный столбец
PROJECT_ID
должен быть добавлен в список выборки SELECT
:
SELECT project_id, COUNT(*), AVG(age) FROM workers WHERE gender = 'M' GROUP BY project_id
Теперь у нас есть полезный запрос. Обратите внимание, что добавление столбца PROJECT_ID
делает предложение GROUP BY
обязательным. Результат будет следующим:
PROJECT_ID |
COUNT |
AVG |
---|---|---|
1 |
2 |
21 |
2 |
1 |
18 |
Допустим, что кроме этого мы хотим знать еще смену, в которую работает сотрудник. Просто добавить смену в список выбора нельзя. Требуется обязательно добавить это поле в список группировки.
SELECT project_id, shift, COUNT(*), AVG(age) FROM workers
GROUP BY project_id, shift
Такой запрос изменит состав групп и вернет следующий результат:
PROJECT_ID |
SHIFT |
COUNT |
AVG |
---|---|---|---|
1 |
Да |
2 |
20,5 |
1 |
Нет |
1 |
22 |
2 |
Нет |
2 |
18,5 |
3 |
Да |
1 |
21 |
3 |
Нет |
1 |
21 |
Использование HAVING
Так же, как и предложение WHERE
ограничивает строки в наборе данных, теми которые
удовлетворяют условию поиска, с той разницей, что предложение HAVING
накладывает
ограничения на агрегированные строки сгруппированного набора. Предложение HAVING
не
является обязательным и может быть использовано только в сочетании с предложением
GROUP BY
.
Условие в предложении HAVING
может ссылаться на:
Любой агрегированный столбец в списке выбора
SELECT
. Это наиболее широко используемый случай;Любое агрегированное выражение, которое не находится в списке выбора
SELECT
, но разрешено в контексте запроса. Иногда это полезно;Любой столбец в списке
GROUP BY
. Однако более эффективно фильтровать не агрегированные данные на более ранней стадии в предложенииWHERE
;Любое выражение, значение которого не зависит от содержимого набора данных (например, константа или контекстная переменная). Это допустимо, но совершенно бессмысленно, потому что такое условие, не имеющее никакого отношения к самому набору данных, либо подавит весь набор, либо оставит его не тронутым.
Предложение HAVING
не может содержать:
Не агрегированные выражения столбца, которые не находятся в списке
GROUP BY
;Позицию столбца. Целое число в предложении
HAVING
– просто целое число;Псевдонимы столбца – даже, если они появляются в предложении
GROUP BY
.
Например, мы можем использовать предложение HAVING
для исключения проектов с малым составом:
SELECT project_id, COUNT(*), AVG(age) FROM workers
GROUP BY project_id HAVING COUNT(*) < 3
Либо выбрать проекты где разброс возраста участников меньше 1.2 года.
SELECT project_id, COUNT(*), AVG(age) FROM workers
GROUP BY project_id HAVING MAX(age) - MIN(age) < 1.2