Рекурсивные CTE в SQL: шпаргалка
Зачем нужны
Рекурсивные CTE — для задач, где нужна итерация:
- Иерархии (менеджер → подчинённые).
- Генерация последовательностей (календарь).
- Обход графов.
- «Пройти цепочку от X до Y».
Синтаксис
WITH RECURSIVE имя AS (
-- базовый случай (якорь)
SELECT ...
UNION ALL
-- рекурсивный шаг (ссылается на `имя`)
SELECT ...
FROM имя JOIN ...
WHERE условие_остановки
)
SELECT * FROM имя;Три части:
- Anchor — начальное состояние.
- Recursive step — итерация, ссылается на сам CTE.
- Termination — условие в WHERE, которое остановит.
Пример 1: числа от 1 до 10
WITH RECURSIVE nums AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM nums WHERE n < 10
)
SELECT n FROM nums;
-- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10Пример 2: календарь
WITH RECURSIVE cal AS (
SELECT DATE '2026-04-01' AS day
UNION ALL
SELECT day + INTERVAL '1 day'
FROM cal
WHERE day < DATE '2026-04-30'
)
SELECT * FROM cal;Классика для заполнения пропущенных дней в отчётах.
Пример 3: иерархия сотрудников
Таблица:
employees (id, name, manager_id)Все подчинённые менеджера
WITH RECURSIVE subs AS (
SELECT id, name, manager_id, 1 AS level
FROM employees
WHERE id = 42 -- ID менеджера
UNION ALL
SELECT e.id, e.name, e.manager_id, s.level + 1
FROM employees e
JOIN subs s ON e.manager_id = s.id
)
SELECT * FROM subs ORDER BY level;Цепочка менеджеров сотрудника
WITH RECURSIVE chain AS (
SELECT id, name, manager_id, 0 AS level
FROM employees
WHERE id = 42 -- ID сотрудника
UNION ALL
SELECT e.id, e.name, e.manager_id, c.level + 1
FROM employees e
JOIN chain c ON e.id = c.manager_id
)
SELECT * FROM chain;Попробовать силы на подобных вопросах проще всего в тренажёре Карьерник — прямо в Telegram, без регистрации через сайт.
Пример 4: факториал
WITH RECURSIVE fact(n, f) AS (
SELECT 1, 1
UNION ALL
SELECT n + 1, f * (n + 1) FROM fact WHERE n < 10
)
SELECT * FROM fact;
-- 1 1, 2 2, 3 6, 4 24, ..., 10 3628800Пример 5: Фибоначчи
WITH RECURSIVE fib(n, a, b) AS (
SELECT 1, 0, 1
UNION ALL
SELECT n + 1, b, a + b FROM fib WHERE n < 10
)
SELECT n, a AS fibonacci FROM fib;Пример 6: даты в формате «каждый понедельник»
WITH RECURSIVE mondays AS (
SELECT DATE '2026-01-05' AS d -- первый понедельник
UNION ALL
SELECT d + INTERVAL '7 day' FROM mondays
WHERE d < DATE '2026-12-31'
)
SELECT * FROM mondays;Пример 7: разделение строки на символы
WITH RECURSIVE split(s, pos) AS (
SELECT 'hello', 1
UNION ALL
SELECT s, pos + 1 FROM split WHERE pos < LENGTH(s)
)
SELECT SUBSTR(s, pos, 1) AS char FROM split;Пример 8: граф — пути между узлами
WITH RECURSIVE paths AS (
SELECT from_id, to_id, ARRAY[from_id, to_id] AS path
FROM edges
WHERE from_id = 'A' -- стартовый узел
UNION ALL
SELECT p.from_id, e.to_id, p.path || e.to_id
FROM paths p
JOIN edges e ON p.to_id = e.from_id
WHERE NOT e.to_id = ANY(p.path) -- избегаем циклов
)
SELECT * FROM paths WHERE to_id = 'Z';Пройти 30–50 задач по теме за вечер можно в Telegram-тренажёре. Это то, что отличает «знаю» от «уверенно отвечу на собесе».
Пример 9: заполнить пропущенные месяцы в отчёте
WITH RECURSIVE month_calendar AS (
SELECT DATE '2026-01-01' AS month
UNION ALL
SELECT month + INTERVAL '1 month' FROM month_calendar
WHERE month < DATE '2026-12-01'
),
revenue AS (
SELECT DATE_TRUNC('month', created_at)::DATE AS month, SUM(amount) AS rev
FROM orders GROUP BY 1
)
SELECT mc.month, COALESCE(r.rev, 0) AS revenue
FROM month_calendar mc
LEFT JOIN revenue r USING (month)
ORDER BY mc.month;Ограничения
Максимальная глубина
PostgreSQL default — без лимита, но может уйти в бесконечный цикл при ошибке.
Поставьте защиту:
WITH RECURSIVE ... WHERE level < 100 -- max depthPerformance
Рекурсивные CTE могут быть медленнее, чем обычные запросы. Для просто генерации чисел — используйте generate_series:
-- Быстрее, чем RECURSIVE
SELECT generate_series('2026-01-01'::DATE, '2026-12-31'::DATE, '1 day'::INTERVAL)::DATE;Совместимость
- ✅ PostgreSQL, SQL Server, Oracle — full support.
- ⚠️ MySQL 8+ — есть, но ограниченно.
- ❌ MySQL <8 — нет.
- ⚠️ ClickHouse — ограниченно.
Типовые ошибки
1. Нет ключевого слова RECURSIVE
-- Ошибка: CTE ссылается сам на себя без RECURSIVE
WITH nums AS (SELECT 1 UNION ALL SELECT n + 1 FROM nums WHERE n < 10)Нужно WITH RECURSIVE.
2. UNION вместо UNION ALL
-- Дедупликация на каждой итерации — медленно
WITH RECURSIVE ... UNION ... -- ❌
-- UNION ALL — быстрее
WITH RECURSIVE ... UNION ALL ... -- ✅3. Бесконечная рекурсия
Забыли условие остановки → pipeline уходит вникуда.
4. Циклы в графах
Обходя граф без проверки visited — зацикливается. Храните visited array.
Читайте также
FAQ
RECURSIVE — как часто встречается?
На работе редко. На собесах middle+ — часто в задачах про иерархии / генерацию.
Предел рекурсии?
PostgreSQL: по умолчанию ~100 итераций, можно увеличить. SQL Server: 100. Может настраиваться.
Альтернатива для генерации?
generate_series в PostgreSQL, seq в другие диалекты. Проще и быстрее.
generate_series или RECURSIVE?
Для чисел/дат — generate_series. Для иерархий / графов — только RECURSIVE.