Сравните фильтрацию строк до группировки с фильтрацией групп после агрегации: когда перенос условия меняет результат?
Фильтрация до группировки отбрасывает отдельные строки перед расчётом агрегатов, а фильтрация после агрегации отбрасывает уже сформированные группы. Перенос условия меняет результат, если условие зависит от агрегата или если нужно считать агрегат по полному набору строк группы.
Разделение фильтрации строк и групп появилось из-за различий между реляционными операциями и агрегированием. Сначала нужно определить набор строк, участвующих в расчёте, затем сформировать группы и вычислить их показатели.
В SQL для этих этапов обычно используются WHERE и HAVING. Такое разделение позволяет явно выразить, фильтруются ли исходные записи или результаты группировки.
Допустим, нужно найти клиентов, у которых суммарная выручка превышает 100. Если отфильтровать отдельные заказы с суммой больше 100 до группировки, заказы меньшего размера исчезнут из расчёта. В результате будет посчитана не полная выручка клиента, а сумма только выбранных заказов.
Обратная ошибка также возможна: условие по отдельной строке нельзя корректно применить после группировки без изменения смысла. Неверное расположение фильтра может привести к потере данных, завышению или занижению агрегатов и неправильным бизнес-выводам.
WHERE применяется к отдельным строкам до группировки. Поэтому условие в нём должно проверять свойства исходной записи, например дату заказа, статус или сумму конкретного заказа.
HAVING применяется к группам после выполнения GROUP BY. Он предназначен для условий по агрегатам: суммарной выручке, количеству заказов, среднему значению и другим рассчитанным показателям.
В этом примере сначала исключаются неоплаченные заказы, затем для каждого клиента считается сумма оплаченных заказов, и только после этого остаются клиенты с суммой больше 100.
Условия по ключам группировки иногда можно перенести из HAVING в WHERE без изменения результата. Например, фильтр по региону клиента безопаснее применить до группировки: это уменьшит объём обрабатываемых данных. Но условие по SUM, COUNT или AVG нельзя перенести в WHERE, потому что на этом этапе агрегат ещё не вычислен.
Важен и смысл самого условия. WHERE amount > 100 означает «учитывать только заказы дороже 100», а HAVING SUM(amount) > 100 — «оставить клиентов, чья полная сумма превышает 100». Это разные бизнес-метрики, даже если в тексте используется похожий порог.
Аналитик оценивал клиентов с выручкой более 10 000 рублей. В первом варианте он отфильтровал заказы дороже 10 000 до группировки. Такой вариант прост и быстрее обрабатывается, но исключает множество небольших заказов и не отвечает на поставленный вопрос.
Во втором варианте он сначала оставил только оплаченные заказы, сгруппировал их по клиенту, вычислил сумму и затем применил условие к агрегату. Этот вариант корректно отражает полную выручку, хотя требует сформировать группы по большему числу строк.
Был выбран второй вариант, поскольку бизнес-условие относилось к общей выручке клиента. Предварительный фильтр по статусу оплаты сохранили в WHERE, так как неоплаченные заказы не должны участвовать в расчёте. В результате метрика стала соответствовать определению, а число ошибочно включённых клиентов уменьшилось.
Нет. Условие SUM(amount) > 100 проверяет итог группы, а фильтрация amount > 100 проверяет каждую строку отдельно. Клиент с двумя заказами по 60 должен пройти первое условие, но будет исключён вторым.
Если неоплаченные заказы не относятся к определяемой метрике выручки, их нужно исключить до расчёта суммы. Если применить фильтр после группировки, оплаченные и неоплаченные заказы уже повлияют на агрегат, и удалить вклад неоплаченных записей простым условием по группе нельзя.
Нет. Перенос безопасен только при сохранении логики запроса, например когда условие зависит от ключа группировки и не меняет набор строк внутри группы. Условия по агрегатам переносить нельзя. Даже корректная оптимизация должна учитывать NULL, дубликаты, тип соединения и точное определение метрики.