HAVING в SQL — фильтрация после GROUP BY
Коротко
HAVING фильтрует группы после агрегации. WHERE работает до GROUP BY и отсеивает отдельные строки. HAVING работает после GROUP BY и отсеивает целые группы по результату агрегатной функции. Без HAVING невозможно написать запрос вида «покажи пользователей с 5+ заказами» или «категории с выручкой больше 100 000». На собеседованиях аналитика данных HAVING встречается в каждой третьей задаче по SQL.
HAVING vs WHERE — ключевое различие
WHERE фильтрует строки до группировки. HAVING фильтрует группы после группировки. Это разные этапы выполнения запроса, и путать их нельзя.
-- WHERE: убирает строки ДО группировки
SELECT category, SUM(amount) AS revenue
FROM orders
WHERE amount > 0
GROUP BY category
-- HAVING: убирает группы ПОСЛЕ группировки
SELECT category, SUM(amount) AS revenue
FROM orders
GROUP BY category
HAVING SUM(amount) > 100000В первом запросе WHERE убирает строки с нулевой суммой до того, как GROUP BY посчитает итоги. Во втором HAVING убирает категории, у которых суммарная выручка не дотягивает до 100 000 — это невозможно сделать через WHERE, потому что на этапе WHERE агрегат ещё не вычислен.
Писать WHERE COUNT(*) > 5 — ошибка. Группировка ещё не произошла, агрегатная функция недопустима. Нужно HAVING COUNT(*) > 5. Подробное сравнение — в статье WHERE vs HAVING.
Синтаксис
HAVING всегда идёт после GROUP BY:
SELECT column, aggregate_function(column2)
FROM TABLE
WHERE условие_на_строки
GROUP BY column
HAVING условие_на_агрегат
ORDER BY ...В HAVING можно использовать любые агрегатные функции: COUNT, SUM, AVG, MIN, MAX. Можно комбинировать несколько условий через AND и OR.
SELECT
user_id,
COUNT(*) AS order_count,
SUM(amount) AS total_spent
FROM orders
GROUP BY user_id
HAVING COUNT(*) >= 5 AND SUM(amount) > 10000Порядок выполнения запроса
Чтобы понять, почему WHERE и HAVING работают по-разному, нужно знать порядок выполнения SQL-запроса:
- FROM — определяет таблицу и джойны
- WHERE — фильтрует строки
- GROUP BY — группирует оставшиеся строки
- HAVING — фильтрует группы
- SELECT — вычисляет выражения
- ORDER BY — сортирует результат
- LIMIT — обрезает количество строк
WHERE выполняется на шаге 2, когда строки ещё не сгруппированы. HAVING — на шаге 4, когда группы уже сформированы и агрегаты вычислены. Именно поэтому в WHERE нельзя использовать агрегатные функции, а в HAVING — можно.
Практические примеры
Пользователи с 5+ заказами
SELECT
user_id,
COUNT(*) AS order_count
FROM orders
GROUP BY user_id
HAVING COUNT(*) >= 5
ORDER BY order_count DESCКлассическая задача с собеседования. GROUP BY группирует заказы по пользователям, HAVING оставляет только тех, у кого 5 и более заказов.
Категории с выручкой больше 100 000
SELECT
p.category,
SUM(o.amount) AS revenue,
COUNT(DISTINCT o.user_id) AS buyers
FROM orders o
JOIN products p ON o.product_id = p.product_id
WHERE o.order_date >= '2025-01-01'
GROUP BY p.category
HAVING SUM(o.amount) > 100000
ORDER BY revenue DESCЗдесь WHERE и HAVING работают вместе: WHERE оставляет заказы с 2025 года, HAVING оставляет категории с выручкой выше порога.
Товары, купленные 3+ уникальными пользователями
SELECT
product_id,
COUNT(DISTINCT user_id) AS unique_buyers,
SUM(quantity) AS total_sold
FROM order_items
GROUP BY product_id
HAVING COUNT(DISTINCT user_id) >= 3COUNT(DISTINCT user_id) в HAVING — обратите внимание, что DISTINCT работает и внутри HAVING, не только в SELECT.
Комбинация WHERE + HAVING + AVG
SELECT
department,
AVG(salary) AS avg_salary,
COUNT(*) AS headcount
FROM employees
WHERE hire_date >= '2023-01-01'
GROUP BY department
HAVING AVG(salary) > 150000 AND COUNT(*) >= 3WHERE оставляет сотрудников, нанятых с 2023 года. GROUP BY группирует по отделам. HAVING оставляет отделы, где средняя зарплата выше 150 000 и хотя бы 3 сотрудника.
HAVING без GROUP BY
Редкий случай, но допустимый. Без GROUP BY вся таблица становится одной группой, и HAVING фильтрует её целиком.
SELECT COUNT(*) AS total_orders
FROM orders
HAVING COUNT(*) > 1000Если в таблице больше 1000 заказов — вернётся одна строка с количеством. Если меньше — пустой результат. На практике такой синтаксис почти не встречается, но на собеседованиях могут спросить: «Сработает ли HAVING без GROUP BY?».
HAVING с подзапросами
HAVING можно комбинировать с подзапросами для сложных условий — например, отфильтровать группы, чей агрегат превышает общее среднее.
SELECT
category,
AVG(price) AS avg_price
FROM products
GROUP BY category
HAVING AVG(price) > (SELECT AVG(price) FROM products)Запрос находит категории, у которых средняя цена товара выше общей средней цены. Подзапрос в HAVING вычисляется один раз и используется для сравнения с каждой группой.
Типичные ошибки
Агрегат в WHERE. WHERE COUNT(*) > 5 — синтаксическая ошибка. Агрегатные функции допустимы только в HAVING, SELECT и ORDER BY.
Алиас в HAVING. HAVING revenue > 100000 — в стандартном SQL ошибка, потому что HAVING выполняется до SELECT, где определяется алиас. PostgreSQL и MySQL допускают это как расширение, но на собеседовании безопаснее дублировать выражение: HAVING SUM(amount) > 100000.
HAVING вместо WHERE. Фильтровать строки через HAVING технически можно — HAVING status = 'active' сработает. Но это неэффективно: база сначала сгруппирует все строки и только потом отфильтрует. WHERE отсеет лишние строки раньше, и группировка будет работать с меньшим объёмом данных.
Забыли GROUP BY. HAVING без GROUP BY трактует всю таблицу как одну группу. Если вы ожидали несколько групп — скорее всего, забыли GROUP BY.
Вопросы с собеседований
-- Чем отличается WHERE от HAVING? -- WHERE фильтрует строки до GROUP BY. HAVING фильтрует группы после GROUP BY. WHERE не может содержать агрегатные функции, HAVING — может.
-- Можно ли использовать HAVING без GROUP BY? -- Да. Без GROUP BY вся таблица считается одной группой. HAVING в таком случае определяет, будет ли возвращена эта единственная строка-результат.
-- Напишите запрос: найти пользователей, у которых средний чек выше 5000 и более 3 заказов.
-- SELECT user_id, AVG(amount) AS avg_check, COUNT(*) AS orders FROM orders GROUP BY user_id HAVING AVG(amount) > 5000 AND COUNT(*) > 3. Два условия в HAVING комбинируются через AND.
-- Почему нельзя написать WHERE SUM(amount) > 10000? -- Потому что WHERE выполняется до GROUP BY. На этапе WHERE строки ещё не сгруппированы, агрегатная функция не применима. Нужно использовать HAVING.
-- В каком порядке выполняются WHERE, GROUP BY, HAVING, SELECT? -- FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, LIMIT. WHERE — до группировки, HAVING — после. SELECT вычисляется позже обоих.
Потренируйтесь решать задачи — откройте тренажёр с 1500+ вопросами для подготовки к собеседованиям аналитиков.
FAQ
Что быстрее — фильтрация через WHERE или HAVING?
WHERE быстрее, потому что отсеивает строки до группировки. Чем меньше строк попадает в GROUP BY, тем меньше работы. Если условие можно выразить через WHERE — используйте WHERE. HAVING нужен только для условий на агрегаты.
Можно ли использовать несколько условий в HAVING?
Да. Условия комбинируются через AND и OR: HAVING COUNT(*) >= 5 AND SUM(amount) > 10000 OR AVG(amount) > 5000. Приоритет AND выше OR — используйте скобки для ясности.
HAVING работает с оконными функциями?
Нет. HAVING работает только с агрегатными функциями (COUNT, SUM, AVG, MIN, MAX). Оконные функции вычисляются на этапе SELECT, то есть после HAVING. Для фильтрации по оконной функции используйте подзапрос или CTE.
Как тренироваться
HAVING — базовая конструкция SQL, которая доведётся до автоматизма после нескольких десятков задач. В тренажёре Карьерник есть вопросы на GROUP BY, HAVING и агрегатные функции с разборами. Больше вопросов по всем темам — в разделе с примерами.