Рекурсивные CTE в SQL: шпаргалка

Зачем нужны

Рекурсивные CTE — для задач, где нужна итерация:

  • Иерархии (менеджер → подчинённые).
  • Генерация последовательностей (календарь).
  • Обход графов.
  • «Пройти цепочку от X до Y».

Синтаксис

WITH RECURSIVE имя AS (
    -- базовый случай (якорь)
    SELECT ...

    UNION ALL

    -- рекурсивный шаг (ссылается на `имя`)
    SELECT ...
    FROM имя JOIN ...
    WHERE условие_остановки
)
SELECT * FROM имя;

Три части:

  1. Anchor — начальное состояние.
  2. Recursive step — итерация, ссылается на сам CTE.
  3. 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 depth

Performance

Рекурсивные 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.