Время чтения: ~7 минут
Подзапрос в SQL - это способ разбить одну задачу на несколько шагов внутри одного оператора. В этом уроке вы разберете базовую идею подзапросов, их типы и увидите, как использовать их в WHERE и FROM на примерах базы Sakila.
Введение в подзапросы: вложенные запросы и Inline View
В предыдущих уроках вы уже научились получать данные из таблиц и объединять их через JOIN. Но в реальных задачах часто нужно сначала вычислить промежуточный результат, а затем использовать его в основном запросе.
Именно для этого применяются подзапросы: они помогают сделать запрос пошаговым и более логичным.
Что такое подзапрос
Подзапрос (Subquery) - это оператор SELECT, вложенный в другой SQL-запрос. Запрос, внутри которого находится подзапрос, называют внешним.
Подзапрос всегда записывается в круглых скобках ().
Как подзапрос выполняется
В большинстве случаев СУБД сначала выполняет внутренний запрос. Затем его результат передается во внешний запрос, который завершает фильтрацию или построение итогового набора данных.
-- Концептуальный пример
SELECT column_name
FROM table_name
WHERE column_name = (SELECT value FROM another_table);
Примечание: сначала вычисляется выражение в скобках, затем применяется условие внешнего WHERE.
Основные типы подзапросов
- Скалярный подзапрос: возвращает одно значение (одна строка, один столбец).
- Многострочный подзапрос: возвращает список значений (один столбец, много строк).
- Табличный подзапрос (Inline View): возвращает набор строк и столбцов, который используется как временная таблица.
Подзапрос в SELECT
Подзапрос можно размещать прямо в списке SELECT, когда рядом с основными столбцами нужно показать дополнительную метрику без изменения детализации результата. Это особенно полезно там, где JOIN легко приводит к дублированию строк или к громоздкой агрегации.
Сценарий 1: показать последний платеж каждого клиента (дата и сумма).
SELECT
c.customer_id,
c.first_name,
c.last_name,
(
SELECT p.payment_date
FROM payment AS p
WHERE p.customer_id = c.customer_id
ORDER BY p.payment_date DESC
LIMIT 1
) AS last_payment_date,
(
SELECT p.amount
FROM payment AS p
WHERE p.customer_id = c.customer_id
ORDER BY p.payment_date DESC
LIMIT 1
) AS last_payment_amount
FROM
customer AS c
LIMIT 10;
Результат: для каждого клиента вы получаете именно один «последний» платеж. Через JOIN это обычно сложнее: нужно сначала вычислять максимальную дату, затем заново соединять таблицы и разруливать совпадения.
Сценарий 2: вывести платеж и его отклонение от среднего платежа по клиенту.
SELECT
p.payment_id,
p.customer_id,
p.amount,
(
SELECT AVG(p2.amount)
FROM payment AS p2
WHERE p2.customer_id = p.customer_id
) AS customer_avg_amount,
p.amount - (
SELECT AVG(p3.amount)
FROM payment AS p3
WHERE p3.customer_id = p.customer_id
) AS delta_from_customer_avg
FROM
payment AS p
LIMIT 15;
Результат: каждая строка платежа сохраняет исходную детализацию и получает персональный бенчмарк клиента. Подход с JOIN потребовал бы отдельной агрегированной таблицы и дополнительного соединения.
Подзапрос в WHERE
Самый частый сценарий - подзапрос внутри WHERE, когда для фильтрации нужно динамически вычисленное значение.
Сценарий: найти фильмы, у которых replacement_cost выше среднего по всем фильмам.
SELECT
title,
replacement_cost
FROM
film
WHERE
replacement_cost > (
SELECT AVG(replacement_cost)
FROM film
);
Результат: внутренний запрос считает среднее значение, внешний возвращает фильмы выше этого среднего.
Подзапрос в FROM (Inline View)
Если подзапрос размещен в FROM, он работает как временная таблица внутри текущего запроса. Такой вариант называют Inline View.
Важно: у Inline View должен быть псевдоним.
Сценарий: получить активных клиентов и их платежи.
SELECT
active_cust.first_name,
p.amount
FROM
(
SELECT
customer_id,
first_name
FROM
customer
WHERE
active = 1
) AS active_cust
INNER JOIN
payment AS p ON active_cust.customer_id = p.customer_id;
Результат: внешний запрос соединяет результат подзапроса active_cust с таблицей payment.
Когда подзапрос удобнее, чем JOIN
- Для пошаговой логики, когда сначала нужно получить одно промежуточное значение.
- Для фильтрации по агрегатам (
AVG,MAX,MIN) без усложнения основного запроса. - Для задач на поиск отсутствующих связей, где часто используют
NOT INилиNOT EXISTS.
Ключевые выводы этого урока:
- Подзапрос - это
SELECT, вложенный в другой SQL-запрос. - Внутренний запрос обычно выполняется раньше внешнего.
- Подзапрос может возвращать одно значение, список значений или целый табличный набор.
- Подзапрос в
FROMназывается Inline View и требует псевдонима. - Подзапросы помогают писать более понятные и гибкие SQL-запросы.
Часто задаваемые вопросы
В чем разница между подзапросом в WHERE и подзапросом в FROM?
Подзапрос в WHERE обычно используется для фильтрации строк внешнего запроса. Подзапрос в FROM формирует временный набор данных (Inline View), который можно далее соединять и обрабатывать.
Нужно ли всегда давать псевдоним подзапросу в FROM?
Да, в большинстве СУБД подзапрос в FROM обязательно должен иметь псевдоним. Без него запрос не выполнится.
Когда лучше выбрать NOT EXISTS вместо NOT IN?
Если подзапрос может вернуть NULL, NOT IN может дать неожиданный результат. В таких случаях NOT EXISTS обычно надежнее.
Вопросы для собеседования
Что такое подзапрос и как он выполняется?
Подзапрос - это вложенный SELECT внутри внешнего SQL-запроса. Обычно сначала выполняется внутренний запрос, а затем внешний использует его результат для фильтрации или построения итоговой выборки.
Чем отличается скалярный подзапрос от многострочного?
Скалярный подзапрос возвращает одно значение и обычно работает с = или >. Многострочный подзапрос возвращает набор значений и используется с IN, ANY или ALL.
Что такое Inline View в SQL?
Inline View - это подзапрос в FROM, который ведет себя как временная таблица в рамках одного запроса. Ему обязательно назначают псевдоним, чтобы обращаться к его столбцам.
В следующем уроке мы подробно рассмотрим подзапросы в WHERE и разберем операторы IN, EXISTS, ANY и ALL.