WHERE и HAVING в SQL: разница на примерах аналитики
Понятное сравнение WHERE и HAVING в SQL: фильтрация строк до GROUP BY, фильтрация групп после агрегации и типичные ошибки аналитика.
Содержание статьи
Запрос «покажи каналы с более чем 100 заказами» выглядит почти так же, как «покажи заказы дороже 1000 ₽», но это разные уровни данных. В первом случае условие относится к группе, во втором — к строке заказа. Ошибка в выборе WHERE или HAVING даёт либо синтаксическую ошибку, либо тихо меняет смысл отчёта. Разберём вопрос на одном запросе: Где фильтровать данные: до группировки или после неё?
Когда нужен этот приём
Где фильтровать данные: до группировки или после неё?
Для выручки канала сначала оставь только оплаченные заказы за период через WHERE. После GROUP BY посчитай SUM(amount), а затем HAVING revenue > 500000. Если поставить status = paid в HAVING, читателю сложнее понять зерно, а база может сначала построить больше групп.
Используй WHERE для отбора исходных строк и HAVING для отбора результатов группировки. Если условие сложно объяснить одним предложением, сначала вынеси промежуточный слой в CTE. Хороший SQL показывает не только ответ, но и уровень, на котором он получен.
Логика оператора
WHERE убирает исходные строки до GROUP BY. HAVING фильтрует уже собранные группы и может использовать агрегаты COUNT, SUM или AVG. Чем раньше отфильтрованы ненужные строки, тем меньше данных проходит дальше. Поэтому условие по исходному столбцу обычно стоит в WHERE.
Опиши результат одной фразой: «одна строка — канал за месяц». Затем раздели фильтры на две корзины: фильтры событий и фильтры агрегата. Проверь знаменатель: удаление строк WHERE меняет и числитель, и базу для последующей агрегации. Для сложных выражений используй CTE.
- Зафиксируй одну строку результата.
- Отдели условия по исходным строкам от условий по рассчитанным значениям.
- Назови период, ключи связи и правило для повторов.
Рабочая версия
Названия таблиц здесь условные. При переносе сохрани зерно и бизнес-условия, а не буквальный синтаксис примера.
SELECT channel, COUNT(*) AS orders_count, SUM(amount) AS revenue
FROM orders
WHERE status = 'paid' AND paid_at >= DATE '2026-07-01'
GROUP BY channel
HAVING COUNT(*) >= 100 AND SUM(amount) >= 500000
ORDER BY revenue DESC;Как сверить ответ
Собери пять-десять строк, где ответ можно получить вручную. Добавь NULL, дубль, объект без связанной записи и значение на границе периода. Затем сравни число строк и уникальных ключей до и после спорного шага.
Выполненный без ошибки запрос ещё не является правильным. Другой аналитик должен суметь восстановить из него источник, фильтр, grain и ожидаемое поведение на крайних случаях.
| Условие | Уровень | Оператор |
|---|---|---|
| status = paid | исходная строка | WHERE |
| paid_at в периоде | исходная строка | WHERE |
| COUNT(*) >= 100 | группа | HAVING |
| SUM(amount) > 500k | группа | HAVING |
Когда выбрать другой способ
HAVING без GROUP BY иногда допустим для одной общей группы, но это не делает запрос понятнее. Не переноси фильтр по дате из WHERE в HAVING. Не фильтруй SUM после округления, если порог чувствителен. И не сравнивай результаты двух запросов, если в одном WHERE исключает отмены, а в другом нет.
Используй WHERE для отбора исходных строк и HAVING для отбора результатов группировки. Если условие сложно объяснить одним предложением, сначала вынеси промежуточный слой в CTE. Хороший SQL показывает не только ответ, но и уровень, на котором он получен.
В условном отчёте WHERE сокращает строки, HAVING — группы.
Небольшой эксперимент
Составь четыре запроса: товары с price > 1000, категории с COUNT(*) > 50, каналы с SUM(amount) выше порога и сегменты с AVG(order_value) ниже медианы. Для каждого подпиши, на каком уровне живёт условие. Затем сравни число обработанных строк.
- Сначала запиши ожидаемый результат словами.
- Проверь запрос на маленькой контрольной выборке.
- Объясни, что запрос считает и чего не доказывает.
Следующий шаг
Используй WHERE для отбора исходных строк и HAVING для отбора результатов группировки. Если условие сложно объяснить одним предложением, сначала вынеси промежуточный слой в CTE. Хороший SQL показывает не только ответ, но и уровень, на котором он получен.
Материалы по теме
Порядок выполнения SQL-запроса: почему WHERE не видит alias из SELECT
Разбираем логический порядок выполнения SQL-запроса: FROM, WHERE, GROUP BY, HAVING, SELECT и ORDER BY. Примеры помогают понять ошибки alias и агрегации.
DISTINCT и GROUP BY в SQL: что выбрать для уникальных значений
Разбираем разницу DISTINCT и GROUP BY в SQL: список уникальных пользователей, дедупликация событий, агрегаты и риск скрыть проблему в данных.

SQL GROUP BY и COUNT: как считать пользователей по каналам и дням
Разбираем GROUP BY, COUNT и COUNT DISTINCT на задачах аналитика: пользователи по каналам, DAU по дням и фильтрация агрегатов через HAVING.