HAVING SQL: шпаргалка для собеседования
Зачем HAVING
HAVING фильтрует группы после агрегации. Ровно для случая «покажи только группы, где условие на агрегат выполнено». На собесе это вечный вопрос: HAVING vs WHERE.
Базовый синтаксис
SELECT col, agg_func(x)
FROM TABLE
WHERE row_condition
GROUP BY col
HAVING group_condition;WHERE— фильтр строк до группировки.GROUP BY— сворачивает строки в группы.HAVING— фильтр групп после агрегации.
HAVING vs WHERE
-- WHERE фильтрует строки
SELECT city, COUNT(*) FROM users
WHERE created_at >= '2026-01-01' -- фильтр на строки
GROUP BY city;
-- HAVING фильтрует группы
SELECT city, COUNT(*) FROM users
GROUP BY city
HAVING COUNT(*) > 100; -- фильтр на агрегатГлавное правило: в WHERE не может быть агрегата — он ещё не посчитан. WHERE COUNT(*) > 100 — синтаксическая ошибка.
Порядок выполнения
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BYОтсюда всё:
- В WHERE нельзя алиасы из SELECT — SELECT ещё не выполнился.
- В WHERE нельзя агрегаты — GROUP BY ещё впереди.
- В HAVING можно всё, кроме ORDER BY.
Если хочется сразу закрепить тему на практике — открой тренажёр в Telegram. 10 минут в день — и синтаксис в пальцах.
Когда фильтровать в WHERE, а когда в HAVING
Пример 1: фильтр по значению столбца
-- ✅ WHERE (фильтр строк, быстрее — не группируем лишнее)
SELECT city, COUNT(*) FROM users WHERE country = 'RU' GROUP BY city;Пример 2: фильтр по агрегату
-- ✅ HAVING
SELECT city, COUNT(*) FROM users GROUP BY city HAVING COUNT(*) > 10;Пример 3: и то, и другое
SELECT city, COUNT(*) FROM users
WHERE country = 'RU' -- фильтр строк
GROUP BY city
HAVING COUNT(*) > 10; -- фильтр группПравило оптимизации: фильтруйте в WHERE всё, что можно. Это уменьшает объём данных для GROUP BY.
HAVING без GROUP BY
Можно — если в SELECT только агрегаты:
SELECT COUNT(*), AVG(amount) FROM orders HAVING SUM(amount) > 1000000;Вернёт либо одну строку (если условие выполнено), либо ничего. На практике почти не используется.
Несколько условий в HAVING
SELECT city, COUNT(*) AS users, SUM(revenue) AS rev
FROM orders
GROUP BY city
HAVING COUNT(*) > 10 AND SUM(revenue) > 100000;Комбинируйте через AND/OR.
HAVING с подзапросами
SELECT category, SUM(revenue)
FROM orders JOIN products USING (product_id)
GROUP BY category
HAVING SUM(revenue) > (
SELECT AVG(cat_rev) FROM (
SELECT SUM(revenue) AS cat_rev FROM orders JOIN products USING (product_id)
GROUP BY category
) t
);«Категории с выручкой выше средней по категориям». На senior-собесах спрашивают.
Частые ловушки
1. Агрегат в WHERE
-- ❌ Ошибка
SELECT city FROM users WHERE COUNT(*) > 100 GROUP BY city;
-- ✅ Правильно
SELECT city FROM users GROUP BY city HAVING COUNT(*) > 100;2. Алиас в HAVING
-- PostgreSQL — можно
SELECT city, COUNT(*) AS cnt FROM users GROUP BY city HAVING cnt > 10;
-- MySQL — тоже можно
-- SQL Server — НЕЛЬЗЯ, нужно повторить выражение
SELECT city, COUNT(*) AS cnt FROM users GROUP BY city HAVING COUNT(*) > 10;Безопаснее повторять выражение — работает везде.
3. HAVING для фильтра по столбцу
-- ❌ Некрасиво и медленно
SELECT city, COUNT(*) FROM users GROUP BY city HAVING city = 'Moscow';
-- ✅ Правильно
SELECT city, COUNT(*) FROM users WHERE city = 'Moscow' GROUP BY city;Чтобы не только читать теорию, но и решать реальные задачи — загляните в бот Карьерника. Там по каждой теме подборка вопросов с разборами.
10 задач на HAVING
1. Города с более 100 пользователей
SELECT city, COUNT(*) FROM users GROUP BY city HAVING COUNT(*) > 100;2. Пользователи с более 3 заказами
SELECT user_id FROM orders GROUP BY user_id HAVING COUNT(*) > 3;3. Товары с суммарной выручкой более 1М
SELECT product_id, SUM(amount) FROM orders GROUP BY product_id HAVING SUM(amount) > 1000000;4. Категории с более 10 уникальных покупателей
SELECT category, COUNT(DISTINCT user_id) FROM orders JOIN products USING (product_id)
GROUP BY category HAVING COUNT(DISTINCT user_id) > 10;5. Дни с выручкой выше среднего по месяцу
WITH daily AS (
SELECT created_at::DATE AS day, SUM(amount) AS rev FROM orders GROUP BY 1
)
SELECT day, rev FROM daily
WHERE rev > (SELECT AVG(rev) FROM daily);Через HAVING с подзапросом — чуть длиннее. Через WHERE + CTE — чище.
6. Клиенты, купившие все три продукта
SELECT user_id
FROM orders
WHERE product_id IN ('A', 'B', 'C')
GROUP BY user_id
HAVING COUNT(DISTINCT product_id) = 3;7. Пользователи с минимум 2 платежами и суммой >5000
SELECT user_id FROM payments
GROUP BY user_id
HAVING COUNT(*) >= 2 AND SUM(amount) > 5000;8. Города, где средний чек больше 2000
SELECT city FROM orders GROUP BY city HAVING AVG(amount) > 2000;9. Группы, где присутствует хотя бы одна покупка > 10000
SELECT user_id FROM orders GROUP BY user_id HAVING MAX(amount) > 10000;10. Пары (user, month), где заказов больше 10
SELECT user_id, DATE_TRUNC('month', created_at), COUNT(*)
FROM orders
GROUP BY 1, 2
HAVING COUNT(*) > 10;Как тренироваться
HAVING — простая конструкция, но на собесе регулярно путают с WHERE. Особенно когда фильтр можно написать и там, и там (по столбцу группировки).
Тренажёр Карьерник содержит блок задач на HAVING и сочетание с WHERE, GROUP BY, подзапросами.
Совет: на собесе всегда проговаривайте порядок выполнения (FROM → WHERE → GROUP BY → HAVING). Это автоматически разводит путаницу между WHERE и HAVING.
Читайте также
FAQ
Можно ли HAVING без GROUP BY?
Можно — если в SELECT только агрегаты. Но такой запрос применим редко (либо 1 строка, либо 0). Обычно HAVING идёт в паре с GROUP BY.
Почему алиас из SELECT не работает в WHERE, но работает в HAVING?
Порядок выполнения: WHERE выполняется до SELECT, поэтому алиасы ещё не известны. HAVING выполняется после (условно говоря) — может ссылаться на алиасы в PostgreSQL и MySQL. В SQL Server — нет, повторяйте выражение.
Что эффективнее — WHERE или HAVING?
WHERE, если фильтр применим до группировки. Чем раньше отсечёте строки — тем меньше работы для GROUP BY. HAVING использовать только для условий на агрегаты.
HAVING в NULL
HAVING работает с NULL так же, как WHERE: условие HAVING col = NULL не сработает — используйте HAVING col IS NULL.