Рекурсивные CTE в SQL: иерархии и графы

0 задач Лёгкие, средние, сложные С эталонными решениями Решение в браузере
WITH RECURSIVE — это водораздел на собеседовании: если кандидат уверенно строит обход дерева, значит, он выходил за пределы простых SELECT с JOIN. Задачи типовые и узнаваемые: «выведи всю цепочку начальников над сотрудником», «посчитай глубину вложенности категорий товара», «размотай реферальное дерево на все уровни». Всё это иерархии переменной глубины, где обычным JOIN не обойтись — вы же не знаете заранее, сколько уровней. Рекурсивный CTE состоит из двух частей, соединённых UNION ALL: якорный запрос (стартовая точка — корень или конкретный сотрудник) и рекурсивная часть, которая ссылается сама на себя и на каждом шаге добавляет следующий уровень. PostgreSQL прогоняет её, пока не перестанут появляться новые строки. Классический подвох — циклы: если в данных менеджер случайно ссылается сам на себя, запрос уходит в бесконечность, и грамотный аналитик сразу добавляет проверку через массив пройденных id или CYCLE-клаузу. На собесе в командах с товарным каталогом или оргструктурой это почти обязательный вопрос.
Начать решать задачи →

Все задачи на «recursive-cte» (0)

FAQ: частые вопросы про recursive-cte

Из каких частей состоит рекурсивный CTE?

Из двух запросов, соединённых UNION ALL внутри WITH RECURSIVE name AS (...). Первый — якорь (anchor): стартовое множество строк, например корень дерева WHERE manager_id IS NULL или конкретный сотрудник. Второй — рекурсивная часть, которая делает JOIN самого CTE с исходной таблицей и добавляет следующий уровень: FROM employees e JOIN name n ON e.manager_id = n.id. PostgreSQL повторяет рекурсивную часть, подставляя результат предыдущей итерации, пока она не вернёт пустой набор.

Как вывести всю цепочку руководителей над сотрудником?

Якорь — сам сотрудник: SELECT id, manager_id, name, 1 AS lvl FROM employees WHERE id = 42. Рекурсия поднимается вверх: SELECT e.id, e.manager_id, e.name, c.lvl + 1 FROM employees e JOIN chain c ON e.id = c.manager_id. На каждом шаге по manager_id находим начальника текущего уровня, счётчик lvl растёт. Итог — вся вертикаль от сотрудника до генерального директора. Если нужно наоборот, разматывать вниз всех подчинённых, меняете условие JOIN на e.manager_id = c.id.

Как защититься от бесконечного цикла в рекурсии?

Два способа. Первый, портативный: тащите массив пройденных id через path = path || e.id и в рекурсивной части добавляете условие WHERE NOT e.id = ANY(c.path), чтобы не заходить в уже посещённый узел. Второй, специфичный для PostgreSQL от версии 14: клауза CYCLE id SET is_cycle USING path, которая сама детектит циклы и помечает их. Без защиты грязные данные (менеджер сам себе начальник или взаимные ссылки) отправят запрос в бесконечный цикл.

Чем рекурсивный CTE лучше нескольких JOIN для иерархии?

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

Как посчитать уровень вложенности и полный путь категории?

Ведёте два накопителя в рекурсии. Уровень — обычный счётчик: lvl = c.lvl + 1 от якоря с lvl = 1. Путь — строковая или массивная конкатенация: path = c.path || ' > ' || cat.name либо path_ids = c.path_ids || cat.id. Тогда для каждой категории вы одновременно получаете её глубину и хлебные крошки вроде 'Электроника > Ноутбуки > Игровые'. Это ровно тот результат, который просят для навигации каталога или breadcrumbs на сайте.

Связанные темы SQL

Агрегации · JOIN-запросы · Оконные функции · CTE (Common Table Expressions) · Подзапросы · Работа с датами · Строковые функции · Условная логика

Открыть весь SQL-тренажёр (565 задач) →