В чем разница между условиями в `ON` и `WHERE` при использовании `JOIN` в SQL?

Ответ

Да, WHERE можно и нужно использовать с JOIN, но ON и WHERE выполняют разные семантические задачи, и их путаница может привести к неверным результатам, особенно с OUTER JOIN.

  • ON: Указывает условие для соединения таблиц. Фильтрация происходит до того, как строки окончательно включаются в результирующий набор JOIN.
  • WHERE: Указывает условие для фильтрации уже сформированного набора данных после выполнения JOIN.

Для INNER JOIN разница часто незаметна: Оптимизатор СУБД может обработать оба запроса одинаково, так как INNER JOIN оставляет только строки, удовлетворяющие всем условиям.

-- Эти запросы вернут одинаковый результат
SELECT u.name, o.product
FROM users u
INNER JOIN orders o ON u.id = o.user_id AND o.status = 'completed';

SELECT u.name, o.product
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.status = 'completed';

Ключевое различие проявляется в OUTER JOIN (например, LEFT JOIN):

Представим, что мы хотим получить всех пользователей и их заказы, если они есть.

  1. Условие в ON (Правильный способ): Фильтрует записи из правой таблицы (orders) перед присоединением. Если у пользователя нет заказов со статусом completed, он все равно останется в результате, а поля заказа будут NULL.

    SELECT u.name, o.product
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'completed';
  2. Условие в WHERE (Неправильный способ, если нужны все пользователи): Сначала LEFT JOIN создает набор, где для пользователей без заказов поля orders равны NULL. Затем WHERE o.status = 'completed' отфильтровывает все строки, где это условие ложно, включая те, где o.status равен NULL. В итоге LEFT JOIN эффективно превращается в INNER JOIN.

    SELECT u.name, o.product
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE o.status = 'completed'; -- Отфильтрует пользователей без заказов

Итог:

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

Ответ 18+ 🔞

О, SQL, ёпта, классика жанра! Вечный спор, где что писать. Сейчас разжую, как есть, чтобы понятно было, а то мозг иногда от этих JOIN'ов плавится.

Смотри, в чём тут соль. ON и WHERE — это как два разных охранника на складе, блядь. Один пропускает грузовики на территорию (ON), а второй уже не пускает на конкретный этаж (WHERE). И если их обязанности перепутать, то можно остаться без нужных коробок, понимаешь?

ON — это твой сцепщик, блядь. Он говорит: «Таблицы, соединяйтесь вот по этому правилу!». Всё, что ты сюда пишешь, влияет на сам процесс склейки данных. Как будто ты говоришь: «Прицепи мне заказы к пользователям, но только если заказ выполнен, сука!». И если заказа такого нет — ну хуй с ним, пользователь-то останется.

WHERE — это уже вышибала, ёпта. Он работает после того, как всё склеили. Смотрит на готовую кучу данных и говорит: «А теперь, блядь, выкидываем отсюда всё, где заказ не выполнен!». И выкидывает напрочь, вместе с пользователем, у которого заказа вообще не было (потому что там NULL).

Для INNER JOIN — да похуй, честно. Оптимизатор умный, он часто к одному результату приведёт. Вот смотри, эти два запроса — близнецы-братья, результат одинаковый:

-- Вариант 1: Условие и связь в ON
SELECT u.name, o.product
FROM users u
INNER JOIN orders o ON u.id = o.user_id AND o.status = 'completed';

-- Вариант 2: Связь в ON, фильтр в WHERE
SELECT u.name, o.product
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.status = 'completed';

А вот где начинается настоящий пиздец и путаница — так это с LEFT JOIN. Тут уже принципиально, куда пишешь.

Пример: Нам нужны ВСЕ пользователи, даже если у них нет завершённых заказов.

  1. Правильно (фильтр для заказа в ON): «Дай всех юзеров и, если найдётся, прилепи к ним заказы, но только завершённые, блядь». Если завершённых нет — юзер всё равно будет в списке, просто поля заказа — пустые.

    SELECT u.name, o.product
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'completed';
  2. Неправильно (фильтр в WHERE): А вот это — ловушка для молодых. Сначала склеиваем всех юзеров со ВСЕМИ их заказами. Потом вышибала (WHERE) приходит и орёт: «Убирай нахуй всё, где status не completed!». И убирает. В том числе и строки, где заказа вообще не было (там status = NULL). И наш красивый LEFT JOIN превращается в говнистый INNER JOIN, а мы сидим и думаем: «И куда, сука, пропали все пользователи без заказов?».

    SELECT u.name, o.product
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE o.status = 'completed'; -- Вот эта хуйня всех и порешает

Короче, запомни, как отче наш:

  • Всё, что про связь между таблицами — пихай в ON.
  • Всё, что про фильтрацию итоговой таблицы — пихай в WHERE.

А то будешь потом, как Герасим, с мешком на речку ходить, Муму топить, а потом совесть мучать будет, что не того утопил. Всё от непонимания, блядь!