← Назад к вопросам

Что такое CTE (Common Table Expression) в SQL?

2.2 Middle🔥 161 комментариев
#SQL и базы данных

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое CTE (Common Table Expression)

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

Синтаксис

WITH cte_name AS (
  SELECT column1, column2
  FROM table1
  WHERE condition
)
SELECT *
FROM cte_name
WHERE additional_condition;

Пример 1: Простой CTE

-- Без CTE (сложнее читать)
SELECT 
  department,
  COUNT(*) as employee_count,
  AVG(salary) as avg_salary
FROM (
  SELECT department, salary
  FROM employees
  WHERE hire_date > '2020-01-01'
) AS filtered_employees
GROUP BY department;

-- С CTE (понятнее)
WITH recent_employees AS (
  SELECT department, salary
  FROM employees
  WHERE hire_date > '2020-01-01'
)
SELECT 
  department,
  COUNT(*) as employee_count,
  AVG(salary) as avg_salary
FROM recent_employees
GROUP BY department;

Пример 2: Множественные CTE

WITH sales_2023 AS (
  SELECT 
    product_id,
    SUM(amount) as total_sales
  FROM sales
  WHERE YEAR(date) = 2023
  GROUP BY product_id
),
sales_2024 AS (
  SELECT 
    product_id,
    SUM(amount) as total_sales
  FROM sales
  WHERE YEAR(date) = 2024
  GROUP BY product_id
)
SELECT 
  s23.product_id,
  s23.total_sales as sales_2023,
  s24.total_sales as sales_2024,
  ROUND((s24.total_sales - s23.total_sales) / s23.total_sales * 100, 2) as growth_percent
FROM sales_2023 s23
JOIN sales_2024 s24 ON s23.product_id = s24.product_id
ORDER BY growth_percent DESC;

Пример 3: Рекурсивный CTE

-- Иерархия сотрудников (подчинённые и начальники)
WITH RECURSIVE employee_hierarchy AS (
  -- Базовый случай: сотрудники без начальника (CEO)
  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,
    eh.level + 1
  FROM employees e
  INNER JOIN employee_hierarchy eh ON e.manager_id = eh.id
  WHERE eh.level < 10  -- ограничение глубины рекурсии
)
SELECT 
  CONCAT(REPEAT('  ', level - 1), name) as employee_tree,
  level
FROM employee_hierarchy
ORDER BY id;

Пример 4: Запрос с CTE и оконными функциями

WITH ranked_sales AS (
  SELECT 
    employee_id,
    name,
    month,
    sales_amount,
    ROW_NUMBER() OVER (PARTITION BY employee_id ORDER BY month) as month_rank,
    LAG(sales_amount) OVER (PARTITION BY employee_id ORDER BY month) as prev_month_sales
  FROM employee_sales
),
sales_growth AS (
  SELECT 
    employee_id,
    name,
    month,
    sales_amount,
    prev_month_sales,
    CASE 
      WHEN prev_month_sales IS NULL THEN 0
      ELSE ROUND((sales_amount - prev_month_sales) / prev_month_sales * 100, 2)
    END as growth_percent
  FROM ranked_sales
)
SELECT 
  employee_id,
  name,
  month,
  sales_amount,
  growth_percent
FROM sales_growth
WHERE growth_percent > 10
ORDER BY month, growth_percent DESC;

CTE vs Подзапросы

Подзапрос (Subquery)

SELECT department, AVG(salary)
FROM (
  SELECT department, salary
  FROM employees
  WHERE hire_date > '2020-01-01'
) AS subq
GROUP BY department;

Минусы:

  • Если подзапрос используется несколько раз, код повторяется
  • Сложнее читать при вложенных подзапросах
  • Не может быть рекурсивным

CTE

WITH recent_emp AS (
  SELECT department, salary
  FROM employees
  WHERE hire_date > '2020-01-01'
)
SELECT department, AVG(salary)
FROM recent_emp
GROUP BY department;

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

  • Более читаемый код
  • Можно повторно использовать в одном запросе
  • Поддерживает рекурсию
  • Легче отлаживать и оптимизировать

Практический пример для Data Science

WITH user_activity AS (
  SELECT 
    user_id,
    DATE(event_date) as event_day,
    COUNT(*) as events_count,
    SUM(spent) as daily_spent
  FROM events
  GROUP BY user_id, DATE(event_date)
),
user_stats AS (
  SELECT 
    user_id,
    COUNT(DISTINCT event_day) as active_days,
    AVG(events_count) as avg_events_per_day,
    AVG(daily_spent) as avg_daily_spent,
    SUM(daily_spent) as total_spent
  FROM user_activity
  GROUP BY user_id
)
SELECT 
  user_id,
  active_days,
  ROUND(avg_events_per_day, 2) as avg_events,
  ROUND(avg_daily_spent, 2) as avg_spent,
  ROUND(total_spent, 2) as total_spent,
  CASE 
    WHEN total_spent > 1000 THEN 'VIP'
    WHEN total_spent > 500 THEN 'Premium'
    ELSE 'Regular'
  END as user_segment
FROM user_stats
WHERE active_days >= 7
ORDER BY total_spent DESC;

Когда использовать CTE

  • Упрощение читаемости — разбиение сложного запроса
  • Повторное использование — один CTE в нескольких местах
  • Иерархические данные — рекурсивные запросы
  • Поэтапные расчёты — вычисления с промежуточными шагами
  • Подготовка данных — фильтрация и трансформация перед основным запросом

CTE — один из самых полезных инструментов для написания понятного и maintainable SQL кода в аналитике данных.

Что такое CTE (Common Table Expression) в SQL? | PrepBro