Ответ
Основное различие в том, что нерекурсивное CTE — это просто временный, именованный набор результатов для упрощения сложных запросов, а рекурсивное CTE ссылается на само себя для выполнения итеративных операций, что идеально подходит для работы с иерархическими или графовыми данными.
Нерекурсивное CTE
Используется как временная таблица в рамках одного запроса. Улучшает читаемость.
Пример: выбрать активных пользователей и затем отфильтровать их по имени.
WITH ActiveUsers AS (
SELECT id, name, email
FROM users
WHERE status = 'active'
)
SELECT name, email
FROM ActiveUsers
WHERE name LIKE 'A%';
Рекурсивное CTE
Имеет обязательную структуру из двух частей, объединенных UNION ALL:
- Базовый случай (Anchor Member): Начальный запрос, который выполняется один раз.
- Рекурсивная часть (Recursive Member): Запрос, который ссылается на само CTE и выполняется до тех пор, пока не вернет пустой результат.
Пример: найти иерархию категорий товаров.
WITH RECURSIVE CategoryTree AS (
-- 1. Базовый случай: выбираем корневую категорию
SELECT id, name, parent_id, 0 AS level
FROM categories
WHERE parent_id IS NULL
UNION ALL
-- 2. Рекурсивная часть: присоединяем дочерние категории
SELECT c.id, c.name, c.parent_id, ct.level + 1
FROM categories c
JOIN CategoryTree ct ON c.parent_id = ct.id
)
SELECT *
FROM CategoryTree;
В Go для выполнения таких запросов используется стандартный пакет database/sql, а результат обрабатывается как обычный SELECT.
Ответ 18+ 🔞
Ну и вот, сидишь ты такой, разбираешься с этими вашими CTE, а там, оказывается, два зверя живут. Один — простой, как валенок, а второй — сам на себя ссылается, как ёбанный нарцисс в зеркале. Сейчас разберём, в чём подвох.
CTE обыкновенный, нерекурсивный
Это просто придуманное на ходу имя для какой-то выборки, чтобы в основном запросе не городить огород. Чисто для красоты и читаемости, чтобы глаза не ебало. Взял кусок запроса, обозвал его и юзаешь дальше.
Смотри, как просто: сначала выцепил всех активных юзеров, а потом из них тех, кто на «А» начинается.
WITH ActiveUsers AS (
SELECT id, name, email
FROM users
WHERE status = 'active'
)
SELECT name, email
FROM ActiveUsers
WHERE name LIKE 'A%';
Всё, пиздец. Никакой магии. Сделал временную табличку в голове у базы и пошёл дальше.
А теперь держись за стул: CTE рекурсивный
Вот тут начинается ёперный театр. Эта штука умеет сама себя жрать, как та змея, которая хвост гоняет. Обязательно состоит из двух кусков, склеенных UNION ALL:
- Якорь (Anchor Member): Это как стартовая точка, основание, с которого всё начинается. Выполняется один раз и всё.
- Рекурсивная часть (Recursive Member): А вот это уже запрос, который тыкает пальцем в само CTE и говорит: «Эй, дай-ка я присоединюсь к тому, что у тебя уже есть!». И так по кругу, пока не кончатся строки для присоединения.
Классический пример — разгрести иерархию категорий, где у каждой есть родительская.
WITH RECURSIVE CategoryTree AS (
-- 1. Якорь: ловим корневые категории, у которых нет родителя
SELECT id, name, parent_id, 0 AS level
FROM categories
WHERE parent_id IS NULL
UNION ALL
-- 2. Рекурсия: цепляем к найденным их детей, потом детей детей, и так до упора
SELECT c.id, c.name, c.parent_id, ct.level + 1
FROM categories c
JOIN CategoryTree ct ON c.parent_id = ct.id
)
SELECT *
FROM CategoryTree;
И вот он, пиздец, красота! Запрос будет крутиться, пока не пройдёт все уровни вложенности.
А что в Go, спросишь?
Да хуйня вопрос. В Go для этого дела используется стандартный database/sql. Запускаешь этот свой рекурсивный запрос через db.Query(), как самый обычный SELECT, а потом построчно считываешь результат, пока rows.Next() не вернёт false. Никаких особых плясок с бубном — база данных всю рекурсивную мозгоёбку делает сама, а ты просто получаешь готовый список строк.