Что такое CTE (Common Table Expression) и зачем он нужен?
CTE (Common Table Expression) — это временный результат (или временная таблица), определяемый внутри SQL-запроса с помощью конструкции WITH, которая может использоваться в основном запросе так же, как обычная таблица или подзапрос. Он позволяет писать более читаемый, структурированный и модульный SQL-код.
Синтаксис CTE
WITH cte_name (optional_columns) AS (
\-- подзапрос
SELECT ...
)
SELECT ...
FROM cte_name;
Можно использовать несколько CTE в одном запросе, разделяя их запятыми:
WITH cte1 AS (...),
cte2 AS (...)
SELECT ...
FROM cte2
JOIN cte1 ON ...;
Простой пример
Пусть есть таблица sales:
id | employee | amount
-----------------------
1 | Alice | 100
2 | Bob | 200
3 | Alice | 300
Если вы хотите сначала найти сумму по каждому сотруднику, а затем выбрать только тех, кто продал больше 150, можно использовать CTE:
WITH total_sales AS (
SELECT employee, SUM(amount) AS total
FROM sales
GROUP BY employee
)
SELECT \*
FROM total_sales
WHERE total > 150;
Без CTE это пришлось бы писать как вложенный подзапрос, что менее читаемо:
SELECT \*
FROM (
SELECT employee, SUM(amount) AS total
FROM sales
GROUP BY employee
) AS total_sales
WHERE total > 150;
Зачем нужен CTE
-
Повышает читаемость
Логика запроса разбивается на "блоки", каждый из которых — как отдельная подзадача. -
Избегает дублирования кода
Один и тот же подзапрос можно использовать многократно без его копирования. -
Упрощает отладку
Можно проверить корректность CTE отдельно, потом добавлять остальные слои запроса. -
Поддерживает рекурсию
CTE можно использовать для построения рекурсивных запросов — например, обход дерева или иерархии.
Пример с несколькими CTE
Пусть нужно:
-
посчитать среднюю сумму продаж по всем сотрудникам,
-
найти сотрудников, чьи продажи выше среднего,
-
отобразить их имена и суммы.
WITH employee_totals AS (
SELECT employee, SUM(amount) AS total
FROM sales
GROUP BY employee
),
average_total AS (
SELECT AVG(total) AS avg_total
FROM employee_totals
)
SELECT e.employee, e.total
FROM employee_totals e
JOIN average_total a
ON e.total > a.avg_total;
Здесь employee_totals и average_total определяются отдельно, а затем соединяются в основном запросе. Это делает код более модульным и наглядным.
Рекурсивные CTE
Рекурсивный CTE — это способ работать с иерархиями, например, в оргструктуре или дереве категорий.
Пример: таблица employees(id, name, manager_id)
WITH RECURSIVE org_chart AS (
SELECT id, name, manager_id, 1 AS level
FROM employees
WHERE manager_id IS NULL -- топ-менеджеры
UNION ALL
SELECT e.id, e.name, e.manager_id, c.level + 1
FROM employees e
JOIN org_chart c ON e.manager_id = c.id
)
SELECT \* FROM org_chart;
Это позволяет получить структуру сотрудников по уровням.
Ограничения и нюансы
-
CTE — это логическая конструкция, а не физическое сохранение данных, как временная таблица.
-
Производительность может отличаться от подзапросов, особенно при множественном использовании. В некоторых СУБД (например, PostgreSQL) CTE раньше оптимизировались хуже, чем подзапросы, но это меняется.
-
В некоторых движках (например, SQL Server) CTE не может быть индексирован — в отличие от временной таблицы.
Разница между CTE, подзапросом и временной таблицей
Особенность | CTE | Подзапрос | Временная таблица (#table) |
---|---|---|---|
Видимость | Только внутри текущего запроса | Только внутри текущего выражения | Видна в пределах сессии или запроса |
--- | --- | --- | --- |
Читабельность | Высокая | Средняя | Высокая, если много шагов |
--- | --- | --- | --- |
Повторное использование | Да | Нет (дублировать) | Да |
--- | --- | --- | --- |
Индексация | Нет | Нет | Да |
--- | --- | --- | --- |
Рекурсия | Да | Нет | Нет |
--- | --- | --- | --- |
Когда использовать CTE
-
Когда запрос становится слишком сложным из-за вложенных подзапросов.
-
Когда нужен рекурсивный обход дерева/графа.
-
Когда один и тот же подзапрос используется несколько раз.
-
Когда нужно логически разбить большой запрос на шаги для отладки или тестирования.
CTE — мощный инструмент для написания структурированных, модульных и читаемых SQL-запросов.