Программирование SQLDML и запросыМладший разработчик SQL

В отчёте фильтр должен отбирать исходные строки до объединения их в группы. На каком этапе логического выпо...

В отчёте фильтр должен отбирать исходные строки до объединения их в группы. На каком этапе логического выполнения SELECT срабатывает такой фильтр?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

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

Исторический контекст

SQL описывает требуемый результат, а не обязательную последовательность физических операций. Для объяснения смысла запроса используется логическая модель обработки, в которой сначала формируется набор исходных строк, затем он фильтруется, группируется и только после этого агрегируется.

Эта модель нужна, чтобы однозначно понимать область действия условий. Оптимизатор может физически применить фильтр позже или раньше, если это сохраняет результат запроса.

Постановка проблемы

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

Типичная ошибка — пытаться использовать WHERE для условия по агрегированному результату. На момент логического выполнения WHERE групп ещё не существует, поэтому такое условие должно относиться к HAVING.

Подробное решение

Упрощённый логический порядок обработки SELECT выглядит так:

  1. FROM и JOIN формируют исходный набор строк;
  2. WHERE отбрасывает отдельные строки;
  3. GROUP BY объединяет оставшиеся строки в группы;
  4. агрегатные функции вычисляют значения групп;
  5. HAVING отбрасывает группы;
  6. SELECT формирует результирующие столбцы;
  7. ORDER BY сортирует результат.

Минимальный пример:

SELECT department_id, SUM(amount) AS total FROM sales WHERE sale_date >= DATE '2025-01-01' GROUP BY department_id HAVING SUM(amount) > 100000;

Здесь WHERE исключает продажи до 2025 года до формирования групп. Затем для оставшихся продаж вычисляется сумма по отделам, а HAVING оставляет только группы с суммой больше 100 000.

Важно отличать логический порядок от физического плана. СУБД может использовать индекс, выполнить предфильтрацию или поменять порядок внутренних операций. Однако результат должен соответствовать логической семантике WHERE как фильтра исходных строк.

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

Ситуация из практики

В отчёте по отделам требовалось посчитать продажи только за текущий год и показать отделы, где итог превышает установленный порог. Размещение обоих условий в HAVING формально могло дать правильный результат, но заставляло рассматривать при группировке и старые продажи.

Вариант с фильтрацией только после группировки проще написать, но он может обрабатывать больше строк и усложняет чтение запроса. Вариант с WHERE для даты и HAVING для суммы явно отражает разные уровни условий: строки и группы.

Был выбран второй вариант. Он сохраняет корректную семантику, уменьшает объём данных перед группировкой и даёт оптимизатору возможность эффективнее применить фильтр по дате.

Что кандидаты часто упускают

  1. Всегда ли физически WHERE выполняется до GROUP BY?

Нет. Это гарантируется как логическая семантика, но не обязательно как последовательность физических операций. Оптимизатор может перестроить план, например использовать индекс или протолкнуть предикат ближе к источнику данных, если это не меняет наблюдаемый результат.

  1. Можно ли условие по обычному столбцу написать в HAVING?

Во многих СУБД это допустимо, особенно если столбец входит в группировку, но смысл остаётся фильтрацией групп после GROUP BY. Обычно условие, не зависящее от агрегатов, лучше помещать в WHERE: это яснее и потенциально уменьшает объём данных до группировки. Переносимость конкретного варианта зависит от правил СУБД и группировки.

  1. Почему перенос фильтра из WHERE в HAVING может изменить агрегатный результат?

Потому что WHERE исключает строки до вычисления агрегата, а HAVING исключает уже готовые группы. Если исключаемые строки влияют на SUM, COUNT или AVG, то при переносе условия агрегат сначала будет рассчитан по большему набору данных, и итоговое значение изменится.