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.