0Pricing
Coding Interview Prep · Урок

Ловушка WHERE при внешнем объединении

Узнайте, почему фильтрация столбца внешнего объединения в WHERE незаметно превращает его во внутреннее объединение.

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

Ловушка, на которой ошибаются все

Это самая распространённая ошибка во внешних соединениях, которую интервьюеры намеренно провоцируют: «Покажите всех клиентов и их заказы за 2024 год, включая клиентов без заказов за 2024 год».

Кандидат пишет LEFT JOIN, а затем добавляет фильтр по дате в WHERE, и клиенты без заказов за 2024 год незаметно исчезают. LEFT JOIN тихо превращается в INNER JOIN. Понимание причины — признак уровня опытного специалиста.

Ошибочный запрос

Вот ошибка. Запрос выглядит разумно: сохранить всех клиентов, соединить их с заказами и отфильтровать заказы за 2024 год.

Но клиенты без заказов или без заказов за 2024 год исчезают из результата. Требование включить их нарушено.

-- BUG: drops customers with no 2024 order
SELECT c.name, o.id, o.order_date
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.order_date >= '2024-01-01';

Почему это не работает

Вспомните порядок выполнения: сначала происходит JOIN, создавая строки, в которых у несовпавших клиентов каждый столбец заказа содержит NULL. Затем выполняется WHERE.

Для несовпавшего клиента o.order_date равен NULL, поэтому выражение o.order_date >= '2024-01-01' вычисляется как UNKNOWN, а не как истина. WHERE сохраняет только истинные строки, поэтому строки с NULL отфильтровываются — именно те строки, которые LEFT JOIN должен был сохранить.

NULL нарушает работу фильтра

Любое сравнение с NULL даёт UNKNOWN: NULL >= '2024-01-01' — UNKNOWN, NULL = 5 — UNKNOWN, и даже NULL <> 5 — UNKNOWN.

Поскольку WHERE пропускает только строки, вычисляющиеся как TRUE, каждая сохранённая строка без соответствия отбрасывается. Вся цель внешнего соединения сводится на нет одним предикатом WHERE по столбцу правой таблицы.

Исправление: фильтр в ON

Переместите фильтр в предложение ON. Там он становится частью условия сопоставления и применяется до сохранения строк, поэтому несовпавшие клиенты по-прежнему сохраняются с NULL.

-- CORRECT: filter lives in ON
SELECT c.name, o.id, o.order_date
FROM customers c
LEFT JOIN orders o
  ON o.customer_id = c.id
  AND o.order_date >= '2024-01-01';
-- customers with no 2024 order: kept, NULL order

ON и WHERE в одном предложении

Правило, которое стоит произнести на собеседовании:

Для сохраняемой (внешней) таблицы условия по другой таблице должны находиться в ON, а условия по самой сохраняемой таблице — в WHERE.

  • ON определяет, что считается совпадением (выполняется во время соединения).
  • WHERE фильтрует итоговые строки (выполняется после соединения и удаляет строки с NULL).

Результаты бок о бок

Одни и те же данные, два места для фильтра, разные ответы. Предположим, у Carol нет заказа за 2024 год.

  • Фильтр в WHERE: Carol исчезает. Фактически это внутреннее соединение.
  • Фильтр в ON: Carol появляется один раз со значениями NULL в столбцах заказа, и требование выполнено.

Разница в результате — суть этой ловушки.

-- ON version output
-- Alice | 50 | 2024-03-01
-- Bob   | 20 | 2024-05-02
-- Carol | NULL | NULL   <-- preserved

Когда WHERE действительно уместен

Не всякий WHERE во внешнем соединении является ошибкой. Фильтрация сохраняемой таблицы допустима: она не связана с NULL, появившимися при соединении.

Кроме того, антисоединение из предыдущего урока намеренно использует WHERE o.id IS NULL, чтобы воспользоваться именно этим поведением. Важно понимать, с каким случаем вы имеете дело.

-- Fine: filtering the preserved (left) table
SELECT c.name, o.id
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE c.country = 'US';

Эвристика обнаружения

Проверяя внешнее соединение, просмотрите предложение WHERE в поисках предикатов по столбцам несохраняемой таблицы (кроме проверок IS NULL для антисоединения).

Если вы видите o.someColumn = ... или проверку диапазона либо равенства по внешней стороне в WHERE, заподозрите эту ловушку. Спросите себя: «Не превращает ли это мой LEFT JOIN в INNER JOIN?» Обычно превращает.

Несколько условий

Вы можете сочетать оба места размещения. Условия сопоставления по правой таблице помещайте в ON, а настоящее условие фильтрации после соединения по левой таблице — в WHERE. Они без проблем работают вместе.

SELECT c.name, o.id, o.amount
FROM customers c
LEFT JOIN orders o
  ON o.customer_id = c.id
  AND o.amount > 100          -- match condition
WHERE c.signup_year = 2023;    -- preserved-table filter

Как объяснить это вслух

На собеседовании объясняйте механизм, а не только исправление:

«Сначала выполняется соединение и заполняет правые столбцы несовпавших строк значениями NULL. Предикат WHERE по этим столбцам для строк с NULL вычисляется как UNKNOWN, а WHERE отбрасывает неистинные строки, поэтому внешнее соединение превращается во внутреннее. Размещение предиката в ON оставляет его условием сопоставления и сохраняет несовпавшие строки». Такое объяснение всегда производит впечатление.

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

Вы должны перечислить всех клиентов и только их заказы за 2024 год, сохранив клиентов, у которых таких заказов не было.

Итоги

Фильтрация столбца несохраняемой таблицы в WHERE незаметно превращает внешнее соединение во внутреннее, потому что NULL из несовпавших строк не проходит предикат (даёт UNKNOWN), и WHERE отбрасывает такие строки.

  • Условия сопоставления с внешней таблицей помещайте в ON.
  • Фильтры по сохраняемой таблице помещайте в WHERE.
  • IS NULL в WHERE — это намеренное антисоединение, а не ловушка.
  • Объясняйте порядок выполнения, чтобы показать понимание механизма.

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

Урок «Ловушка WHERE при внешнем объединении» бесплатный?

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

Чему я научусь в уроке «Ловушка WHERE при внешнем объединении»?

Узнайте, почему фильтрация столбца внешнего объединения в WHERE незаметно превращает его во внутреннее объединение. Ты практикуешь Coding Interview Prep с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

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

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

Сколько времени занимает урок «Ловушка WHERE при внешнем объединении»?

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

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

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

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

  1. LEFT JOIN и сохранение несовпавших строк
  2. Смысл RIGHT и FULL OUTER JOIN
  3. Поиск строк без совпадений (антиобъединение)
  4. Ловушка WHERE при внешнем объединении
← Назад к Coding Interview Prep