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

Назовите условия, при которых фильтр внешнего запроса можно безопасно протолкнуть внутрь каждой ветви UNION ALL.

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

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

Фильтр можно протолкнуть внутрь каждой ветви UNION ALL, если он зависит только от столбцов, доступных в результате объединения, одинаково трактуется в каждой ветви и не меняет семантику из-за порядка операций. Для этого также не должны мешать конструкции вроде LIMIT, оконных функций или недетерминированных выражений.

В реляционной алгебре это преобразование выражается так: выборка над объединением σp(A ∪all B) эквивалентна σp(A) ∪all σp(B). Практический эффект — уменьшение объёма данных до объединения и возможность эффективнее использовать индексы или отсечение партиций.

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

Подобные преобразования появились из потребности отделить логическое описание результата от физкого способа его получения. SQL-запрос должен задавать результат, а оптимизатор — выбирать эквивалентный план с меньшим объёмом промежуточных данных.

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

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

Рассмотрим объединение текущих и архивных продаж. Если условие по сумме применяется только после UNION ALL, обе таблицы могут быть полностью прочитаны и объединены, даже когда нужны лишь дорогие продажи.

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

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

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

  • предикат использует только столбцы результата UNION ALL;
  • соответствующие столбцы ветвей имеют одинаковую семантику, а не только совместимые типы;
  • предикат детерминированен и не зависит от побочных эффектов или числа вызовов выражения;
  • между источником данных и внешним фильтром нет операций, чувствительных к порядку строк или кардинальности, например LIMIT, OFFSET или вычисления оконных функций;
  • сохраняется трёхзначная логика SQL, включая поведение NULL.

Минимальный пример эквивалентного преобразования:

WITH sales AS ( SELECT id, amount FROM current_sales UNION ALL SELECT id, amount FROM archive_sales ) SELECT id, amount FROM sales WHERE amount >= 100; SELECT id, amount FROM current_sales WHERE amount >= 100 UNION ALL SELECT id, amount FROM archive_sales WHERE amount >= 100;

Во втором варианте каждая ветвь уменьшает свой результат до объединения. Дубликаты при этом сохраняются в том же количестве, потому что используется именно UNION ALL; фильтр лишь удаляет строки, не устраняя повторения.

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

Проталкивание не является гарантией конкретного физического плана: оптимизатор может отказаться от него из-за стоимости, ограничений реализации или отсутствия подходящего индекса. Поэтому логическая эквивалентность преобразования и фактическая эффективность — разные вопросы.

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

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

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

Выбрали представление с единым логическим фильтром и проверили план выполнения: оптимизатор протолкнул предикаты в обе ветви и использовал отсечение партиций. Это сохранило читаемость исходного запроса и уменьшило объём чтения; при отсутствии такой оптимизации фильтр дублировали явно в контролируемом слое доступа.

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

  1. Сохраняется ли количество дубликатов после проталкивания фильтра через UNION ALL?

Да. UNION ALL не устраняет дубликаты, а проталкивание фильтра только проверяет каждую строку раньше. Каждая строка, удовлетворяющая предикату, останется ровно столько раз, сколько она присутствовала в исходном результате.

  1. Можно ли протолкнуть фильтр, если он использует столбец, вычисленный внутри ветви?

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

  1. Гарантирует ли логически допустимое проталкивание ускорение запроса?

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