При разбиении фильтра с OR на две ветви UNION ALL в каком случае изменится число строк результата?
Число строк увеличится, если одна исходная строка удовлетворяет обоим условиям: исходный фильтр с OR возвращает её один раз, а UNION ALL добавляет её из каждой подходящей ветви. Такое преобразование эквивалентно только при взаимоисключающих условиях либо при дополнительном контроле дубликатов.
Разбиение условия OR на несколько запросов применяют, чтобы дать оптимизатору возможность независимо использовать индексы или разные стратегии доступа для каждой ветви. Особенно полезен этот подход, когда сложное условие плохо оптимизируется как единое выражение.
Однако SQL обычно сохраняет дубликаты, если явно не указано обратное. Поэтому преобразование должно учитывать не только логическую эквивалентность условий, но и правила формирования множества строк.
Пусть строка подходит одновременно под оба предиката. В исходном запросе логическое выражение условие_1 OR условие_2 имеет истинное значение, и строка попадает в результат один раз.
После разбиения с UNION ALL та же строка попадёт в первую и вторую ветви. В результате появится две строки. Ошибка особенно опасна в отчётах, подсчётах и дальнейшей агрегации: суммы и количества могут быть завышены.
UNION ALL объединяет результаты без удаления дубликатов. Поэтому преобразование безопасно, если ветви не пересекаются или если бизнес-логика допускает повторное появление строк.
Например, условия по статусу и региону пересекаются для европейских заказов со статусом paid:
Первый запрос возвращает три строки, а второй — четыре: заказ с идентификатором 1 присутствует в обеих ветвях. Замена UNION ALL на UNION уберёт повторяющиеся строки результата, но это не всегда эквивалентно исходному запросу: если исходная таблица или соединение уже содержат две одинаковые строки, UNION дополнительно их дедуплицирует.
Надёжный вариант — сделать ветви взаимоисключающими. Вторая ветвь должна выбирать строки, подходящие под второе условие, но не подходящие под первое. При этом необходимо учитывать трёхзначную логику SQL: значение UNKNOWN, возникающее из-за NULL, не равно TRUE, поэтому простое отрицание условия может требовать отдельной проверки.
Если корректно построить непересекающиеся ветви невозможно, следует сохранить исходную форму запроса либо применять дедупликацию с чётко определёнными правилами. Цена безопасного решения — возможная дополнительная сортировка, хеширование или потеря преимуществ независимого доступа по индексам.
В отчёте нужно выбрать товары, которые либо имеют статус discounted, либо относятся к категории featured. Разработчик разделил запрос на две индексируемые ветви через UNION ALL. Товары, удовлетворяющие обоим признакам, начали учитываться дважды, поэтому количество товаров и сумма продаж выросли.
Вариант с UNION устранил повторения, но оказался неверным для отчёта, где одинаковые строки из разных источников должны сохраняться. Вариант с одной ветвью и OR был семантически корректен, однако использовал менее эффективный план.
Выбрали две взаимоисключающие ветви: первая выбирала товары со статусом discounted, а вторая — featured, исключая уже отобранные первой ветвью. Это сохранило возможность раздельной оптимизации и исходную кратность строк; после проверки случаев с NULL результат совпал с исходной логикой.
Нет. UNION удаляет дубликаты полного результата, тогда как исходный фильтр с OR не удаляет дубликаты, уже существующие в исходном наборе или появившиеся после соединения. Такая замена может изменить как количество строк, так и результаты последующей агрегации.
Не всегда. При NULL отрицание может дать UNKNOWN, а не TRUE. Например, если первое условие сравнивает столбец со значением, то строка с NULL может не попасть ни в ветвь первого условия, ни в ветвь с его обычным отрицанием. Взаимоисключение нужно проверять с учётом того, какие строки исходное выражение OR считает истинными.
Соединение само может порождать несколько строк для одной сущности, если правая сторона не уникальна. Разбиение фильтра через UNION ALL затем может добавить одну и ту же уже размноженную строку из нескольких ветвей. Поэтому проверять эквивалентность нужно по фактическим строкам результата и их кратности, а не только по уникальным идентификаторам сущностей.