0Pricing
SQL Interview Prep · Урок

Коррелированные EXISTS и NOT EXISTS

Надёжная альтернатива антиобъединению, корректно обрабатывающая NULL.

«Коррелированные EXISTS и NOT EXISTS» — бесплатный урок SQL Interview Prep на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения SQL Interview Prep, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс SQL Interview Prep содержит 4 уроков всего.

EXISTS проверяет наличие

EXISTS принимает подзапрос и возвращает TRUE, как только этот подзапрос выдаёт хотя бы одну строку, а в противном случае — FALSE. Он никогда не возвращает сами строки.

С коррелированным подзапросом внутри EXISTS превращается в проверку наличия для каждой внешней строки: «существует ли подходящая строка для этой внешней строки?»

Поскольку проверка прекращается при первом совпадении, количество совпавших строк не имеет значения. На собеседованиях часто проверяют именно это свойство.

Базовый коррелированный EXISTS

Найдите клиентов, которые разместили хотя бы один заказ. Внутренний запрос связан с внешним через o.customer_id = c.customer_id.

Для каждого клиента EXISTS спрашивает: есть ли для него хотя бы один заказ? Если да, клиент сохраняется в результате.

SELECT c.customer_id, c.name
FROM customers c
WHERE EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customer_id
);

Зачем использовать SELECT 1 внутри EXISTS

Внутри EXISTS вы увидите SELECT 1, SELECT * или SELECT NULL. Все эти варианты эквивалентны.

EXISTS проверяет только наличие возвращённых строк, а не их содержимое, поэтому выбираемые столбцы не имеют значения. Оптимизатор их игнорирует.

SELECT 1 — распространённое соглашение, показывающее намерение: «Меня интересует только наличие». Выберите один вариант и будьте последовательны; не позволяйте собеседующему решить, что список столбцов здесь важен.

NOT EXISTS находит отсутствующие строки

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

Это классическое антисоединение: клиенты без заказов, товары, которые ни разу не продавались, и студенты без отправленных работ.

SELECT c.customer_id, c.name
FROM customers c
WHERE NOT EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customer_id
);

Ловушка NULL в NOT IN

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

Это происходит потому, что сравнение с NULL даёт UNKNOWN, а NOT IN требует, чтобы каждое сравнение было ложным. Одно значение UNKNOWN портит всё условие.

NOT EXISTS не подвержен этой проблеме: он просто проверяет наличие строк и безопасно обрабатывает NULL.

-- Risky: returns nothing if any o.customer_id is NULL
SELECT c.customer_id FROM customers c
WHERE c.customer_id NOT IN (SELECT o.customer_id FROM orders o);

-- Safe: NULLs do not break it
SELECT c.customer_id FROM customers c
WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);

Почему NOT EXISTS безопасен при NULL

Причина заключается в логике сопоставления. NOT EXISTS проверяет, существует ли внутренняя строка, удовлетворяющая условию o.customer_id = c.customer_id.

Строка, в которой o.customer_id равен NULL, никогда не удовлетворяет этому равенству (NULL = любое значение даёт UNKNOWN, а не TRUE), поэтому она просто не считается совпадением. Проверка наличия остаётся корректной.

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

EXISTS с дополнительными условиями

Коррелированный подзапрос может содержать дополнительные предикаты. Найдите клиентов, разместивших хотя бы один заказ на сумму больше 1000.

Дополнительное условие находится внутри подзапроса EXISTS и применяется отдельно для каждого клиента.

SELECT c.customer_id, c.name
FROM customers c
WHERE EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customer_id
      AND o.amount > 1000
);

Производительность: поведение с досрочным завершением

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

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

Сравните это с коррелированным COUNT(*) > 0, который вынужденно подсчитывает все совпадения. Предпочитайте EXISTS, когда Вам нужен только ответ «да» или «нет».

EXISTS и COUNT для проверки наличия

Кандидаты иногда пишут коррелированный подсчёт, чтобы проверить наличие. Это работает, но приводит к лишним затратам.

В варианте с COUNT подсчитывается каждый подходящий заказ, а EXISTS завершается после первого. Для проверки одного лишь существования EXISTS явно выражает намерение и позволяет оптимизатору завершить проверку досрочно.

-- Works but counts everything
SELECT c.customer_id FROM customers c
WHERE (SELECT COUNT(*) FROM orders o
       WHERE o.customer_id = c.customer_id) > 0;

-- Better: stops at first match
SELECT c.customer_id FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o
              WHERE o.customer_id = c.customer_id);

Разобранный пример: товары, которые никогда не заказывали

Классический вопрос на собеседовании об антисоединении: выведите товары, которые никогда не заказывали. NOT EXISTS почти дословно соответствует требованию на английском языке.

Для каждого товара проверьте, ссылается ли на него какая-либо строка заказа; оставьте только товары без таких строк.

SELECT p.product_id, p.name
FROM products p
WHERE NOT EXISTS (
    SELECT 1
    FROM order_items oi
    WHERE oi.product_id = p.product_id
);

EXISTS внутри NOT EXISTS для запросов в стиле деления

Вложение EXISTS внутрь NOT EXISTS выражает реляционное деление: «найдите строки, соответствующие ALL элементам множества». Классический вопрос звучит так: «найдите клиентов, которые заказали каждый товар из категории».

Логика такова: оставьте клиента, для которого не существует ни одного товара, который он не заказал. Это двойное отрицание — отличительный признак запроса с делением, и интервьюеры используют его, чтобы проверить глубокое понимание EXISTS.

SELECT c.customer_id
FROM customers c
WHERE NOT EXISTS (
    SELECT 1 FROM products p
    WHERE p.category = 'Coffee'
      AND NOT EXISTS (
          SELECT 1 FROM order_items oi
          JOIN orders o ON o.order_id = oi.order_id
          WHERE oi.product_id = p.product_id
            AND o.customer_id = c.customer_id
      )
);

Быстрая проверка

Выберите самый безопасный способ найти клиентов без заказов.

Итоги: коррелированные EXISTS и NOT EXISTS

Основные выводы:

  • EXISTS — это проверка наличия для каждой строки, которая завершается при первом совпадении; выбор столбца внутри не имеет значения (используйте SELECT 1).
  • NOT EXISTS — это безопасное относительно NULL антисоединение для поиска строк без совпадений.
  • NOT IN со значением NULL в списке возвращает ничего; предпочитайте NOT EXISTS.
  • Для проверки существования EXISTS лучше коррелированного COUNT(*) > 0, потому что он прекращает работу раньше.

Упомяните ловушку с NULL в NOT IN без подсказки: это надёжный признак зрелого владения SQL.

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

Урок «Коррелированные EXISTS и NOT EXISTS» бесплатный?

Да — полный текст урока «Коррелированные EXISTS и NOT EXISTS» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс SQL Interview Prep, подпишись на CoddyKit PRO. Курс SQL Interview Prep содержит 4 уроков всего.

Чему я научусь в уроке «Коррелированные EXISTS и NOT EXISTS»?

Надёжная альтернатива антиобъединению, корректно обрабатывающая NULL. Ты практикуешь SQL Interview Prep с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать SQL Interview Prep?

Предыдущий опыт не требуется. SQL Interview Prep на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Коррелированные EXISTS и NOT EXISTS»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке SQL Interview Prep?

Да. Каждый урок SQL Interview Prep включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Анатомия коррелированного подзапроса
  2. Агрегаты по группам без GROUP BY
  3. Коррелированные EXISTS и NOT EXISTS
  4. Переписывание коррелированных подзапросов через JOIN
← Назад к SQL Interview Prep