middle
Что такое CTE (Common Table Expressions)?
CTE (Common Table Expressions) — именованные подзапросы, определяемые в блоке WITH, которые улучшают читаемость сложных запросов и позволяют ссылаться на один и тот же подзапрос несколько раз.
Простой и составной CTE
-- Простой CTE
WITH large_accounts AS (
SELECT id, client_id, balance
FROM accounts
WHERE balance > 1000000
)
SELECT c.name, la.balance
FROM large_accounts la
JOIN clients c ON c.id = la.client_id;
-- Несколько CTE
WITH
debits AS (
SELECT account_id, sum(amount) AS total_debit
FROM transactions
WHERE type = 'DEBIT' AND created_at > '2024-01-01'
GROUP BY account_id
),
credits AS (
SELECT account_id, sum(amount) AS total_credit
FROM transactions
WHERE type = 'CREDIT' AND created_at > '2024-01-01'
GROUP BY account_id
)
SELECT
a.id,
COALESCE(d.total_debit, 0) AS total_debit,
COALESCE(c.total_credit, 0) AS total_credit,
COALESCE(c.total_credit, 0) - COALESCE(d.total_debit, 0) AS net_flow
FROM accounts a
LEFT JOIN debits d ON d.account_id = a.id
LEFT JOIN credits c ON c.account_id = a.id;
Рекурсивный CTE
Пример
-- Иерархия подразделений
WITH RECURSIVE department_tree AS (
-- Якорный запрос (базовый случай)
SELECT id, name, parent_id, 1 AS level
FROM departments
WHERE parent_id IS NULL
UNION ALL
-- Рекурсивный запрос
SELECT d.id, d.name, d.parent_id, dt.level + 1
FROM departments d
JOIN department_tree dt ON d.parent_id = dt.id
)
SELECT * FROM department_tree ORDER BY level, name;
CTE с модификацией данных
Пример
-- Удалить устаревшие записи и вернуть количество
WITH deleted AS (
DELETE FROM sessions
WHERE expires_at < now()
RETURNING *
)
SELECT count(*) AS deleted_count FROM deleted;
Важно о производительности
- До PostgreSQL 12 CTE были «барьером оптимизации» — планировщик не мог протолкнуть условия из внешнего запроса внутрь CTE
- Начиная с PostgreSQL 12 CTE по умолчанию инлайнятся (встраиваются) в основной запрос, если используются один раз и не имеют побочных эффектов
- Принудительная материализация:
WITH cte AS MATERIALIZED (...) - Принудительный инлайн:
WITH cte AS NOT MATERIALIZED (...)
На собеседовании: два ключевых момента: (1) рекурсивный CTE для обхода деревьев — WHERE RECURSIVE и UNION ALL, (2) изменение поведения в PostgreSQL 12 — CTE теперь инлайнятся, что может как улучшить, так и ухудшить производительность по сравнению с ожидаемым.