Кейс: фильтр поверх UNION

Ситуация

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

Причина

WHERE находился внутри первого SELECT. Его область действия заканчивалась до UNION:

SELECT id, state
FROM primary_items
WHERE state = :state
 
UNION
 
SELECT id, state
FROM related_items;

Фильтр применялся только к primary_items.

Мой вклад

Я сначала сформировал общий набор, а затем применил условия к нему:

SELECT *
FROM (
    SELECT id, state
    FROM primary_items
 
    UNION
 
    SELECT id, state
    FROM related_items
) AS all_items
WHERE state = :state
ORDER BY id;

Динамический предикат был перенесён во внешний запрос, поэтому стал одинаковым для обеих веток.

Результат

Фильтрация начала соответствовать контракту метода: условие применяется ко всему результату, независимо от источника строки.

Как я проверяю такой запрос

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

Связанные заметки