Рассматривается рекурсивный CTE с двумя независимыми начальными выборками. Как формируется множество строк ...

Рассматривается рекурсивный CTE с двумя независимыми начальными выборками. Как формируется множество строк до первой рекурсивной итерации?

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

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

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

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

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

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

Это избавляет от необходимости дублировать рекурсивную логику для каждого корня и делает начальный набор явно отделённым от правила перехода к следующему уровню.

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

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

Отдельный риск связан с дубликатами. При UNION ALL одинаковый начальный узел может появиться несколько раз, а рекурсивная ветвь размножит каждое такое вхождение. Это способно увеличить объём результата и исказить подсчёты.

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

Логически рекурсивный CTE состоит из двух частей:

  • якорных ветвей — формируют начальное множество;
  • рекурсивной ветви — получает строки предыдущего шага и строит новые строки.

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

Минимальный пример с двумя начальными узлами:

WITH RECURSIVE reach(node) AS ( SELECT 1 UNION ALL SELECT 10 UNION ALL SELECT e.child FROM edges e JOIN reach r ON e.parent = r.node ) SELECT node FROM reach;

В этом примере узлы 1 и 10 образуют единое начальное множество. Рекурсивная ветвь ищет потомков обоих узлов в рамках одного рекурсивного CTE.

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

Все якорные ветви должны формировать совместимый набор столбцов: совпадать по количеству и иметь совместимые типы. Имена столбцов обычно определяются явным списком имён CTE или первой выборкой, но это не меняет принципа формирования начального множества.

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

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

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

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

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

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

  1. Может ли рекурсивная ветвь различать происхождение строки из разных якорей?

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

  2. Что произойдёт, если две якорные ветви породят одинаковый корень при UNION ALL?

    В начальном наборе останутся две одинаковые строки. Рекурсивная ветвь обработает обе, поэтому потомки могут появиться дважды, а при наличии нескольких уровней число повторов может расти. Если нужна одна логическая копия каждого корня, необходимо устранить дубликаты явно или использовать подходящую семантику UNION.

  3. Обязательно ли выполнение рекурсивной ветви отдельно для каждой якорной выборки?

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