Сравните проверку наличия связанной строки через EXISTS и обычное соединение: почему первое не размножает строки внешнего запроса?
EXISTS проверяет только факт существования хотя бы одной подходящей строки во внутреннем запросе. Поэтому каждая строка внешнего запроса либо проходит фильтр один раз, либо не проходит вовсе; количество найденных совпадений внутри не влияет на результат. Обычное соединение формирует строку результата для каждой пары совпавших строк, поэтому одна внешняя строка может появиться многократно.
В реляционной алгебре проверка существования соответствует идее полусоединения: из внешнего отношения выбираются строки, для которых есть совпадение во внутреннем отношении, но сами внутренние строки в результат не добавляются. Такой механизм нужен для выражения условия «существует связанная запись» без получения данных и кратности из связанной таблицы.
Обычное соединение решает другую задачу: оно объединяет атрибуты совпавших строк. Если требуется только проверка наличия, соединение может быть избыточным и семантически опасным из-за размножения строк.
Допустим, нужно получить клиентов, у которых есть хотя бы один заказ. У клиента может быть десять заказов, но в отчёте он должен присутствовать один раз.
При использовании соединения результат будет содержать десять строк для такого клиента, если не принять дополнительные меры. Попытка исправить это с помощью DISTINCT иногда скрывает проблему, но может потребовать лишней сортировки или хеширования и не решает задачу выбора данных из заказа.
EXISTS вычисляется как логическое условие для текущей строки внешнего запроса. Как только СУБД устанавливает, что подходящая внутренняя строка существует, условие логически считается истинным; дополнительные совпадения не создают новые строки результата.
Значение SELECT 1 здесь условно: для EXISTS важен сам факт наличия строки, а не выбранное выражение. Даже если у клиента несколько заказов, внешняя строка c будет возвращена не более одного раза.
Оптимизатор обычно может преобразовать такой запрос в эффективный план полусоединения и остановить поиск после нахождения совпадения. Однако полагаться именно на физическую остановку поиска или на порядок вычисления условий нельзя: это детали плана выполнения, а не семантика SQL.
Обычное соединение следует выбирать, когда нужны столбцы связанной таблицы или отдельная строка результата для каждого совпадения. EXISTS предпочтителен, когда требуется только проверка наличия. Для проверки отсутствия используется NOT EXISTS; это обычно надёжнее, чем отрицательная проверка через NOT IN, если внутренний результат может содержать NULL.
В API нужно вернуть список активных клиентов, у которых есть хотя бы один оплаченный заказ. Вариант с соединением прост, но клиент с несколькими оплаченными заказами появляется несколько раз; устранение дублей через DISTINCT увеличивает стоимость запроса и маскирует его семантику.
Вариант с предварительным группированием заказов явно устраняет кратность, но добавляет отдельный этап агрегации. Вариант с EXISTS непосредственно выражает требование наличия, не материализует связанные строки в результирующем наборе и позволяет оптимизатору использовать индекс по идентификатору клиента и статусу заказа.
Выбирают EXISTS, а составной индекс проектируют под фактическое условие поиска. В результате клиент возвращается один раз, а поиск для него может завершиться после первого подходящего заказа.
Нет. EXISTS является предикатом со значением истина или ложь для каждой строки внешнего запроса. Число совпавших внутренних строк не передаётся в результирующий набор, поэтому внешняя строка не размножается.
Обычно нет: сам предикат EXISTS не создаёт дубликатов внешних строк. DISTINCT может быть нужен по другой причине, например если дубликаты уже порождаются отдельным соединением в запросе, но добавлять его только из-за EXISTS не следует.
Логически СУБД обязана определить только факт существования, поэтому последующие совпадения не меняют результат. Физический план может действительно остановить поиск рано, но оптимизатор также вправе выбрать другой эквивалентный способ выполнения, например использовать хеширование или другой тип полусоединения. Гарантируется результат, а не конкретная стратегия доступа.