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-запроса:

  1. FROM — определяет таблицу и джойны
  2. WHERE — фильтрует строки
  3. GROUP BY — группирует оставшиеся строки
  4. HAVING — фильтрует группы
  5. SELECT — вычисляет выражения
  6. ORDER BY — сортирует результат
  7. 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) >= 3

COUNT(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(*) >= 3

WHERE оставляет сотрудников, нанятых с 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 и агрегатные функции с разборами. Больше вопросов по всем темам — в разделе с примерами.