📝 Как работают JOIN в SQL — разбор на пальцах:
Разберём, откуда берутся дубли и почему порядок таблиц в join важен.
Условие: Две таблицы, связаны по ключу users.id = orders.user_id:
У Ани два заказа, у Бори один, у Вики заказов нет. А заказ на 900 — от юзера 4, которого в users нет.
1️⃣ INNER JOIN — только совпадения
Берём каждую строку слева и ищем ей пару справа. Нет пары — строки нет.
Вика вылетела — у неё нет заказов. Заказ на 900 вылетел — у него нет юзера.
2️⃣ Откуда дубли
Аня в результате дважды. Строку слева никто не копировал — просто справа ей нашлось два совпадения:
1 строка × 2 совпадения = 2 строки.
Отсюда классическая ошибка: COUNT(*) после JOIN считает не юзеров, а пары. Считать надо COUNT(DISTINCT u.id).
3️⃣ LEFT JOIN — все слева
Оставляем каждую строку левой таблицы. Нет пары — правая часть заполняется NULL.
4️⃣ Порядок таблиц решает
Тот же LEFT JOIN, то же ON. Меняем только одно — какая таблица стоит слева:
Вики больше нет. Бережём мы теперь orders, а не users.
u LEFT o ≠ o LEFT u.
5️⃣ RIGHT JOIN — то же, но без перестановки
RIGHT держит все строки правой таблицы:
Результат тот же, что в шаге 4: o LEFT u = u RIGHT o. RIGHT почти не пишут — переставить таблицы и оставить LEFT читается проще.
6️⃣ FULL OUTER JOIN — не теряем никого
Всё с обеих сторон, чего нет — NULL:
FULL = LEFT + RIGHT. Удобно для сверки двух источников: сразу видно пропуски с обеих сторон.
7️⃣ CROSS JOIN — каждая с каждой
Единственный джойн без ON. 2 юзера × 2 промокода = 4 строки:
Это декартово произведение: N × M строк. Нужен редко — например, разложить каждого юзера по всем дням календаря.
8️⃣ Цепочка из трёх таблиц
users → orders → payments, всюду LEFT:
3 строки — все юзеры на месте.
9️⃣ Ловушка: стартовали не с той таблицы
Джойны те же, LEFT. Меняем только стартовую таблицу:
Одна строка.
Боря и Вика в набор даже не попали — платежа-то у них нет, а LEFT бережёт только то, что уже слева.
Стартовая таблица задаёт максимум строк: дальше их можно только размножить или отфильтровать, но не добавить новых.
🔟 Ловушка: INNER после LEFT
LEFT честно донёс Борю с NULL и Вику с NULL.
А следующий INNER выкинул все строки без пары
Шпаргалка:
INNER — только совпадения
LEFT — все слева + совпадения
RIGHT — все справа + совпадения
FULL — всё с обеих сторон
CROSS — каждая с каждой (N × M)
Итог: 🤩
❓ Все ли теперь стало понятно в теме join? Пишите в комментариях!
✔️ Подпишитесь на канал, чтобы не пропустить следующие хаки.
🚬 Вопросы, обучение, консультации: Написать в ЛС | mentor.dima-sqlit.ru
@dima_sqlit
Разберём, откуда берутся дубли и почему порядок таблиц в join важен.
Условие: Две таблицы, связаны по ключу users.id = orders.user_id:
users
id name
1 Аня
2 Боря
3 Вика
orders
user_id sum
1 500
1 300
2 700
4 900
У Ани два заказа, у Бори один, у Вики заказов нет. А заказ на 900 — от юзера 4, которого в users нет.
1️⃣ INNER JOIN — только совпадения
Берём каждую строку слева и ищем ей пару справа. Нет пары — строки нет.
SELECT u.name, o.sum
FROM users u
JOIN orders o ON u.id = o.user_id;
Аня 500
Аня 300
Боря 700
Вика вылетела — у неё нет заказов. Заказ на 900 вылетел — у него нет юзера.
2️⃣ Откуда дубли
Аня в результате дважды. Строку слева никто не копировал — просто справа ей нашлось два совпадения:
1 строка × 2 совпадения = 2 строки.
Отсюда классическая ошибка: COUNT(*) после JOIN считает не юзеров, а пары. Считать надо COUNT(DISTINCT u.id).
3️⃣ LEFT JOIN — все слева
Оставляем каждую строку левой таблицы. Нет пары — правая часть заполняется NULL.
SELECT u.name, o.sum
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
Аня 500
Аня 300
Боря 700
Вика NULL -- осталась, но пустая
4️⃣ Порядок таблиц решает
Тот же LEFT JOIN, то же ON. Меняем только одно — какая таблица стоит слева:
SELECT u.name, o.sum
FROM orders o
LEFT JOIN users u ON o.user_id = u.id;
Аня 500
Аня 300
Боря 700
NULL 900 -- заказ без юзера остался
Вики больше нет. Бережём мы теперь orders, а не users.
u LEFT o ≠ o LEFT u.
5️⃣ RIGHT JOIN — то же, но без перестановки
RIGHT держит все строки правой таблицы:
SELECT u.name, o.sum
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;
Результат тот же, что в шаге 4: o LEFT u = u RIGHT o. RIGHT почти не пишут — переставить таблицы и оставить LEFT читается проще.
6️⃣ FULL OUTER JOIN — не теряем никого
Всё с обеих сторон, чего нет — NULL:
Аня 500
Аня 300
Боря 700
Вика NULL -- юзер без заказов
NULL 900 -- заказ без юзера
FULL = LEFT + RIGHT. Удобно для сверки двух источников: сразу видно пропуски с обеих сторон.
7️⃣ CROSS JOIN — каждая с каждой
Единственный джойн без ON. 2 юзера × 2 промокода = 4 строки:
SELECT u.name, p.code
FROM users u
CROSS JOIN promo p;
Аня SALE10
Аня SALE20
Боря SALE10
Боря SALE20
Это декартово произведение: N × M строк. Нужен редко — например, разложить каждого юзера по всем дням календаря.
8️⃣ Цепочка из трёх таблиц
users → orders → payments, всюду LEFT:
SELECT u.name, o.id AS order_id, p.sum AS paid
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
LEFT JOIN payments p ON p.order_id = o.id;
Аня 10 500
Боря 20 NULL
Вика NULL NULL
3 строки — все юзеры на месте.
9️⃣ Ловушка: стартовали не с той таблицы
Джойны те же, LEFT. Меняем только стартовую таблицу:
FROM payments p
LEFT JOIN orders o ON o.id = p.order_id
LEFT JOIN users u ON u.id = o.user_id;
Аня 10 500
Одна строка.
Боря и Вика в набор даже не попали — платежа-то у них нет, а LEFT бережёт только то, что уже слева.
Стартовая таблица задаёт максимум строк: дальше их можно только размножить или отфильтровать, но не добавить новых.
🔟 Ловушка: INNER после LEFT
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
JOIN payments p ON p.order_id = o.id; -- обычный JOIN = INNER
Аня 10 500
LEFT честно донёс Борю с NULL и Вику с NULL.
А следующий INNER выкинул все строки без пары
Шпаргалка:
INNER — только совпадения
LEFT — все слева + совпадения
RIGHT — все справа + совпадения
FULL — всё с обеих сторон
CROSS — каждая с каждой (N × M)
Итог: 🤩
❓ Все ли теперь стало понятно в теме join? Пишите в комментариях!
✔️ Подпишитесь на канал, чтобы не пропустить следующие хаки.
🚬 Вопросы, обучение, консультации: Написать в ЛС | mentor.dima-sqlit.ru
@dima_sqlit