Агрегация и группировка

GROUP BY и HAVING

Содержание курса

WHERE против HAVING: момент применения фильтра и совместное использование

Ключевое различие между WHERE и HAVING — это момент, в который каждый из них вступает в работу.

WHERE применяется до группировки. Строки, не прошедшие условие, исчезают из набора данных до того, как GROUP BY успел их увидеть. Агрегатная функция даже не знает об их существовании.

HAVING применяется после группировки. К этому моменту все группы уже собраны и агрегаты вычислены. HAVING просто выбрасывает группы, которые не удовлетворяют условию.

Практически это означает: попытка написать WHERE COUNT(*) > 3 завершится ошибкой — в момент выполнения WHERE никаких агрегатов ещё не существует. Агрегатные функции в WHERE запрещены синтаксически.

Вот запрос, где оба предложения работают вместе:

SELECT category, COUNT(*) AS количество
FROM products
WHERE price > 100
GROUP BY category
HAVING COUNT(*) >= 3;

Что здесь происходит: WHERE price > 100 сначала отсекает все дешёвые товары — в группировку идут только строки, прошедшие фильтр. Затем GROUP BY category собирает оставшиеся строки по категориям. Наконец, HAVING COUNT(*) >= 3 оставляет только те категории, в которых после фильтрации по цене осталось хотя бы три товара.

Это принципиально иной результат, чем если бы WHERE отсутствовал: без него в подсчёт вошли бы и дешёвые товары. Предварительный фильтр может только сохранить или уменьшить COUNT(*) каждой категории, поэтому некоторые категории перестанут проходить порог >= 3, но новые из-за фильтра не появятся.

Практическое правило выбора: если условие можно проверить на уровне отдельной строки без агрегации — используйте WHERE; если условие требует знания итогового значения по группе — используйте HAVING. Фильтрация через WHERE ещё и эффективнее: чем меньше строк дойдёт до группировки, тем меньше работы у GROUP BY.

WHERE отбирает заказы, HAVING — группы

Нужно найти клиентов с оплаченной выручкой больше 100. У первого клиента два оплаченных заказа, у второго есть один оплаченный и один отменённый. Сначала исключим отмены, затем сгруппируем оставшиеся строки и проверим сумму каждой группы.

import sqlite3
with sqlite3.connect(":memory:") as db:
    db.execute("CREATE TABLE orders (customer TEXT, amount INTEGER, status TEXT)")
    db.executemany("INSERT INTO orders VALUES (?, ?, ?)", [("A",80,"paid"),("A",70,"paid"),("B",60,"paid"),("B",1000,"cancelled")])
    result = db.execute("SELECT customer, SUM(amount) FROM orders WHERE status = 'paid' GROUP BY customer HAVING SUM(amount) > 100 ORDER BY customer").fetchall()
    print(result)
    assert result == [("A",150)]

Если убрать WHERE, отменённый заказ сделает клиента B лидером. Если перенести агрегатную проверку в WHERE, перепутаются стадии обработки. Перед оптимизацией запроса вручную посчитайте ожидаемый результат: хороший план выполнения не исправляет неправильное правило.

Продолжите разбор: Концепция оконной функции и OVER с PARTITION BY.

Начать заново?

Помощник начнёт новый диалог по этой задаче. Текущая переписка останется в истории до конца срока хранения.

О переписке

Можно приложить до трёх фото PNG, JPEG или WebP по 5 МБ.

Переписка и фото хранятся 90 дней после последнего принятого сообщения. Преподаватель получит доступ к диалогу, когда вы попросите его помочь.

Проверка решения остаётся отдельным действием.