При каких условиях CROSS JOIN с фильтром в WHERE семантически эквивалентен INNER JOIN с условием соединения?

При каких условиях CROSS JOIN с фильтром в WHERE семантически эквивалентен INNER JOIN с условием соединения?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

CROSS JOIN с условием в WHERE эквивалентен INNER JOIN, если фильтр в WHERE содержит то же условие связи между таблицами, не зависит от внешнего контекста и не используются эффекты внешнего соединения. При этом результат имеет ту же кардинальность и те же значения: каждая пара строк сохраняется только при истинности условия.

Логическая эквивалентность не означает, что СУБД обязана физически сначала построить полный декартов результат. Оптимизатор обычно распознаёт предикат и выполняет соединение более эффективно.

Исторический контекст

CROSS JOIN напрямую выражает декартово произведение отношений, а фильтрация результата соответствует операции выбора в реляционной алгебре. INNER JOIN был введён как более точная и читаемая форма записи соединения по условию.

Разделение декартова произведения и фильтра исторически важно для понимания логической обработки запроса. Современный SQL позволяет записывать один и тот же смысл разными способами, а оптимизатор преобразует эквивалентные формы в подходящий план.

Постановка проблемы

Неосторожная запись через CROSS JOIN может выглядеть как запрос, создающий все пары строк. Если таблицы велики, это вызывает опасения по поводу памяти, времени и числа промежуточных строк.

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

Подробное решение

Логически CROSS JOIN сначала рассматривает каждую пару строк из двух источников, а WHERE удаляет пары, для которых предикат не имеет значения TRUE. INNER JOIN сохраняет ровно те же пары, если его условие соединения совпадает с этим предикатом.

SELECT c.id, o.id FROM clients AS c CROSS JOIN orders AS o WHERE o.client_id = c.id AND o.status = 'paid'; SELECT c.id, o.id FROM clients AS c JOIN orders AS o ON o.client_id = c.id WHERE o.status = 'paid';

В примере условие o.client_id = c.id определяет пары строк, а фильтр по статусу ограничивает уже найденные пары. При обычном равенстве строки с NULL в ключе не соединяются, потому что сравнение даёт UNKNOWN, а не TRUE.

Эквивалентность сохраняется для внутренних соединений и обычных детерминированных предикатов. Она не переносится автоматически на LEFT, RIGHT или FULL OUTER JOIN: перенос условия между ON и WHERE там может изменить сохранение строк без пары.

Также важно учитывать дубликаты. Если одному клиенту соответствуют три заказа, результат содержит три строки; ни CROSS JOIN ... WHERE, ни INNER JOIN сами по себе не устраняют повторения.

Физический план не обязан строить полный декартов продукт. Оптимизатор может применить предикат во время поиска пар, выбрать хеш-соединение, соединение вложенными циклами или другой алгоритм. Однако сложная форма выражения, функции над ключами или ограничения оптимизатора могут ухудшить распознавание условия и план, поэтому INNER JOIN ... ON обычно предпочтительнее по читаемости.

Ситуация из практики

В отчёте разработчик записал связь клиентов и заказов через CROSS JOIN, а условие равенства ключей поместил в WHERE. На тестовых данных запрос дал правильный результат, но команда решила, что он обязательно материализует все пары клиентов и заказов.

Рассматривались два варианта. Сохранение CROSS JOIN не меняло семантику, но затрудняло ревью и повышало риск случайно удалить условие связи. Переписывание в INNER JOIN ... ON делало назначение предиката очевидным; при корректной статистике оптимизатор мог построить тот же эффективный план.

Выбрали явный INNER JOIN, проверили план выполнения и отдельно добавили тест на NULL и повторяющиеся ключи. Результат не изменился, а запрос стал понятнее и безопаснее для дальнейшего редактирования.

Что кандидаты часто упускают

  1. Может ли такая запись изменить количество строк из-за дубликатов?

Да. Соединение формирует строку для каждой подходящей пары. Если ключ не уникален в одной таблице, одна строка другой таблицы может соответствовать нескольким строкам; если дубликаты есть в обеих таблицах, их количества перемножаются. Эквивалентные формы CROSS JOIN ... WHERE и INNER JOIN сохраняют эту мультипликативность одинаково.

  1. Что произойдёт, если часть условия связи оставить в WHERE, а часть считать условием соединения?

Для INNER JOIN это обычно не меняет результат, поскольку оба предиката должны быть истинными для сохранения пары. Но это не означает одинаковую производительность: размещение, структура выражений и возможность проталкивания фильтров могут повлиять на план.

Для внешних соединений такое рассуждение уже неверно. Условие в WHERE, проверяющее правую таблицу, может удалить дополненные NULL-строки и фактически превратить LEFT JOIN во внутреннее соединение.

  1. Почему запрос может быть семантически эквивалентен, но работать по-разному?

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

Кроме того, качество плана зависит от статистики, индексов и оценок кардинальности. Поэтому эквивалентность результатов доказывается логикой предикатов, а производительность проверяется планом выполнения и измерениями.