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

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

Подзапрос в WHERE позволяет фильтровать строки по промежуточному результату другого запроса. В этом уроке вы разберете, когда использовать операторы сравнения, IN, NOT IN, EXISTS, NOT EXISTS, ANY и ALL, и научитесь выбирать безопасный вариант для практических задач.

Подзапросы в предложении WHERE

В прошлом уроке мы познакомились с общей идеей подзапросов. Теперь сфокусируемся на самом частом сценарии: фильтрации в WHERE, когда внешний запрос должен опираться на динамически рассчитанные значения.

На практике это нужно постоянно: от поиска клиентов без платежей до сравнения строки с результатами выборки по группе.

Схема подзапросов в WHERE с операторами IN, EXISTS, ANY и ALL

Скалярные подзапросы и операторы сравнения

Если подзапрос возвращает ровно одно значение, его называют скалярным. В этом случае можно использовать обычные операторы =, <>, >, >=, <, <=.

Сценарий: найти актеров с тем же именем, что и у актера с actor_id = 10.

SELECT
    first_name,
    last_name
FROM
    actor
WHERE
    first_name = (
        SELECT first_name
        FROM actor
        WHERE actor_id = 10
    )
    AND actor_id <> 10;

Примечание: если внутренний запрос вернет несколько строк, такой запрос завершится ошибкой.


Многострочные подзапросы: IN и NOT IN

Когда подзапрос возвращает список значений (один столбец, много строк), используйте IN.

Сценарий: найти фильмы категории Action.

SELECT
    f.title
FROM
    film AS f
WHERE
    f.film_id IN (
        SELECT
            fc.film_id
        FROM
            film_category AS fc
        WHERE
            fc.category_id = (
                SELECT
                    c.category_id
                FROM
                    category AS c
                WHERE
                    c.name = 'Action'
            )
    );

Результат: вы получите все фильмы, которые связаны с категорией Action через таблицу film_category.

NOT IN делает обратную фильтрацию, но помните важный нюанс: если в результате подзапроса есть NULL, условие может дать неожиданно пустой результат. В таких случаях часто надежнее использовать NOT EXISTS.


Проверка существования: EXISTS и NOT EXISTS

EXISTS проверяет факт наличия хотя бы одной строки в подзапросе. База данных может остановиться на первом найденном совпадении, поэтому этот подход часто эффективен на больших таблицах.

EXISTS

Сценарий: найти клиентов, у которых есть хотя бы один платеж.

SELECT
    c.first_name,
    c.last_name
FROM
    customer AS c
WHERE
    EXISTS (
        SELECT
            1
        FROM
            payment AS p
        WHERE
            p.customer_id = c.customer_id
    );

Примечание: в EXISTS обычно используют SELECT 1, потому что важен факт наличия строки, а не ее содержимое.

NOT EXISTS

Сценарий: найти клиентов, которые не сделали ни одного платежа.

SELECT
    c.first_name,
    c.last_name
FROM
    customer AS c
WHERE
    NOT EXISTS (
        SELECT
            1
        FROM
            payment AS p
        WHERE
            p.customer_id = c.customer_id
    );

Результат: в выборку попадут только клиенты, для которых в payment нет ни одной связанной записи.


Сравнение с набором: ANY и ALL

  • ANY: условие истинно, если оно выполняется хотя бы для одного значения из подзапроса.
  • ALL: условие истинно, только если оно выполняется для всех значений из подзапроса.

Сценарий: сравнить длительность фильма с длительностями фильмов в категории Comedy.

SELECT
    f.title,
    f.length
FROM
    film AS f
WHERE
    f.length > ANY (
        SELECT
            f2.length
        FROM
            film AS f2
        INNER JOIN film_category AS fc ON f2.film_id = fc.film_id
        INNER JOIN category AS c ON fc.category_id = c.category_id
        WHERE
            c.name = 'Comedy'
    );

Результат: фильм попадет в выборку, если он длиннее хотя бы одного фильма из Comedy.

SELECT
    f.title,
    f.length
FROM
    film AS f
WHERE
    f.length > ALL (
        SELECT
            f2.length
        FROM
            film AS f2
        INNER JOIN film_category AS fc ON f2.film_id = fc.film_id
        INNER JOIN category AS c ON fc.category_id = c.category_id
        WHERE
            c.name = 'Comedy'
    );

Результат: фильм попадет в выборку, только если он длиннее каждого фильма из Comedy.


На что обращать внимание в реальных запросах

  • Для одного значения используйте скалярный подзапрос с оператором сравнения.
  • Для списка значений используйте IN или EXISTS в зависимости от задачи.
  • Для поиска отсутствующих связей предпочитайте NOT EXISTS, особенно если в данных возможны NULL.
  • Проверяйте, может ли подзапрос вернуть больше строк, чем ожидается.

Ключевые выводы этого урока:

  • WHERE с подзапросом помогает строить динамическую фильтрацию без ручной подстановки значений.
  • IN удобно использовать для проверки принадлежности значению из списка.
  • EXISTS и NOT EXISTS хорошо подходят для проверки наличия и отсутствия связанных строк.
  • ANY и ALL позволяют гибко сравнивать строку с целым набором значений.
  • Правильный выбор оператора делает запросы точнее, понятнее и безопаснее.

Часто задаваемые вопросы

Что лучше использовать для поиска отсутствующих связей: NOT IN или NOT EXISTS?

В большинстве практических задач безопаснее использовать NOT EXISTS. Если подзапрос в NOT IN возвращает NULL, результат может стать неожиданным и отфильтровать больше строк, чем нужно.

Почему в EXISTS обычно пишут SELECT 1, а не SELECT *?

Потому что EXISTS проверяет только факт наличия строк. Содержимое выбираемых столбцов не используется, поэтому SELECT 1 является стандартной и понятной формой записи.

Когда использовать ANY, а когда ALL?

Используйте ANY, когда условие должно выполниться хотя бы для одного значения из подзапроса. Используйте ALL, когда условие должно быть истинно для каждого значения из набора.

Вопросы для собеседования

Чем отличается IN от EXISTS в SQL?

IN сравнивает значение со списком результатов подзапроса, а EXISTS проверяет наличие хотя бы одной подходящей строки. На больших данных EXISTS часто работает эффективнее в коррелированных сценариях, потому что может завершиться после первого совпадения.

Как вы объясните разницу между скалярным и многострочным подзапросом?

Скалярный подзапрос возвращает одно значение и используется с операторами вроде = или >. Многострочный подзапрос возвращает набор значений и обычно применяется с IN, ANY или ALL.

Почему запрос с оператором = и подзапросом может завершиться ошибкой?

Оператор = ожидает одно значение справа. Если подзапрос вернет больше одной строки, SQL-движок не сможет выполнить однозначное сравнение и вернет ошибку.

В следующем уроке мы рассмотрим коррелированные подзапросы и разберем, как они выполняются построчно во внешнем запросе.