Время чтения: ~8 минут
Подзапрос в WHERE позволяет фильтровать строки по промежуточному результату другого запроса. В этом уроке вы разберете, когда использовать операторы сравнения, IN, NOT IN, EXISTS, NOT EXISTS, ANY и ALL, и научитесь выбирать безопасный вариант для практических задач.
Подзапросы в предложении WHERE
В прошлом уроке мы познакомились с общей идеей подзапросов. Теперь сфокусируемся на самом частом сценарии: фильтрации в WHERE, когда внешний запрос должен опираться на динамически рассчитанные значения.
На практике это нужно постоянно: от поиска клиентов без платежей до сравнения строки с результатами выборки по группе.
Скалярные подзапросы и операторы сравнения
Если подзапрос возвращает ровно одно значение, его называют скалярным. В этом случае можно использовать обычные операторы =, <>, >, >=, <, <=.
Сценарий: найти актеров с тем же именем, что и у актера с 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-движок не сможет выполнить однозначное сравнение и вернет ошибку.
В следующем уроке мы рассмотрим коррелированные подзапросы и разберем, как они выполняются построчно во внешнем запросе.