SQL EXISTS: фильтрация по связанным записям

SQL EXISTS возвращает true, когда коррелированный подзапрос находит хотя бы одну совпадающую строку. Используйте его, чтобы фильтровать родительские строки по наличию (или отсутствию) связанных дочерних строк без дублирования результатов, как это бывает с JOIN. Планировщики запросов часто выполняют EXISTS как semi-join, поэтому могут остановить работу подзапроса после первого совпадения.

Пример SQL EXISTS Для Фильтрации По Наличию Строк

Вывод:

Результат появится здесь...

Вывод:

+------+
| name |
+------+
| Alma |
| Cara |
+------+
2 row(s)

Почему EXISTS Возвращает Alma И Cara

  1. Создаются две таблицы: customers хранит трех клиентов, orders хранит три заказа, связанные через customer_id.
  2. WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id) запускает подзапрос один раз для каждой строки клиента. Если существует хотя бы один подходящий заказ, клиент проходит фильтр.
  3. SELECT 1 внутри подзапроса - это соглашение: база данных игнорирует список SELECT и проверяет только то, что подходит хотя бы одна строка.
  4. У Ben нет заказов, поэтому его подзапрос возвращает ноль строк, и EXISTS дает false. У Alma и Cara есть как минимум один заказ, поэтому они попадают в результат.

В отличие от INNER JOIN, EXISTS никогда не дублирует внешнюю строку. У Alma два заказа, но она появляется один раз, потому что EXISTS спрашивает только “есть ли совпадение?”, а не “сколько их?”.

NOT EXISTS vs NOT IN: Ловушка NULL

SQL NOT EXISTS (пишется как NOT EXISTS) - безопасный выбор для anti-join по nullable-столбцам. Когда подзапрос возвращает значения NULL, NOT IN дает неожиданный результат, потому что любое сравнение с NULL вычисляется в unknown:

-- Предположим, в orders есть строка с NULL customer_id
-- NOT IN молча отбрасывает ВСЕ строки
SELECT name FROM customers
WHERE id NOT IN (SELECT customer_id FROM orders);

-- NOT EXISTS по-прежнему работает корректно
SELECT name FROM customers c
WHERE NOT EXISTS (
  SELECT 1 FROM orders o WHERE o.customer_id = c.id
);

NOT IN трактует id NOT IN (1, 3, NULL) как id <> 1 AND id <> 3 AND id <> NULL. Последнее сравнение всегда unknown, поэтому весь предикат никогда не становится true, и запрос возвращает ноль строк. NOT EXISTS избегает этой проблемы, потому что проверяет существование строк, а не равенство значений.

Частые Ошибки С SQL EXISTS

Отсутствует условие корреляции

Если подзапрос не связан с внешней строкой, EXISTS становится константной проверкой: если существует хоть один заказ, подойдут все клиенты.

-- Неправильно: нет связи с внешней строкой
SELECT c.name FROM customers c
WHERE EXISTS (SELECT 1 FROM orders);

-- Правильно: корреляция по customer_id
SELECT c.name FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);

Использование JOIN, когда важен только факт существования

INNER JOIN возвращает по одной строке на каждое совпадение, поэтому клиенты с несколькими заказами появляются несколько раз.

SELECT c.name
FROM customers c
JOIN orders o ON o.customer_id = c.id;

Используйте EXISTS, если вам не нужны столбцы из orders.

EXISTS vs IN vs JOIN

ПодходКогда использовать
EXISTS (subquery)Проверка существования связанных строк; подзапрос коррелированный
NOT EXISTS (subquery)Anti-join, особенно с nullable внешними ключами
IN (subquery)Сравнение с небольшим набором значений без NULL
INNER JOINНужны столбцы из обеих таблиц в результате
LEFT JOIN ... IS NULLАльтернатива anti-join; иногда ее предпочитает планировщик

Правило: используйте EXISTS для вопросов “существует ли связанная строка?”. Переходите к JOIN, когда нужны данные с обеих сторон.

Заметки О Производительности

EXISTS часто планируется как semi-join. Индекс по столбцу корреляции (orders.customer_id в этом примере) позволяет движку искать первое совпадение для каждого клиента вместо полного сканирования orders. NOT EXISTS получает ту же выгоду от индекса. Если EXISTS работает медленно, проверьте план выполнения и наличие индекса на столбце корреляции.

FAQ

EXISTS Останавливается После Первого Совпадения?

Логически результат становится true, как только существует одна подходящая строка. Многие базы данных реализуют это как semi-join с остановкой после первого совпадения, но конкретная стратегия зависит от планировщика запросов. Проверьте план выполнения, если нужно подтвердить поведение в вашей СУБД.

Можно Ли Использовать EXISTS В UPDATE И DELETE?

Да. UPDATE ... WHERE EXISTS (subquery) и DELETE ... WHERE EXISTS (subquery) ограничивают, какие строки будут изменены или удалены, на основе наличия связанных строк. Используйте тот же предикат корреляции, что и в SELECT, чтобы каждая целевая строка проверяла только свои связанные строки.

В Чем Разница Между EXISTS И IN В SQL?

EXISTS проверяет, возвращает ли (обычно коррелированный) подзапрос хоть одну строку. IN сравнивает значение с набором результатов. EXISTS естественно работает с корреляцией к внешнему запросу, и многие движки могут остановиться после первого совпадения; IN подходит для небольших литеральных списков или некоррелированных проверок принадлежности. Для anti-join по nullable-столбцам выбирайте NOT EXISTS вместо NOT IN.