Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
CTE (Common Table Expression) — Примеры и Применение
CTE — это временная именованная таблица, которая существует только в рамках одного запроса. Пишется с помощью оператора WITH.
Базовый пример: простой CTE
-- CTE содержит список пользователей с большим балансом
WITH high_balance_users AS (
SELECT id, name, balance
FROM users
WHERE balance > 1000
)
SELECT * FROM high_balance_users;
Это эквивалентно:
SELECT id, name, balance
FROM users
WHERE balance > 1000;
Но CTE улучшает читаемость при сложных запросах.
Пример 1: Используем CTE несколько раз
-- CTE используется дважды
WITH rich_users AS (
SELECT id, name, balance
FROM users
WHERE balance > 5000
),
rich_users_orders AS (
SELECT o.id, o.user_id, o.amount, ru.name
FROM orders o
JOIN rich_users ru ON o.user_id = ru.id
)
SELECT
name,
COUNT(*) as order_count,
SUM(amount) as total_spent
FROM rich_users_orders
GROUP BY name
ORDER BY total_spent DESC;
Что происходит:
rich_users— находит пользователей с балансом > 5000rich_users_orders— соединяет заказы этих пользователей- Считаем статистику по каждому пользователю
Пример 2: Рекурсивный CTE (иерархия)
-- Структура: менеджеры и их подчинённые
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(100),
manager_id INT REFERENCES employees(id)
);
INSERT INTO employees VALUES
(1, 'CEO Alice', NULL),
(2, 'Manager Bob', 1),
(3, 'Manager Carol', 1),
(4, 'Developer Dave', 2),
(5, 'Developer Eve', 2),
(6, 'Designer Frank', 3);
-- Рекурсивный CTE: показать всю иерархию
WITH RECURSIVE employee_hierarchy AS (
-- Базовый случай: начиная с CEO
SELECT id, name, manager_id, 0 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
)
SELECT
REPEAT(' ', level) || name as employee_tree,
level
FROM employee_hierarchy
ORDER BY level, name;
-- Вывод:
-- CEO Alice
-- Manager Bob
-- Developer Dave
-- Developer Eve
-- Manager Carol
-- Designer Frank
Пример 3: CTE для ранжирования (ROW_NUMBER)
-- Найти топ-3 товаров по продажам в каждой категории
WITH product_sales AS (
SELECT
p.name,
c.category,
SUM(o.quantity) as total_qty,
ROW_NUMBER() OVER (PARTITION BY c.category ORDER BY SUM(o.quantity) DESC) as rank
FROM products p
JOIN categories c ON p.category_id = c.id
LEFT JOIN order_items o ON p.id = o.product_id
GROUP BY p.id, p.name, c.category
)
SELECT name, category, total_qty
FROM product_sales
WHERE rank <= 3
ORDER BY category, rank;
-- Вывод:
-- Electronics: iPhone (1000), Samsung TV (800), iPad (600)
-- Clothing: T-Shirt (500), Jeans (400), Shirt (350)
Пример 4: CTE для удаления дубликатов
-- Таблица с потенциальными дубликатами
CREATE TABLE user_emails (
id INT,
email VARCHAR(100)
);
INSERT INTO user_emails VALUES
(1, 'alice@example.com'),
(2, 'alice@example.com'),
(3, 'bob@example.com'),
(4, 'bob@example.com'),
(5, 'carol@example.com');
-- Найти первое вхождение каждого email
WITH ranked_emails AS (
SELECT
id,
email,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) as rn
FROM user_emails
)
SELECT id, email
FROM ranked_emails
WHERE rn = 1;
-- Вывод:
-- id=1, alice@example.com
-- id=3, bob@example.com
-- id=5, carol@example.com
Пример 5: CTE с вычисляемыми значениями
-- Анализ продаж по месяцам
WITH monthly_sales AS (
SELECT
DATE_TRUNC('month', created_at)::DATE as month,
SUM(amount) as total_sales,
COUNT(*) as order_count
FROM orders
WHERE created_at >= NOW() - INTERVAL '1 year'
GROUP BY DATE_TRUNC('month', created_at)
),
sales_with_stats AS (
SELECT
month,
total_sales,
order_count,
AVG(total_sales) OVER () as avg_monthly_sales,
LAG(total_sales) OVER (ORDER BY month) as prev_month_sales
FROM monthly_sales
)
SELECT
month,
total_sales,
order_count,
ROUND((total_sales / avg_monthly_sales - 1) * 100, 2) as vs_avg_percent,
ROUND((total_sales / prev_month_sales - 1) * 100, 2) as vs_prev_month_percent
FROM sales_with_stats
ORDER BY month DESC;
Пример 6: Практический CTE на Python с SQLAlchemy
from sqlalchemy import text, create_engine
from sqlalchemy.orm import Session
engine = create_engine('postgresql://user:pass@localhost/dbname')
def get_top_customers():
"""CTE запрос через SQLAlchemy"""
query = text("""
WITH customer_totals AS (
SELECT
c.id,
c.name,
COUNT(o.id) as order_count,
SUM(o.amount) as total_spent
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
)
SELECT
id,
name,
order_count,
total_spent,
CASE
WHEN total_spent > 10000 THEN 'VIP'
WHEN total_spent > 5000 THEN 'Premium'
ELSE 'Regular'
END as customer_tier
FROM customer_totals
WHERE order_count > 0
ORDER BY total_spent DESC
LIMIT 10
""")
with Session(engine) as session:
results = session.execute(query).fetchall()
return results
# Использование
for row in get_top_customers():
print(f"{row.name}: {row.total_spent} ({row.customer_tier})")
Пример 7: Множественные CTE (цепочка)
-- Сложный анализ с несколькими CTE
WITH user_purchases AS (
SELECT user_id, COUNT(*) as purchase_count, SUM(amount) as total_amount
FROM purchases
GROUP BY user_id
),
user_scores AS (
SELECT
user_id,
purchase_count,
total_amount,
purchase_count * 10 + (total_amount / 100) as loyalty_score
FROM user_purchases
WHERE total_amount > 0
),
top_loyal_users AS (
SELECT
u.id,
u.name,
us.loyalty_score,
RANK() OVER (ORDER BY us.loyalty_score DESC) as loyalty_rank
FROM users u
JOIN user_scores us ON u.id = us.user_id
)
SELECT *
FROM top_loyal_users
WHERE loyalty_rank <= 20
ORDER BY loyalty_rank;
Преимущества CTE
-
Читаемость — большой запрос разбивается на логические части
WITH step1 AS (...), step2 AS (...), step3 AS (...) SELECT * FROM step3; -- Вся логика видна сверху -
Повторное использование — одну CTE можно использовать несколько раз
WITH active_users AS (...) SELECT COUNT(*) FROM active_users -- использование 1 UNION ALL SELECT SUM(balance) FROM active_users; -- использование 2 -
Рекурсивные запросы — возможны только через CTE
WITH RECURSIVE tree AS (...) SELECT * FROM tree; -
Улучшенная отладка — можно проверить каждый CTE отдельно
-- Проверяем промежуточный результат WITH intermediate AS (...) SELECT * FROM intermediate; -- Видим что там
Когда использовать CTE
- Сложные многошаговые запросы — разбей на CTE
- Рекурсивные данные (иерархии, графы) — нужен рекурсивный CTE
- Повторяющиеся подзапросы — одна CTE вместо дублирования
- Нужна читаемость — CTE лучше вложенных подзапросов
- Window functions — часто комбинируют с CTE для ранжирования/статистики