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 после.
SELECT customer_id, COUNT(*)
FROM orders
WHERE amount > 50
GROUP BY customer_id;
SELECT customer_id, COUNT(*)
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 5;
Можно использовать оба сразу:
SELECT customer_id, COUNT(*) AS big_orders
FROM orders
WHERE amount > 50
GROUP BY customer_id
HAVING COUNT(*) >= 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 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.
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 — лучше повторять выражение явно для портабельности.
HAVING— это фильтр для групп послеGROUP BY. ЕслиWHEREотсекает строки до группировки, тоHAVINGотсекает уже сами группы по результатам агрегатов.Пример «на пальцах»: у тебя таблица заказов. Хочешь найти клиентов, у которых больше 10 заказов. «Больше 10 заказов» — это про
COUNT(*), агрегат, и появляется он только после группировки. На отдельной строке заказа этого числа просто нет. ПоэтомуWHEREтут бессилен — нуженHAVING.Зачем нужен HAVING
GROUP BYгруппирует. Агрегаты (COUNT,SUM,AVGи т.д.) считают итоги. АHAVING— фильтрует «итоги»:Без
HAVINGпришлось бы делать запрос дважды или пихать в подзапрос.Базовый синтаксис
SELECT country, COUNT(*) AS users_count FROM users GROUP BY country HAVING COUNT(*) > 100;HAVINGидёт послеGROUP BY, доORDER BY. Условие — на агрегаты или на колонки группировки.Пример с таблицей
Таблица
orders:Сначала сгруппируем по
customer_id:SELECT customer_id, COUNT(*) AS orders_count, SUM(amount) AS total FROM orders GROUP BY customer_id;Промежуточный результат:
Теперь добавим
HAVING— оставим только тех, у когоtotal > 150:SELECT customer_id, COUNT(*) AS orders_count, SUM(amount) AS total FROM orders GROUP BY customer_id HAVING SUM(amount) > 150;Финальный результат:
Клиенты 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упадёт с ошибкой:В таких случаях нужен
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). Если в HAVINGCOUNT(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— лучше повторять выражение явно для портабельности.