Виртуальные таблицы и Common Table Expressions (CTE)

Зачем нужны CTE? Временные таблицы на лету 🏗️

Когда запрос становится сложным, как лабиринт, на помощь приходят Common Table Expressions (CTE) — виртуальные таблицы, существующие только во время выполнения запроса. Они делают код:

  • Чище — разбивают монолитные запросы на логические блоки
  • Читабельнее — как главы в книге
  • Удобнее для отладки — можно проверить каждую часть отдельно
WITH team_stats AS (
  SELECT 
    team_id,
    COUNT(*) AS games_played,
    AVG(score) AS avg_score
  FROM matches
  GROUP BY team_id
)
SELECT * FROM team_stats WHERE avg_score > 50;

Базовый синтаксис CTE ⚙️

CTE начинается с ключевого слова WITH, за которым следует имя и определение:

WITH виртуальная_таблица AS (
  SELECT ... FROM ... WHERE ...
)
SELECT * FROM виртуальная_таблица;

Важно:

  • CTE существует только в рамках одного запроса
  • Можно создавать несколько CTE через запятую
  • Допускается вложенность (CTE внутри CTE)

Реальный пример: анализ продаж 🛒

Допустим, нам нужно найти лучших менеджеров по динамике продаж:

WITH monthly_sales AS (
  SELECT 
    manager_id,
    EXTRACT(MONTH FROM sale_date) AS month,
    SUM(amount) AS total
  FROM sales
  GROUP BY manager_id, month
),
growth AS (
  SELECT 
    manager_id,
    (MAX(total) - MIN(total)) / MIN(total) AS growth_rate
  FROM monthly_sales
  GROUP BY manager_id
)
SELECT 
  m.name,
  g.growth_rate * 100 AS "Рост, %"
FROM managers m
JOIN growth g ON m.id = g.manager_id
ORDER BY g.growth_rate DESC
LIMIT 5;

Рекурсивные CTE: мощь рекурсии 🔄

Особый тип CTE, который умеет обращаться к самому себе — идеально подходит для иерархических данных (например, структура компании):

WITH RECURSIVE employee_tree AS (
  -- Базовый случай: начальник
  SELECT id, name, 1 AS level
  FROM employees
  WHERE manager_id IS NULL

  UNION ALL

  -- Рекурсивная часть: подчинённые
  SELECT e.id, e.name, et.level + 1
  FROM employees e
  JOIN employee_tree et ON e.manager_id = et.id
)
SELECT * FROM employee_tree ORDER BY level;

Осторожно: всегда указывайте условие остановки рекурсии!


CTE vs подзапросы: когда что использовать ⚖️

Преимущества CTE:

  1. Читаемость — именованные блоки вместо вложенности
  2. Многократное использование — можно ссылаться несколько раз
  3. Проще дебажить — проверяем каждую часть отдельно

Подзапросы лучше, когда:

  • Нужна простая однострочная операция
  • Требуется максимальная производительность (иногда)

Оптимизация запросов с CTE 🚀

Советы от Данилы:

  1. Ограничивайте данные в CTE настолько рано, насколько возможно
  2. Для больших таблиц добавляйте индексы по полям соединения
  3. Избегайте излишней вложенности — 3-4 CTE обычно оптимально
WITH filtered_data AS (
  SELECT * FROM huge_table WHERE created_at > '2023-01-01'
)
-- Дальше работаем только с отфильтрованными данными

Практика: попробуйте сами! ✏️

Задача: Найдите клиентов, которые покупали больше всего категорий товаров.

Подсказка: вам понадобятся три CTE:

  1. Связь клиентов с заказами
  2. Связь заказов с категориями
  3. Подсчёт уникальных категорий на клиента
WITH client_orders AS (
  SELECT client_id, order_id 
  FROM orders
),
order_categories AS (
  SELECT 
    o.order_id,
    p.category_id
  FROM order_items o
  JOIN products p ON o.product_id = p.id
),
client_category_counts AS (
  SELECT
    co.client_id,
    COUNT(DISTINCT oc.category_id) AS unique_categories
  FROM client_orders co
  JOIN order_categories oc ON co.order_id = oc.order_id
  GROUP BY co.client_id
)
SELECT 
  c.name,
  ccc.unique_categories
FROM clients c
JOIN client_category_counts ccc ON c.id = ccc.client_id
ORDER BY ccc.unique_categories DESC
LIMIT 10;
Скрыть рекламу навсегда

🎥 YouTube: программирование простым языком

Канал, где я спокойно и по шагам объясняю сложные темы — без заумных терминов и лишней теории.

Подходит, если раньше «не заходило», но хочется наконец понять.

▶️ Смотреть курсы на YouTube