Перейти к содержанию

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

SQL подзапросы и CTE (WITH)

Когда одного SELECT не хватает, используем подзапросы и CTE — это делает запросы понятнее.


Подзапросы

1) Подзапрос в WHERE: IN

Покажем пользователей, у которых есть заказы:

SQL
SELECT id, name
FROM users
WHERE id IN (
  SELECT user_id
  FROM orders
);

2) EXISTS: существует ли хотя бы одна строка?

SQL
SELECT u.id, u.name
FROM users u
WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.user_id = u.id
);

EXISTS часто эффективен, потому что может остановиться на первой найденной строке.

3) Коррелированный подзапрос

Подзапрос, который ссылается на внешнюю строку:

SQL
SELECT
  u.id,
  u.name,
  (
    SELECT COUNT(*)
    FROM orders o
    WHERE o.user_id = u.id
  ) AS orders_count
FROM users u
ORDER BY u.id;

CTE (WITH): запрос "по шагам"

CTE (Common Table Expression) — это способ писать запрос как цепочку понятных шагов.

SQL
WITH order_totals AS (
  SELECT
    o.id AS order_id,
    o.user_id,
    SUM(oi.qty * oi.price) AS total
  FROM orders o
  JOIN order_items oi ON oi.order_id = o.id
  GROUP BY o.id, o.user_id
)
SELECT *
FROM order_totals
WHERE total >= 100
ORDER BY total DESC;

sql-05-cte-steps.svg Сначала считаем, потом фильтруем и сортируем.


Рекурсивный CTE (продвинутый уровень)

Нужен для деревьев: категории, оргструктура, комментарии.

SQL
WITH RECURSIVE tree AS (
  -- 1) базовый уровень (корень)
  SELECT id, parent_id, name, 0 AS depth
  FROM categories
  WHERE parent_id IS NULL

  UNION ALL

  -- 2) рекурсивный шаг (дети)
  SELECT c.id, c.parent_id, c.name, t.depth + 1
  FROM categories c
  JOIN tree t ON t.id = c.parent_id
)
SELECT *
FROM tree
ORDER BY depth, name;

Если вы новичок — просто запомните, что инструмент есть; он отлично работает для иерархий.