Подзапросы в SQL
Подзапрос — это SELECT внутри другого SELECT. Позволяет решать задачи в несколько шагов: сначала вычислить что-то, потом использовать результат. Часто встречается на SQL-собеседованиях как способ проверить понимание логики.
Содержание
Скалярный подзапрос
Возвращает ровно одно значение (одну строку, одну колонку). Можно использовать в SELECT, WHERE, HAVING.
-- Заказы дороже средней суммы
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.
-- Пользователи, которые купили товар из категории '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 для больших таблиц).
-- Пользователи, сделавшие хотя бы один заказ
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 создаёт временную таблицу. Это часто более читаемая альтернатива оконным функциям для простых задач.
-- Топ-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 удобен для «обогащения» строк одним значением, но медленный.
На собеседовании — покажи оба варианта и объясни разницу.
Закрепи знания на практике
Решай реальные задачи с собеседований прямо в браузере — без установки.
Решить задачи на подзапросы →