Неочевидные факты про JOIN
Про JOIN обычно пишут общую теоретическую информацию. Всегда упоминают виды JOIN-ов: INNER, LEFT, RIGHT, FULL, CROSS, а за кулисами могут остаться интересные факты, подводные камни и тонкости, которые редко упоминаются, но которые могут реально пригодиться.
В моем канале На связи: SQL уже есть про это посты: вот этот и вот этот. Канал я веду с нуля подписчиков. Чтобы показать, что аналитика и SQL это не страшно, а интересно. Присоединяйся, если ты тоже хочешь начать изучать SQL или вспомнить и осознать некоторые нюансы.
Неочевидные факты про JOIN
Условие в ON vs WHERE
Для INNER JOIN — разницы нет, фильтрация в ON или в WHERE даст одинаковый результат.
Но для LEFT JOIN это уже не так:
ON фильтрует при объединении (строка может остаться с NULL в правой таблице).
WHERE фильтрует после — и может "выбросить" строки, ради которых делался LEFT JOIN.
👉 Классический баг у новичков: пишут условие в WHERE и не понимают, почему LEFT превратился в INNER.
Предположим, у нас есть две таблицы:
orders (таблица заказов):
order_id | customer_id | amount
---------|-------------|--------
1 | 1 | 100
2 | 2 | 200
3 | 1 | 150
customers (таблица клиентов):
customer_id | country
------------|---------
1 | USA
2 | UK
3 | USA
И есть два запроса:
SELECT o.order_id, c.customer_id, c.country
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id
AND c.country = 'USA';
SELECT o.order_id, c.customer_id, c.country
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id
WHERE c.country = 'USA';
Результаты этих запросов будут одинаковы?
Можно писать свои рассуждения в комментариях.
JOIN на неравенстве
SELECT *
FROM a
JOIN b ON a.value BETWEEN b.min AND b.max;
Это называется non-equi join (неравенственный джойн).
👉 В BI и аналитике это часто используют для «поиска диапазона» (например, попадает ли дата заказа в акцию).
Но! Такой JOIN почти всегда тяжелее, потому что индексы плохо помогают.
FULL JOIN в проде почти не используют
Многие базы его поддерживают, но реально на проектах встречается редко. Почему?
Почти всегда можно заменить комбинацией LEFT JOIN UNION RIGHT JOIN.
А оптимизаторы некоторых СУБД работают с FULL OUTER JOIN медленнее.
👉 Часто факт наличия FULL JOIN в запросе сигнализирует, что "что-то не так со схемой данных".
CROSS JOIN — не только для Декарта
Все думают, что CROSS JOIN = "перемножить всё на всё". Но он используется:
для генерации тестовых данных:
SELECT d::date
FROM generate_series('2025-01-01', '2025-01-31', interval '1 day') d
CROSS JOIN users;
для построения матриц, календарей, отчетов с дырками.
👉 То есть CROSS JOIN часто — инструмент BI-разработчика
JOIN и NULL — подстава
NULL = NULL → всегда FALSE.
Поэтому если соединяешь таблицы по колонке с NULL, то такие строки просто теряются.
👉 В проде это часто ломает аналитику: ожидали, что будет связь «пустое с пустым», а SQL этого не понимает.
Производительность JOIN-ов
ANTI JOIN
Вместо NOT IN или NOT EXISTS можно писать LEFT JOIN ... WHERE b.id IS NULL.
Это часто быстрее, особенно в старых версиях MySQL.
👉 Но не забывай: NOT IN (NULL, ...) ведет себя неожиданно (возвращает пустой набор).
JOIN ≠ JOIN ORDER
SQL — декларативный язык. Ты пишешь JOIN-ы в любом порядке, но оптимизатор сам решает, какую таблицу читать первой, как переставить местами соединения. Поэтому писать «самую маленькую таблицу первой» часто не имеет смысла. Но иногда хинты (JOIN ORDER, FORCE JOIN, USE INDEX) всё же нужны, когда оптимизатор ошибается.
Когда мы пишем:
мы как бы говорим:
👉 «Дай мне все заказы, вместе с клиентами и регионами».
НО! Мы не указываем порядок, в котором эти таблицы реально будут соединяться.
Оптимизатор (query planner) сам решает:
какую таблицу читать первой;
по какому индексу идти;
в каком порядке выполнять JOIN-ы.
И этот порядок почти всегда ≠ порядок в SQL-запросе.
Как это работает на практике
Оптимизатор строит граф зависимостей между таблицами и условиями.
Считает «стоимость» разных стратегий (в PostgreSQL это cost-based optimizer).
Выбирает план с минимальной стоимостью: например, начать с маленькой таблицы, потом по индексу сходить в большую.
OIN ORDER hints
Иногда оптимизатор ошибается. Причины:
Неправильная статистика (например, таблица только что обновилась).
Очень сложный запрос (10+ JOIN-ов, подзапросы).
Особенности движка (MySQL раньше любил «сначала левую таблицу»).
Тогда СУБД позволяют подсказать оптимизатору:
FORCE ORDER (SQL Server, Oracle) → использовать JOIN-ы в том порядке, как написаны.
LEADING (Oracle) → указать, с какой таблицы начать.
USE INDEX (MySQL) → подсказать, какой индекс использовать.
PostgreSQL не имеет FORCE JOIN, но можно:
отключить конкретные алгоритмы (SET enable_hashjoin = off;)
использовать LATERAL, JOIN LATERAL, CROSS JOIN чтобы «подсказать» порядок.
🔹 Когда порядок JOIN реально важен
Суперсложные запросы (20+ таблиц).
Оптимизатор может выбрать очень дорогой план → запрос работает минуты/часы.
Иногда правильный хинт → ускорение в десятки раз.Необновлённая статистика.
Оптимизатор думает: «таблица маленькая», но на самом деле она разрослась. → выбирает плохой порядок JOIN.LIMIT + ORDER BY.
Тут реально важно, с чего начать — иногда оптимизатор «тянет» всю таблицу, хотя мог бы остановиться раньше.
🔹 Лайфхак для практики
В PostgreSQL можно посмотреть план:
EXPLAIN (ANALYZE, BUFFERS) SELECT ...
→ увидишь реальный JOIN order.
Не доверяй слепо «писать маленькую таблицу первой» — это миф, из старых времён MySQL.
Иногда лучше переписать запрос так, чтобы оптимизатору нечего было гадать. Например, вынести фильтрацию в CTE или subquery.
JOIN — это не только "соединить таблицы", а целый набор особенностей: от NULL и порядка фильтрации до генерации календарей через CROSS JOIN.
Подписывайся на мой канал На связи SQL и давай изучать особенности вместе!