sqlgroup byhavingагрегациясобеседование

GROUP BY и HAVING в SQL: разница и порядок

2026-06-07 8 мин

Коротко: HAVING фильтрует строки ПОСЛЕ группировки и агрегации, а WHERE — ДО неё. Поэтому в WHERE нельзя писать COUNT(), SUM() и другие агрегаты, а в HAVING — можно и нужно. Если запомнить порядок выполнения запроса (FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY), 90% ошибок с «group by having» исчезают сами.

Связка GROUP BY и HAVING — один из самых частых блоков на собеседовании аналитика данных. Спрашивают не просто синтаксис, а понимание: почему агрегат в WHERE падает с ошибкой, а в HAVING работает. Разберём по шагам с примерами, которые реально встречаются в рабочих задачах.

Что делает GROUP BY?

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

Например, у нас таблица заказов orders(user_id, amount, status). Хотим узнать сумму по каждому клиенту:

Здесь все строки с одинаковым user_id объединяются в группу, а SUM(amount) считает сумму внутри неё. На выходе — по одной строке на клиента.

Главное правило: в SELECT вместе с GROUP BY могут быть только сгруппированные колонки и агрегаты. Нельзя написать SELECT user_id, amount FROM orders GROUP BY user_id — колонка amount не входит в GROUP BY и не обёрнута в агрегат. PostgreSQL выдаст ошибку «column must appear in the GROUP BY clause or be used in an aggregate function». MySQL в старых режимах вернёт случайное значение — это ещё хуже, потому что ошибки нет, а данные мусорные.

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

Это вопрос-ловушка номер один на собесах. Оба ключевых слова фильтруют строки, но на разных этапах.

КритерийWHEREHAVING
Когда работаетдо группировкипосле группировки
С агрегатаминельзя (COUNT(), SUM() запрещены)можно и нужно
По каким полямпо исходным колонкам строкпо результатам агрегации и группировочным колонкам
Производительностьотсекает строки рано, дешевлеработает по уже агрегированным группам

Простое правило: если условие про отдельную строку — это WHERE. Если условие про группу целиком (её сумму, количество, среднее) — это HAVING.

Пример. Найти клиентов, у которых больше 5 оплаченных заказов:

Разберём фильтры:

В каком порядке выполняется SQL-запрос?

Ключ к пониманию HAVING vs WHERE — логический порядок выполнения. SQL пишется не в том порядке, в каком исполняется. Вот реальная последовательность:

Из этого порядка следуют все «странности»:

Этот порядок стоит выучить наизусть — на собеседовании его просят озвучить дословно. Подробный разбор агрегатов с интерактивными задачами есть на странице SQL-агрегация и группировка.

Какие типичные ошибки с GROUP BY и HAVING?

Соберём грабли, на которые наступают чаще всего.

1. Агрегат в WHERE. Классика:

2. Колонка не в GROUP BY и не в агрегате. Если хотите вывести имя клиента вместе с суммой, добавьте имя в GROUP BY либо оберните в агрегат:

3. Фильтр строк затолкали в HAVING. Технически HAVING умеет фильтровать по обычным колонкам, но это медленнее и нелогично:

WHERE отсекает строки рано, до группировки — групп получается меньше, запрос быстрее.

4. Забыли про NULL. COUNT(*) считает все строки группы, а COUNT(amount) — только строки, где amount не NULL. Это разные числа. Если в данных есть NULL, AVG(amount) их игнорирует — среднее считается по не-NULL значениям, а не по всем строкам.

5. HAVING без GROUP BY. Технически допустимо: HAVING тогда работает по всей таблице как по одной группе. Но это редкий случай — обычно это признак опечатки.

Как комбинировать WHERE и HAVING в одном запросе?

В реальных задачах оба фильтра работают вместе. Пример из продуктовой аналитики — найти категории товаров, где за 2026 год средний чек превысил 3000 рублей при минимум 100 заказах:

Как это читается по этапам выполнения:

Заметьте: фильтр по дате — в WHERE (про строку), а фильтры по среднему и количеству — в HAVING (про группу). Это и есть правильное разделение ответственности.

Когда вместо HAVING стоит взять оконную функцию или CTE?

HAVING фильтрует группы, но не даёт сравнить строку с агрегатом группы, не схлопывая её. Если нужно оставить детализацию строк, но при этом фильтровать по агрегату — берут оконные функции или CTE.

Например, «вывести все заказы клиентов, у которых суммарно больше 5 заказов»: GROUP BY + HAVING схлопнет данные до одной строки на клиента, а нам нужны все строки. Решение — посчитать COUNT(*) OVER (PARTITION BY user_id) в подзапросе и отфильтровать снаружи во WHERE. Этот переход «HAVING → оконка» — отличный follow-up вопрос на собесе, который любят задавать middle-аналитикам.

Если запросы становятся многоэтажными, выносите промежуточную агрегацию в CTE (WITH ... AS (...)) — читаемость растёт, а оптимизатор обычно справляется не хуже подзапроса.

Где потренироваться?

Теория без практики выветривается за неделю. Закрепите GROUP BY и HAVING на живых данных:

Пройдите 20-30 задач на агрегацию — и на собеседовании вопрос про разницу HAVING и WHERE вы будете отвечать на автомате, вместе с порядком выполнения запроса и типичными ловушками.

Закрепи GROUP BY и HAVING на практике
Теорию вы только что прочитали — теперь решите 20-30 задач на агрегацию в интерактивном тренажёре с настоящим PostgreSQL в браузере. Ошибки с HAVING и порядком выполнения уходят за один вечер реальной практики.
Открыть SQL-тренажёр →