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
Sales3
IT2
HR1

Важное правило SELECT при GROUP BY

Если в запросе есть GROUP BY, то в SELECT можно выводить:

Иначе СУБД не понимает, какое значение вывести для столбца внутри группы. Например, в группе "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
SalesAlice1
SalesBob1
SalesCarol1
ITDave1
ITEve1
HRMallory1

Группировка по нескольким столбцам

Группировать можно сразу по нескольким полям. Тогда группы формируются по уникальным комбинациям значений. Это полезно, когда нужно “разрезать” данные сразу по двум (или более) признакам.

Например, посчитаем число заказов по отделу и статусу заказа:

SELECT department, status, COUNT(*) AS orders_count
FROM orders
GROUP BY department, status
ORDER BY department, status;
Пример результата:
department status orders_count
ITdone4
ITnew2
Salesdone7
Salesnew3

WHERE и HAVING: в чём разница

Часто путают WHERE и HAVING. Они фильтруют данные на разных этапах:

💡 Практический совет

Очень частый паттерн: WHERE — “какие строки участвуют в расчёте”, HAVING — “какие группы оставить”.

Фильтрация строк до группировки (WHERE)

Посчитаем количество сотрудников в отделах, но только тех, у кого зарплата ≥ 1000.

SELECT department, COUNT(*) AS employees_count
FROM employees
WHERE salary >= 1000
GROUP BY department;
Пример результата:
department employees_count
Sales2
IT2

Фильтрация групп после группировки (HAVING)

Теперь оставим только те отделы, где сотрудников 2 и больше.

SELECT department, COUNT(*) AS employees_count
FROM employees
GROUP BY department
HAVING COUNT(*) >= 2;
Пример результата:
department employees_count
Sales3
IT2
Запомни

Если условие использует агрегатную функцию (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
Sales330011009001400
IT2600130012001400
HR900900900900

COUNT(*) vs COUNT(column)

Это важно, когда в данных встречаются NULL (например, не у всех заполнен телефон или бонус).


Порядок выполнения (упрощённо)

Полезно понимать, в каком порядке “мысленно” обрабатывается запрос:

FROM
WHERE
GROUP BY
HAVING
SELECT
ORDER BY
LIMIT

Из этого порядка легко понять:


Итого