❤️ Спасибо всем, кто поддержал проект! В прошлом месяце вы задонатили почти $100 — это позволяет в этом месяце держать сервис бесплатным для всех. Очень надеюсь на вашу поддержку и в следующем месяце. Поддержать →
SQL код скопирован в буфер обмена
EN PT FR

Время чтения: ~7 минут

Подзапрос в SQL - это способ разбить одну задачу на несколько шагов внутри одного оператора. В этом уроке вы разберете базовую идею подзапросов, их типы и увидите, как использовать их в WHERE и FROM на примерах базы Sakila.

Введение в подзапросы: вложенные запросы и Inline View

В предыдущих уроках вы уже научились получать данные из таблиц и объединять их через JOIN. Но в реальных задачах часто нужно сначала вычислить промежуточный результат, а затем использовать его в основном запросе.

Именно для этого применяются подзапросы: они помогают сделать запрос пошаговым и более логичным.

Схема подзапросов SQL: выполнение внутреннего запроса, затем внешнего, и варианты использования в SELECT, WHERE и FROM

Что такое подзапрос

Подзапрос (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.