SQLLab
🔍

Подзапросы в SQL

Подзапрос — это SELECT внутри другого SELECT. Позволяет решать задачи в несколько шагов: сначала вычислить что-то, потом использовать результат. Часто встречается на SQL-собеседованиях как способ проверить понимание логики.

Скалярный подзапрос

Возвращает ровно одно значение (одну строку, одну колонку). Можно использовать в SELECT, WHERE, HAVING.

Скалярный подзапрос в WHERE и SELECT
-- Заказы дороже средней суммы
SELECT *
FROM orders
WHERE amount > (SELECT AVG(amount) FROM orders);

-- В SELECT: разница от среднего
SELECT
  order_id,
  amount,
  amount - (SELECT AVG(amount) FROM orders) AS diff_from_avg
FROM orders;

IN и NOT IN

Подзапрос возвращает список значений, IN проверяет принадлежность к этому списку.

Осторожно с NOT IN: если подзапрос вернёт хоть один NULL — NOT IN вернёт 0 строк. В таких случаях лучше использовать NOT EXISTS.

IN vs NOT EXISTS
-- Пользователи, которые купили товар из категории 'Electronics'
SELECT DISTINCT user_id
FROM orders
WHERE product_id IN (
  SELECT id FROM products WHERE category = 'Electronics'
);

-- Безопасная альтернатива NOT IN:
SELECT u.id FROM users u
WHERE NOT EXISTS (
  SELECT 1 FROM orders o WHERE o.user_id = u.id
);

EXISTS и NOT EXISTS

EXISTS проверяет, существует ли хоть одна строка в подзапросе. Как только находит — останавливается (эффективнее IN для больших таблиц).

EXISTS: проверка существования
-- Пользователи, сделавшие хотя бы один заказ
SELECT u.name
FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o WHERE o.user_id = u.id
);

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

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

Коррелированный подзапрос: последний заказ
-- Для каждого пользователя — его последний заказ
SELECT u.name,
  (SELECT MAX(o.created_at)
   FROM orders o
   WHERE o.user_id = u.id) AS last_order_date
FROM users u;

Подзапрос в FROM (производная таблица)

Подзапрос в FROM создаёт временную таблицу. Это часто более читаемая альтернатива оконным функциям для простых задач.

Подзапрос в FROM с ранжированием
-- Топ-1 пользователь по выручке в каждой категории
SELECT category, user_id, revenue
FROM (
  SELECT
    p.category,
    o.user_id,
    SUM(o.amount) AS revenue,
    RANK() OVER (PARTITION BY p.category ORDER BY SUM(o.amount) DESC) AS rnk
  FROM orders o
  JOIN products p ON o.product_id = p.id
  GROUP BY p.category, o.user_id
) t
WHERE rnk = 1;

Подзапрос vs JOIN: что выбрать

JOIN обычно быстрее — оптимизатор лучше с ним справляется. Подзапрос с EXISTS/IN удобнее когда нужна только проверка существования. Коррелированный подзапрос в SELECT удобен для «обогащения» строк одним значением, но медленный.

На собеседовании — покажи оба варианта и объясни разницу.

Закрепи знания на практике

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

Решить задачи на подзапросы