GROUP BY
GROUP BY используется для группировки строк по одному или нескольким столбцам.
Обычно применяется вместе с агрегатными функциями (COUNT, SUM, AVG,
MIN, MAX), чтобы получить сводную статистику по группам.
Сначала данные делятся на группы по значению(ям) столбцов из GROUP BY, затем для каждой группы считаются агрегаты.
Базовый пример
Посчитаем количество сотрудников в каждом отделе.
SELECT department, COUNT(*) AS employees_count
FROM employees
GROUP BY department;
| department | employees_count |
|---|---|
| Sales | 3 |
| IT | 2 |
| HR | 1 |
Важное правило SELECT при GROUP BY
Если в запросе есть GROUP BY, то в SELECT можно выводить:
- столбцы, которые перечислены в
GROUP BY, - и/или выражения с агрегатными функциями (
COUNT,SUM, ...).
Иначе СУБД не понимает, какое значение вывести для столбца внутри группы.
Например, в группе "Sales" несколько разных сотрудников — какой name показывать?
-
Выводить в
SELECTстолбец, которого нет вGROUP BYи который не агрегирован (например,name,salaryи т.п.). -
Пытаться фильтровать агрегаты через
WHERE(нужноHAVING). -
Путать
COUNT(*)иCOUNT(column)при наличииNULL.
❌ Пример ошибки
SELECT department, name, COUNT(*) -- name не в GROUP BY и не агрегирован
FROM employees
GROUP BY department;
Такой запрос логически некорректен: для каждого department есть несколько разных name.
В большинстве СУБД это будет ошибка.
SQLite иногда “пропускает” такое, но результат будет неопределённым (фактически случайным) — так писать не надо.
✅ Правильно
Либо добавляем столбец в GROUP BY, либо агрегируем (в зависимости от того, что ты хочешь получить).
SELECT department, name, COUNT(*) AS cnt
FROM employees
GROUP BY department, name;
| department | name | cnt |
|---|---|---|
| Sales | Alice | 1 |
| Sales | Bob | 1 |
| Sales | Carol | 1 |
| IT | Dave | 1 |
| IT | Eve | 1 |
| HR | Mallory | 1 |
Группировка по нескольким столбцам
Группировать можно сразу по нескольким полям. Тогда группы формируются по уникальным комбинациям значений. Это полезно, когда нужно “разрезать” данные сразу по двум (или более) признакам.
Например, посчитаем число заказов по отделу и статусу заказа:
SELECT department, status, COUNT(*) AS orders_count
FROM orders
GROUP BY department, status
ORDER BY department, status;
| department | status | orders_count |
|---|---|---|
| IT | done | 4 |
| IT | new | 2 |
| Sales | done | 7 |
| Sales | new | 3 |
WHERE и HAVING: в чём разница
Часто путают WHERE и HAVING. Они фильтруют данные на разных этапах:
-
WHEREфильтрует строки до группировки (доGROUP BY). Сначала отбрасываются лишние строки, потом оставшиеся группируются. -
HAVINGфильтрует группы после группировки. Его используют, когда условие зависит от агрегатных значений (COUNT,SUM, ...).
Очень частый паттерн: WHERE — “какие строки участвуют в расчёте”, HAVING — “какие группы оставить”.
Фильтрация строк до группировки (WHERE)
Посчитаем количество сотрудников в отделах, но только тех, у кого зарплата ≥ 1000.
SELECT department, COUNT(*) AS employees_count
FROM employees
WHERE salary >= 1000
GROUP BY department;
| department | employees_count |
|---|---|
| Sales | 2 |
| IT | 2 |
Фильтрация групп после группировки (HAVING)
Теперь оставим только те отделы, где сотрудников 2 и больше.
SELECT department, COUNT(*) AS employees_count
FROM employees
GROUP BY department
HAVING COUNT(*) >= 2;
| department | employees_count |
|---|---|
| Sales | 3 |
| IT | 2 |
Если условие использует агрегатную функцию (COUNT, SUM, ...),
то это почти всегда HAVING, а не WHERE.
Частые приёмы с агрегатами
SUM / AVG / MIN / MAX
Посчитаем общую и среднюю зарплату по отделам, а также минимальную и максимальную.
SELECT
department,
SUM(salary) AS total_salary,
AVG(salary) AS avg_salary,
MIN(salary) AS min_salary,
MAX(salary) AS max_salary
FROM employees
GROUP BY department;
| department | total_salary | avg_salary | min_salary | max_salary |
|---|---|---|---|---|
| Sales | 3300 | 1100 | 900 | 1400 |
| IT | 2600 | 1300 | 1200 | 1400 |
| HR | 900 | 900 | 900 | 900 |
COUNT(*) vs COUNT(column)
COUNT(*)считает все строки в группе.COUNT(column)считает только строки, гдеcolumnнеNULL.
Это важно, когда в данных встречаются NULL (например, не у всех заполнен телефон или бонус).
Порядок выполнения (упрощённо)
Полезно понимать, в каком порядке “мысленно” обрабатывается запрос:
FROM
WHERE
GROUP BY
HAVING
SELECT
ORDER BY
LIMIT
Из этого порядка легко понять:
- почему
WHEREне видит агрегаты (они появляются только послеGROUP BY), - почему
HAVINGотлично подходит для условий поCOUNT/SUM/AVG.
Итого
GROUP BYгруппирует строки по одному или нескольким столбцам.- В
SELECTпри группировке — либо агрегаты, либо столбцы изGROUP BY. WHEREфильтрует строки до группировки,HAVING— группы после.COUNT(*)считает строки,COUNT(col)— только не-NULLзначения.