CodeRang
Темы SQL

GROUP BY SQL: группировка, агрегаты и HAVING

GROUP BY SQL используется, когда строки нужно объединить в группы и посчитать для каждой из них агрегаты: количество, сумму, среднее, минимум или максимум. На примере разберём группировку магазинов, условные агрегаты и отличие HAVING от WHERE.

Обновлено · Команда SQL Arena

Что потренируете

  • COUNT, SUM, AVG, MIN и MAX
  • GROUP BY по ключу и атрибуту
  • WHERE до агрегации и HAVING после неё
  • COUNT(*) против COUNT(col) и COUNT(DISTINCT col)
  • условная агрегация через FILTER и CASE

Пример задачи в тренажёре

Так выглядит задача в SQL Arena: условие, редактор, результат и схема базы.

Пред.След.Случайная
ЗаданиеОтправкиAI Mentor
Найти магазины минимум с двумя оплаченными заказами

Есть магазины stores и их заказы orders. Для каждого магазина посчитайте только оплаченные заказы. В результат должны попасть магазины, у которых не меньше двух оплаченных заказов. Выведите название как store_name, количество как paid_orders и сумму оплат как paid_revenue.

Запрос 1PostgreSQLv16
SELECT s.store_name,
       COUNT(*) FILTER (WHERE o.status = 'paid') AS paid_orders,
       SUM(o.amount) FILTER (WHERE o.status = 'paid') AS paid_revenue
FROM stores AS s
JOIN orders AS o ON o.store_id = s.id
GROUP BY s.id, s.store_name
HAVING COUNT(*) FILTER (WHERE o.status = 'paid') >= 2
ORDER BY paid_revenue DESC, s.store_name;
Отправить
Результатstoresorders
Ответ принят

Как работает GROUP BY SQL вместе с агрегатами

После JOIN каждая строка результата соответствует одному заказу вместе с данными его магазина. Затем GROUP BY s.id, s.store_name собирает заказы обратно в группы по магазинам. Важно группировать по s.id, а не только по названию: два разных магазина теоретически могут называться одинаково. Название добавлено в GROUP BY, потому что оно выводится в SELECT и само не является агрегатом.

Оба агрегата используют FILTER (WHERE o.status = 'paid'). Поэтому отменённый заказ остаётся в исходной группе, но не участвует ни в paid_orders, ни в paid_revenue. COUNT(*) FILTER (...) считает оплаченные строки, а SUM(o.amount) FILTER (...) складывает их суммы. В PostgreSQL FILTER часто читается яснее, чем несколько конструкций SUM(CASE WHEN ... THEN ... END), особенно когда в одном отчёте нужно получить несколько условных метрик.

Последний шаг — HAVING COUNT(*) FILTER (...) >= 2. HAVING применяется уже к сформированным группам, поэтому здесь можно отфильтровать магазины по результату агрегата. North Books остаётся с двумя оплатами на 200, Paper Moon — с тремя на 350, Blue Shelf — с двумя на 270. Ink House имеет только одну оплату, а Page Corner — ни одной, поэтому они не проходят HAVING. ORDER BY paid_revenue DESC, s.store_name сначала ставит выше магазины с большей выручкой, а второй ключ задаёт стабильный порядок при равенстве.

SQLPostgreSQL v16
SELECT s.store_name,
       COUNT(*) FILTER (WHERE o.status = 'paid') AS paid_orders,
       SUM(o.amount) FILTER (WHERE o.status = 'paid') AS paid_revenue
FROM stores AS s
JOIN orders AS o ON o.store_id = s.id
GROUP BY s.id, s.store_name
HAVING COUNT(*) FILTER (WHERE o.status = 'paid') >= 2
ORDER BY paid_revenue DESC, s.store_name;
Результат запроса
store_namepaid_orderspaid_revenue
Paper Moon3350
Blue Shelf2270
North Books2200

Ошибки в GROUP BY и HAVING SQL

01

Агрегат в WHERE вместо HAVING

Условие COUNT(*) >= 2 нельзя проверять обычным WHERE, потому что WHERE работает до формирования групп. Ограничения на отдельные строки относятся к WHERE, а ограничения на COUNT, SUM, AVG и другие результаты группировки — к HAVING.

02

WHERE status = 'paid' вместо условной агрегации

В этой конкретной задаче предварительный WHERE тоже позволил бы получить нужные три строки, но он полностью удалил бы отменённые заказы до группировки. Это становится проблемой, как только в том же SELECT нужно одновременно посчитать оплаченные и отменённые заказы. FILTER или SUM(CASE ...) позволяют считать разные условия внутри одной группы.

03

COUNT(column) без понимания NULL и DISTINCT

COUNT(*) считает строки, COUNT(column) — только строки, где конкретная колонка не NULL, а COUNT(DISTINCT column) — число различных ненулевых значений. Эти выражения отвечают на разные вопросы. Подмена одного другим часто проходит на маленьком примере и ломается на скрытых данных.

Задачи по теме в тренажёре

Трек «SQL с нуля → Аналитик»

Дальше полезно менять не только таблицы, но и сам тип агрегации. В SQL Arena есть задачи на определение активности по времени, построение набора товаров пользователя и подсчёт уникальных товаров, а на следующем уровне — аудиторию платформ, историю действий и помесячную выручку. Так приходится выбирать между COUNT, COUNT DISTINCT, SUM, массивами, условными агрегатами и разными уровнями группировки, а не повторять один шаблон. В тренажёре 1 192 задачи с автопроверкой, причём у каждой задачи собственная схема. Решение дополнительно проверяется на скрытых данных, поэтому запрос должен быть корректным в общем случае. PostgreSQL, MySQL, ClickHouse и BigQuery доступны в отдельных браузерных песочницах. Бесплатный тариф без карты включает 292 задачи на всех четырёх диалектах. Для последовательного изучения темы можно пройти трек «SQL с нуля → Аналитик»: он связывает базовые запросы с задачами, которые постепенно требуют более сложной агрегации.

Частые вопросы

Что такое GROUP BY SQL?

GROUP BY объединяет строки с одинаковыми значениями указанных столбцов в группы. Обычно его используют вместе с агрегатными функциями COUNT, SUM, AVG, MIN и MAX, чтобы получить по одной итоговой строке на каждую группу.

Как использовать GROUP BY в SQL?

В GROUP BY перечисляют столбцы, по которым нужно сгруппировать результат. Если столбец выводится в SELECT и не находится внутри агрегатной функции, обычно он должен участвовать в группировке.

Что такое HAVING SQL?

HAVING задаёт условие для уже сформированных групп. Поэтому в HAVING можно проверять результаты агрегатных функций, например оставить только группы, для которых COUNT(*) больше заданного значения.

Чем HAVING отличается от WHERE в SQL?

WHERE фильтрует отдельные строки до группировки, а HAVING — группы после GROUP BY. Если условие относится к исходным строкам, обычно нужен WHERE; если к COUNT, SUM или другому агрегату по группе — HAVING.

Можно ли использовать HAVING без GROUP BY?

Во многих СУБД можно: тогда весь результат рассматривается как одна группа. Но типичный сценарий HAVING связан именно с GROUP BY и фильтрацией нескольких сформированных групп.

Практика GROUP BY SQL на задачах

В SQL Arena можно практиковать агрегаты на разных схемах и проверять запросы автоматически. Для анализа доступны ER-диаграммы и EXPLAIN, а режим «Собеседование» запускает сессию с серверным таймером и без подсказок, после которой формируется отчёт; бесплатно он доступен один раз в день.

Решать задачи на GROUP BY