SQLHAVINGaggregationtutorial

Что такое HAVING в SQL? Фильтрация групп для начинающих

HAVING — это фильтр, который работает уже после GROUP BY и применяется к агрегатам. Простыми словами: WHERE отсекает строки на входе, HAVING — группы на выходе. Разберём разницу, типичные кейсы (топ-N, поиск аномалий) и почему WHERE и HAVING путают.

3 мин чтенияСправочникSQL · HAVING · aggregation · tutorial

HAVING — это фильтр для групп после GROUP BY. Если WHERE отсекает строки до группировки, то HAVING отсекает уже сами группы по результатам агрегатов.

Пример «на пальцах»: у тебя таблица заказов. Хочешь найти клиентов, у которых больше 10 заказов. «Больше 10 заказов» — это про COUNT(*), агрегат, и появляется он только после группировки. На отдельной строке заказа этого числа просто нет. Поэтому WHERE тут бессилен — нужен HAVING.

Зачем нужен HAVING

GROUP BY группирует. Агрегаты (COUNT, SUM, AVG и т.д.) считают итоги. А HAVING — фильтрует «итоги»:

  • «Покажи только страны, где больше 100 пользователей.»
  • «Покажи только продукты, у которых средняя оценка > 4.»
  • «Покажи только клиентов, которые заказали больше чем на 50 000.»

Без HAVING пришлось бы делать запрос дважды или пихать в подзапрос.

Базовый синтаксис

SELECT country, COUNT(*) AS users_count
FROM users
GROUP BY country
HAVING COUNT(*) > 100;

HAVING идёт после GROUP BY, до ORDER BY. Условие — на агрегаты или на колонки группировки.

Пример с таблицей

Таблица orders:

id customer_id amount
1 1 100
2 1 50
3 1 200
4 2 80
5 3 30
6 3 70

Сначала сгруппируем по customer_id:

SELECT customer_id, COUNT(*) AS orders_count, SUM(amount) AS total
FROM orders
GROUP BY customer_id;

Промежуточный результат:

customer_id orders_count total
1 3 350
2 1 80
3 2 100

Теперь добавим HAVING — оставим только тех, у кого total > 150:

SELECT customer_id, COUNT(*) AS orders_count, SUM(amount) AS total
FROM orders
GROUP BY customer_id
HAVING SUM(amount) > 150;

Финальный результат:

customer_id orders_count total
1 3 350

Клиенты 2 и 3 отсеялись — их total меньше 150. Остался только клиент 1 (350).

HAVING vs WHERE — главное различие

Это самая частая путаница для новичков. Запомни одну фразу: WHERE до группировки, HAVING после.

-- WHERE: фильтрует ИСХОДНЫЕ строки
SELECT customer_id, COUNT(*)
FROM orders
WHERE amount > 50          -- сначала выкидываем заказы дешевле 50
GROUP BY customer_id;

-- HAVING: фильтрует ГРУППЫ после агрегации
SELECT customer_id, COUNT(*)
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 5;       -- потом оставляем только тех, у кого осталось > 5 заказов

Можно использовать оба сразу:

SELECT customer_id, COUNT(*) AS big_orders
FROM orders
WHERE amount > 50          -- только дорогие заказы
GROUP BY customer_id
HAVING COUNT(*) >= 3;      -- среди них — клиенты, у кого таких ≥ 3
ORDER BY big_orders DESC;

Логически: «Клиенты, у которых хотя бы 3 заказа дороже 50, отсортированы от самого активного».

Что можно писать в HAVING

  • Агрегатные функции: COUNT(*), SUM(amount), AVG(score), MAX/MIN.
  • Колонки из GROUP BY: их тоже можно (хотя то же самое можно вынести в WHERE и часто будет быстрее).
  • Логику: AND, OR, NOT, BETWEEN, IN.
-- Несколько условий в HAVING
HAVING COUNT(*) > 5 AND AVG(rating) >= 4.0;

-- Можно использовать вычисленные значения
HAVING SUM(amount) BETWEEN 1000 AND 10000;

Алиасы из SELECT в HAVING использовать нельзя в большинстве БД (потому что HAVING выполняется до того, как известны алиасы). Postgres более лоялен и иногда позволяет — но лучше повторять выражение явно.

HAVING без агрегата

Технически можно написать HAVING country = 'RU' — Postgres не упадёт. Но это будет медленнее, чем WHERE country = 'RU'. Правило: если условие можно в WHERE — пиши в WHERE. HAVING оставляй для агрегатов.

Частые ошибки новичков

1. Использовать WHERE для агрегата. WHERE COUNT(*) > 10 упадёт с ошибкой:

aggregate functions are not allowed in WHERE

В таких случаях нужен HAVING. WHERE не знает про результаты агрегации, потому что выполняется ДО группировки.

2. HAVING без GROUP BY. Технически можно, но редко имеет смысл — без GROUP BY вся таблица это «одна большая группа», и HAVING SUM(amount) > 1000 либо вернёт всё, либо ничего.

3. Дублирование условия в WHERE и HAVING. Если фильтрация не требует агрегата — клади её в WHERE. Postgres всё равно постарается «протолкнуть» условие в WHERE на уровне планировщика, но рассчитывать на это не стоит. И WHERE использует индексы лучше.

4. Алиасы из SELECT в HAVING.

-- В большинстве БД НЕ работает (в Postgres работает)
SELECT country, COUNT(*) AS cnt
FROM users
GROUP BY country
HAVING cnt > 100;

-- Универсально и надёжно
HAVING COUNT(*) > 100;

5. Забывают, что COUNT(*)COUNT(column). Если в HAVING COUNT(email) > 100 — считаются только не-NULL emails. На таблице, где половина юзеров без email, разница огромна.

6. HAVING с WHERE — порядок написания. WHERE всегда до GROUP BY, HAVINGпосле. Перепутаешь местами — ошибка синтаксиса.

Мини-резюме

  • WHERE до группировки, HAVING после.
  • В HAVING можно агрегаты (COUNT, SUM, AVG, ...) и колонки из GROUP BY.
  • Условия без агрегатов — лучше в WHERE (быстрее, использует индексы).
  • Можно сочетать оба: WHERE отсекает строки, потом GROUP BY, потом HAVING отсекает группы.
  • Алиасы из SELECT в HAVING — лучше повторять выражение явно для портабельности.

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр