Gymterview
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 теперь инлайнятся, что может как улучшить, так и ухудшить производительность по сравнению с ожидаемым.