ТакПростоТакПросто.ai
ЦеныДля бизнесаОбразованиеДля инвесторов
ВойтиНачать

Продукт

ЦеныДля бизнесаДля инвесторов

Ресурсы

Связаться с намиПоддержкаОбразованиеБлог

Правовая информация

Политика конфиденциальностиУсловия использованияБезопасностьПолитика допустимого использованияСообщить о нарушении
ТакПросто.ai

© 2026 ТакПросто.ai. Все права защищены.

Главная›Блог›6 SQL JOIN-ов, которые нужно знать каждому аналитику
23 авг. 2025 г.·7 мин

6 SQL JOIN-ов, которые нужно знать каждому аналитику

Понятное руководство по 6 типам SQL JOIN: что делают, чем отличаются и когда применять. Примеры запросов, типичные ошибки и советы по производительности.

6 SQL JOIN-ов, которые нужно знать каждому аналитику

Что такое JOIN и как читать результат соединения

JOIN — это способ «склеить» данные из двух (или больше) таблиц в один результат запроса. В аналитике JOIN нужен, чтобы собрать единую картину: кто пользователь, что он заказал, оплатил ли заказ, когда это произошло и т. д. В приложениях JOIN помогает получать связанные данные за один запрос вместо серии отдельных запросов.

Как представлять результат JOIN

Важно помнить: результат JOIN — это таблица строк, а не «подстановка полей». База данных берёт строки из одной таблицы и сопоставляет их со строками из другой по условию.

  • Если совпадение найдено — вы получаете строку, где столбцы обеих таблиц стоят рядом.
  • Если совпадения нет — в зависимости от типа JOIN строка может пропасть (например, INNER JOIN) или остаться, но с NULL в колонках второй таблицы (например, LEFT JOIN).
  • Если совпадений несколько (у пользователя 3 заказа) — строки размножаются: одна строка слева превращается в несколько строк результата.

Полезный мысленный вопрос при чтении результата: «Сколько строк справа подходит к одной строке слева?» Он почти сразу объясняет, почему итоговый набор вырос.

Мини-набор терминов

  • Таблица — набор строк (записей) и столбцов (полей).
  • Ключ — поле, по которому удобно идентифицировать запись. Часто это id.
  • Связь — правило, что одно поле в таблице A соответствует полю в таблице B.
  • Совпадение — пара строк, которая удовлетворяет условию в ON (например, одинаковый user_id).

Пример предметной области: users, orders, payments

Представим 3 таблицы:

  • users(id, name) — пользователи.
  • orders(id, user_id, created_at, total) — заказы пользователя.
  • payments(id, order_id, paid_at, amount) — оплаты заказа.

Типичная цепочка в аналитике: users → orders → payments. Здесь JOIN отвечает на практические вопросы: какие пользователи делали заказы, сколько заказов у каждого, какие заказы остались без оплаты (в результате будут NULL по полям payments). Дальше разберём типы JOIN и научимся заранее понимать, какие строки попадут в итог и почему.

INNER JOIN: берём только совпадения

INNER JOIN — самый «строгий» вид соединения: он возвращает только те строки, для которых нашлась пара в обеих таблицах по условию ON. Если соответствия нет — строка просто не попадает в результат.

Мини-схема для примеров

Представим две таблицы:

  • users(id, name) — список пользователей
  • orders(id, user_id, created_at) — заказы, где user_id указывает на users.id

Логика INNER JOIN здесь простая: в выдаче окажутся только пользователи, у которых есть хотя бы один заказ (и сами заказы, которые им соответствуют).

Базовый синтаксис (с ON и алиасами)

SELECT
  u.id,
  u.name,
  o.id AS order_id,
  o.created_at
FROM users AS u
INNER JOIN orders AS o
  ON o.user_id = u.id;

Как читать результат:

  • каждая строка результата — это пара «пользователь + заказ»;
  • если у пользователя 3 заказа, то он появится 3 раза (одна строка на каждый заказ);
  • пользователь без заказов не появится ни разу.

Практический кейс: «пользователи с заказами» без пустых клиентов

INNER JOIN удобно использовать, когда вам важны только активные клиенты, и «пустые» записи будут мешать анализу. Например, чтобы получить список пользователей, которые делали заказы за последний месяц:

SELECT DISTINCT
  u.id,
  u.name
FROM users AS u
INNER JOIN orders AS o
  ON o.user_id = u.id
WHERE o.created_at >= CURRENT_DATE - INTERVAL '30 day';

Здесь INNER JOIN гарантирует: в список попадут только те, у кого реально есть заказ, а DISTINCT убирает повторы пользователей, если заказов было несколько.

Совет: условие связи держите в ON, а фильтры по датам/статусам — в WHERE. Так запрос проще читать и проверять.

LEFT JOIN: все слева, даже без пары

LEFT JOIN нужен, когда «левая» таблица — основная, и вы хотите сохранить все её строки, даже если в правой таблице для части строк нет соответствий. В таких случаях столбцы справа заполнятся NULL.

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

SELECT
  u.user_id,
  u.email,
  o.order_id,
  o.created_at
FROM users u
LEFT JOIN orders o
  ON o.user_id = u.user_id;

Примечание: в реальных схемах названия ключевых колонок могут различаться (id vs user_id, id vs order_id). Важно не имя поля, а смысл: вы соединяете PK↔FK (или иной уникальный ключ↔ссылка).

ON vs WHERE: где фильтровать, чтобы не потерять строки

Критически важно понимать: условия в ON влияют на то, как соединяются таблицы, а условия в WHERE — на итоговый набор строк после соединения.

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

SELECT u.user_id, o.order_id
FROM users u
LEFT JOIN orders o
  ON o.user_id = u.user_id
 AND o.status = 'paid';

Если написать WHERE o.status = 'paid', то пользователи без заказов (у которых o.status = NULL) исчезнут — это частая ловушка.

Кейс: найти пользователей без заказов (anti-join)

Один из самых полезных сценариев LEFT JOIN — поиск «нехваток»: кто есть в левой таблице, но отсутствует в правой.

SELECT u.user_id, u.email
FROM users u
LEFT JOIN orders o
  ON o.user_id = u.user_id
WHERE o.user_id IS NULL;

Такой приём часто называют anti-join: мы соединяем, а затем оставляем только строки, где «пара» справа не нашлась.

Частая ошибка: LEFT JOIN внезапно стал INNER JOIN

Если после LEFT JOIN вы в WHERE проверяете столбцы правой таблицы на конкретные значения (WHERE o.created_at >= ..., WHERE o.status = ...), вы почти всегда отсекаете NULL и тем самым превращаете запрос в аналог INNER JOIN. Если цель — сохранить все строки слева, переносите такие условия в ON или используйте проверки с учётом NULL.

RIGHT JOIN: все справа и как его заменить

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

Когда RIGHT JOIN действительно нужен

  • Вы читаете чужой запрос/BI-генератор, который по умолчанию строит RIGHT JOIN.
  • У вас уже «зафиксирована» правая таблица (например, подзапрос) и перестановка ухудшает читаемость.
  • Вам важно подчеркнуть, что «истина» запроса — правая таблица (редко, но бывает в ревью).

Эквивалент через LEFT JOIN (на примере)

Пусть есть таблицы users и orders. Хотим получить все заказы, даже если пользователя уже нет в users.

Вариант с RIGHT JOIN:

SELECT
  o.order_id,
  o.user_id,
  u.email
FROM users u
RIGHT JOIN orders o
  ON u.user_id = o.user_id;

То же самое через LEFT JOIN — просто переставляем таблицы:

SELECT
  o.order_id,
  o.user_id,
  u.email
FROM orders o
LEFT JOIN users u
  ON u.user_id = o.user_id;

Результат будет одинаковым: если пользователь удалён или не найден, поля u.* станут NULL, но заказ останется.

Кейс: отчёт по всем заказам и данным пользователя

Это частый сценарий для аналитики: заказы — факты, пользователи — справочник. «Потерять» заказ из-за отсутствующего пользователя нельзя, поэтому базовая таблица здесь — orders.

Как сделать запрос читабельнее

Выберите один стиль и придерживайтесь его:

  • факты (orders, events, payments) — в FROM;
  • справочники (users, products) — через LEFT JOIN;
  • одинаковые алиасы и форматирование JOIN ... ON ... на новых строках.

Так быстрее читать запросы и проще замечать ошибки в условиях ON.

FULL OUTER JOIN: объединяем всё и ищем расхождения

Planning mode для витрин
Спланируйте ключи, зерно и условия ON, прежде чем писать код.
Планировать

FULL OUTER JOIN — это соединение, которое сохраняет строки с обеих сторон. Если для строки из левой таблицы нашлась пара справа — вы получите объединённую строку. Если пары нет — строка всё равно попадёт в результат, а недостающие поля второй таблицы будут заполнены NULL. То же самое работает и для строк из правой таблицы.

Когда FULL OUTER JOIN особенно полезен

Главный сценарий — сверка данных из двух источников и поиск расхождений. Например:

  • сравнить список клиентов в CRM и в биллинге;
  • проверить, все ли заказы из витрины попали в хранилище;
  • найти товары, которые есть в каталоге, но отсутствуют в прайсе поставщика (и наоборот).

В таких задачах FULL OUTER JOIN удобен тем, что сразу показывает три группы: совпавшие записи, «только слева» и «только справа».

Как читать строки, где одна сторона NULL

Если после соединения вы видите NULL-значения только в колонках правой таблицы, это значит: запись есть слева, но не нашлось соответствия справа.

Если NULL-значения только в колонках левой таблицы, это наоборот: запись есть справа, но отсутствует слева.

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

Если ваша СУБД не поддерживает FULL OUTER JOIN

Некоторые системы (например, MySQL в классическом виде) не имеют FULL OUTER JOIN. Идея обхода простая: сделать LEFT JOIN (все слева) и добавить к нему строки, которые есть только справа, через UNION (обычно UNION ALL + фильтр). Детали реализации зависят от СУБД и ключей, но логика всегда одна: «всё слева» + «всё справа, чего не было слева».

CROSS JOIN: все комбинации и контроль объёма

CROSS JOIN — это соединение «каждый с каждым». Он возвращает декартово произведение: для каждой строки из первой таблицы берётся каждая строка из второй.

Звучит просто, но именно поэтому это опасно: 10 000 строк × 10 000 строк = 100 000 000 строк результата. Часто CROSS JOIN «случайно» появляется из-за пропущенного условия ON в обычном JOIN — и запрос внезапно начинает считаться минутами.

Когда CROSS JOIN действительно полезен

Есть несколько нормальных сценариев, где «все комбинации» — то, что нужно:

  • Генерация календаря (дни/недели/месяцы) и последующее «раскладывание» метрик по каждому дню.
  • Сетка параметров для отчётов: например, все регионы × все каналы × все типы клиента.
  • Тестовые комбинации: проверить, что бизнес-правила работают для всех пар значений.

Ключевой признак «здорового» CROSS JOIN: хотя бы одна из таблиц — маленький справочник или искусственно ограниченный набор.

Как держать объём результата под контролем

  1. Ограничивайте входные наборы до CROSS JOIN: фильтруйте даты, статусы, актуальные версии.

  2. Джойните маленькие справочники (10–100 строк), а не «сырые» факты.

  3. Сразу добавляйте ограничения после: например, отсекайте невозможные комбинации по условиям WHERE.

Пример: все дни месяца × список тарифов для план‑факта

Ниже — заготовка «плановой сетки»: на каждый день месяца создаём строки для каждого тарифа, чтобы затем подтянуть факт и сравнить.

WITH days AS (
  SELECT generate_series(date '2025-12-01', date '2025-12-31', interval '1 day')::date AS day
),
plans AS (
  SELECT tariff_id, planned_revenue
  FROM tariff_plan
  WHERE month = date '2025-12-01'
)
SELECT
  d.day,
  p.tariff_id,
  p.planned_revenue
FROM days d
CROSS JOIN plans p;

Такой результат удобно дальше соединять с фактом по (day, tariff_id) и сразу видеть пропуски: где план есть, а факта нет (или наоборот).

SELF JOIN: связи внутри одной таблицы

SELF JOIN — это обычный JOIN, только таблица соединяется сама с собой. Чтобы SQL понимал, где «левая», а где «правая» сторона, используют алиасы (псевдонимы): например, employees e и employees m.

Зачем соединять таблицу с самой собой

SELF JOIN полезен, когда связь находится внутри одной таблицы: одна строка ссылается на другую (иерархия), либо вы сравниваете записи между собой (поиск дублей, конфликтов, несостыковок).

Кейс 1: иерархия «сотрудник → руководитель»

Предположим, в таблице employees есть id, name, manager_id (ссылка на employees.id). Тогда руководителя можно «подтянуть» так:

SELECT
  e.id,
  e.name        AS employee_name,
  m.name        AS manager_name
FROM employees e
LEFT JOIN employees m
  ON e.manager_id = m.id;

Почему часто именно LEFT JOIN: у верхнего руководителя manager_id может быть NULL, и сотрудник всё равно должен остаться в выдаче.

Кейс 2: сравнение записей внутри таблицы (поиск дублей)

Если нужно найти возможные дубли по email/телефону, удобнее сравнивать строки попарно, исключая «саму себя»:

SELECT
  a.id AS id1,
  b.id AS id2,
  a.email
FROM users a
JOIN users b
  ON a.email = b.email
 AND a.id < b.id;

Условие a.id < b.id убирает зеркальные пары (1–2 и 2–1) и совпадение строки с самой собой.

Типовые ловушки

  • Неверное условие ON: если перепутать ключи (например, e.id = m.id вместо e.manager_id = m.id), получите «самосовпадения» или бессмысленную выборку.
  • Лишние совпадения: при дублях часто забывают ограничение вроде a.id < b.id, и результат раздувается вдвое.
  • Циклы в иерархиях: если данные испорчены (A руководит B, B руководит A), простая выборка ещё отработает, но рекурсивные отчёты/деревья могут зациклиться. Практика: валидировать manager_id, запрещать самоссылки и отслеживать циклы отдельной проверкой.

Ключи и условия ON: как не получить лишние строки

Экспортируйте исходный код
Выгрузите код React, Go и схемы PostgreSQL, когда прототип станет рабочим.
Экспортировать

Большинство «странных» результатов JOIN возникают не из-за самого JOIN, а из-за неверно выбранных ключей и условий в ON. Если заранее понять, какие поля действительно связывают таблицы, вы избежите дубликатов, потерянных строк и неожиданного раздувания выборки.

Выбираем ключи: первичный, внешний, уникальность

Идеальный сценарий — соединять первичный ключ (PK) одной таблицы с внешним ключом (FK) другой. PK гарантирует уникальность, а FK — корректную ссылку.

Если вы соединяете неуникальные поля (например, city или status), то легко получить много совпадений на одну строку — и результат умножится.

JOIN по нескольким полям и составные ключи

Иногда «ключ» — это комбинация полей: например, заказ в разрезе магазина и номера заказа. Тогда соединение делайте по всем частям составного ключа.

SELECT *
FROM orders o
JOIN payments p
  ON o.shop_id = p.shop_id
 AND o.order_id = p.order_id;

Если забыть одно поле (например, shop_id), вы рискуете склеить записи из разных магазинов и получить лишние строки.

NULL в ключах: что будет и как себя защитить

NULL никогда не равен NULL, поэтому условие o.user_id = u.user_id не соединит строки, где оба значения NULL. Это обычно полезно: «неизвестные» идентификаторы не должны совпадать.

Но важно понимать последствия:

  • в INNER JOIN строки с NULL в ключе просто исчезнут;
  • в LEFT JOIN они останутся слева, но поля справа будут NULL.

Если вам нужно явно исключить такие строки, добавляйте фильтр в ON, чтобы не превратить LEFT JOIN в скрытый INNER JOIN через WHERE:

SELECT *
FROM orders o
LEFT JOIN users u
  ON o.user_id = u.user_id
 AND o.user_id IS NOT NULL;

Проверяем кардинальность: 1:1, 1:N, N:N

Перед JOIN ответьте себе: связь один-к-одному, один-ко-многим или многие-ко-многим?

  • 1:1 — ожидаете столько же строк, сколько слева (для LEFT JOIN) или совпадений (для INNER JOIN).
  • 1:N — строки слева могут дублироваться (это нормально, если вы «разворачиваете» детали).
  • N:N — почти всегда означает взрыв количества строк. Обычно нужен промежуточный справочник/таблица связей или предварительная агрегация.

Практический тест: сравните количество строк до и после JOIN и проверьте, какой ключ даёт дубликаты (например, COUNT(*) vs COUNT(DISTINCT ...)). Если рост неожиданный — проблема почти всегда в ON.

Типичные ошибки JOIN и как их быстро найти

Даже «правильный» JOIN может внезапно дать в 2–10 раз больше строк, странные суммы или исчезнувшие записи. Хорошая новость: большинство проблем повторяются — и их можно быстро диагностировать несколькими проверками.

1) Дубли строк из-за 1:N и N:N

Самая частая причина — вы соединяете сущность «один ко многим» (клиент → заказы) или даже «многие ко многим» (товары ↔ категории), а ожидаете одну строку на клиента.

Быстрая проверка: сравните количество строк до и после JOIN и посмотрите, какие ключи размножаются.

-- какие id стали встречаться чаще
select a.id, count(*) as cnt
from a
join b on b.a_id = a.id
group by a.id
having count(*) > 1
order by cnt desc;

Исправление: агрегируйте «правую» таблицу до нужной зернистости до соединения (подзапрос/CTE) или соединяйте по уникальному ключу.

2) WHERE vs ON: как не сломать LEFT JOIN

Если вы делаете LEFT JOIN, но затем фильтруете поля правой таблицы в WHERE, вы часто превращаете его в INNER JOIN и теряете строки без пары.

Плохой паттерн:

select *
from a
left join b on b.a_id = a.id
where b.status = 'paid';

Лучше переносить фильтр в ON, сохраняя строки из A:

select *
from a
left join b
  on b.a_id = a.id
 and b.status = 'paid';

3) Агрегации после JOIN: почему суммы «раздуваются»

Суммы и количества «раздуваются», когда одна строка факта (например, заказ) после JOIN превращается в несколько строк (например, по позициям заказа). Симптом: общая сумма выше ожидаемой.

Защита: агрегируйте на уровне факта до JOIN, либо считайте уникальные сущности (например, count(distinct order_id)), либо используйте подзапрос, который возвращает одну строку на ключ.

4) Псевдо-уникальность: JOIN по имени/городу

Соединение по неуникальным полям (имя, город, дата рождения) почти гарантирует ложные совпадения и дубли. Даже если «сейчас работает», позже появится второй «Иван Петров» — и отчёт незаметно испортится.

Правило: соединяйте по стабильным ключам (id, внешний ключ), а текстовые поля используйте только для отображения. Для диагностики ищите значения, у которых в правой таблице больше одной строки на ключ.

Производительность JOIN: простые и безопасные улучшения

API для JOIN запросов
Сделайте Go бэкенд, который отдаёт результат JOIN одним эндпоинтом.
Создать API

JOIN почти всегда упирается в объём данных и то, как быстро СУБД находит строки по ключам. Хорошая новость: несколько привычек дают заметный прирост без «магии» и риска сломать логику.

1) Индексы на полях соединения

Чаще всего помогает индекс на колонках, которые участвуют в ON: например, orders.customer_id и (обычно уже есть) customers.id. Если соединение идёт по нескольким полям, иногда нужен составной индекс в том же порядке, что и в условии.

Важно: индекс на внешнем ключе особенно полезен в LEFT JOIN, когда справа таблица большая.

2) Почему SELECT * вреден

В больших JOIN SELECT * заставляет читать и передавать лишние столбцы, увеличивает память на сортировки/хеши и нагрузку на сеть.

Лучше перечислять только нужные поля и думать, из какой таблицы они берутся:

SELECT o.order_id, o.created_at, c.name
FROM orders o
JOIN customers c ON c.id = o.customer_id;

3) Уменьшайте данные до JOIN

Дешевле соединять меньше строк.

  • Предфильтрация: условия по каждой таблице (дату, статус) задавайте как можно раньше.
  • Подзапрос/CTE: сначала сузьте «тяжёлую» таблицу, потом JOIN.

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

4) План запроса: что смотреть, если «тормозит»

Откройте EXPLAIN/EXPLAIN ANALYZE и проверьте:

  • сколько строк реально проходит через узлы JOIN (оценка vs факт);
  • есть ли Seq Scan по большой таблице там, где ожидали индекс;
  • где появляются самые дорогие операции (сортировки, хеширование, материализация).

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

Шпаргалка и практика: закрепляем 6 JOIN

Мини‑чеклист: какой JOIN выбрать

Когда сомневаетесь, начните с вопроса: «какие строки обязаны быть в результате?»

  • INNER JOIN — нужны только записи, у которых есть пара в обеих таблицах (пересечение).
  • LEFT JOIN — важна «левая» таблица: вернуть всех слева, а справа — если нашлось совпадение.
  • RIGHT JOIN — то же, но «важна правая». На практике чаще заменяют перестановкой таблиц и LEFT JOIN.
  • FULL OUTER JOIN — нужны все строки с обеих сторон и полезно увидеть, где пары нет.
  • CROSS JOIN — нужны все комбинации (матрица). Используйте осознанно и с ограничениями.
  • SELF JOIN — связываете таблицу саму с собой (иерархии, «пригласил друга», замены менеджеров).

Практика на users / orders / payments

Представим:

  • users(id, email)
  • orders(id, user_id, created_at)
  • payments(id, order_id, status)
  1. INNER JOIN: пользователи с заказами
SELECT u.id, o.id AS order_id
FROM users u
JOIN orders o ON o.user_id = u.id;
  1. LEFT JOIN: все пользователи и число их заказов (включая 0)
SELECT u.id, COUNT(o.id) AS orders_cnt
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id;
  1. FULL OUTER JOIN: найти «разрывы» между заказами и платежами
SELECT o.id AS order_id, p.id AS payment_id
FROM orders o
FULL OUTER JOIN payments p ON p.order_id = o.id
WHERE o.id IS NULL OR p.id IS NULL;
  1. CROSS JOIN: все пары «пользователь × статус оплаты» (для отчётной матрицы)
SELECT u.id, s.status
FROM users u
CROSS JOIN (SELECT 'paid' AS status UNION ALL SELECT 'failed') s;

Тестовые вопросы (проверьте себя)

  • Если у пользователя нет заказов, появится ли он при INNER JOIN users↔orders?
  • Что вернёт LEFT JOIN, если в orders.user_id есть NULL?
  • Сколько строк даст CROSS JOIN при 100 users и 5 значениях статуса?
  • При FULL OUTER JOIN: какие строки попадут в результат, если у платежа нет заказа?

Итог

Держите в памяти простую опору: INNER — только пары, LEFT/RIGHT — «все с одной стороны», FULL — «все вообще», CROSS — «все комбинации», SELF — «связь внутри таблицы». Если результат вдруг «раздулся» или исчезли строки — первым делом перепроверьте ключи и условие ON.

Если вы не только пишете запросы, но и собираете внутренние инструменты/витрины для команды, удобно сразу закладывать правильные JOIN‑паттерны в прототип. Например, в TakProsto.AI (vibe‑coding платформа для российского рынка) можно в формате чата быстро собрать веб‑панель или небольшой сервис: React‑интерфейс, Go‑бэкенд с PostgreSQL, а затем итеративно дорабатывать SQL‑запросы, сохраняя снапшоты и откатываясь при необходимости. Это особенно полезно, когда вы отлаживаете кардинальность (1:N, N:N), переносите фильтры из WHERE в ON и проверяете, что отчёт не теряет строки и не раздувает суммы.

FAQ

Что на самом деле «делает» JOIN и почему после него меняется количество строк?

JOIN соединяет строки из двух таблиц по условию в ON.

  • Если справа находится 0 строк — при INNER JOIN строка пропадёт, при LEFT JOIN останется с NULL справа.
  • Если справа находится 1 строка — получите одну строку результата.
  • Если справа находится N строк — строка слева размножится на N строк.

Полезная привычка: перед выполнением запроса спросить себя «сколько строк справа может соответствовать одной строке слева?» — это сразу объясняет рост результата.

Когда выбирать INNER JOIN и что он отсекает?

INNER JOIN возвращает только пары строк, которые удовлетворяют условию ON.

Практический признак: если у сущности слева нет связанной записи справа, её не будет в результате вообще.

Типичный сценарий — «показать только активных»: пользователи, у которых есть заказы, или заказы, у которых есть платежи.

В каких случаях нужен LEFT JOIN и как интерпретировать NULL-ы справа?

LEFT JOIN сохраняет все строки из левой таблицы и добавляет данные справа, если пара нашлась.

Используйте его, когда левая таблица — «основа отчёта»:

  • нужно увидеть всех пользователей, включая тех, у кого 0 заказов;
  • нужно показать все заказы, даже если справочник (пользователи/товары) неполный.

Отсутствие пары справа проявляется как NULL в колонках правой таблицы.

Чем отличается фильтрация в ON и в WHERE (и почему LEFT JOIN «ломается»)?

Правило простое:

  • ON — определяет, как строятся пары строк.
  • WHERE — фильтрует готовый результат после JOIN.

Если после LEFT JOIN поставить в WHERE условие на правую таблицу (например, WHERE o.status = 'paid'), строки без пары (где o.status = NULL) исчезнут — и запрос станет похож на INNER JOIN.

Чтобы сохранить строки слева, фильтры по правой таблице чаще переносите в ON:

LEFT JOIN orders o
  ON o.user_id = u.id
 AND o.status = 'paid'
Как найти записи «есть слева, но нет справа» (anti-join)?

Самый распространённый приём — LEFT JOIN + проверка IS NULL по ключу правой таблицы.

SELECT u.id, u.email
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.user_id IS NULL;

Важно проверять именно ключ/идентификатор правой таблицы (или FK), а не произвольное поле, которое само по себе может быть NULL в данных.

Нужен ли RIGHT JOIN и чем его обычно заменяют?

RIGHT JOIN — «зеркало» LEFT JOIN: сохраняет все строки справа.

На практике его часто заменяют перестановкой таблиц и LEFT JOIN, чтобы придерживаться одного стиля:

-- эквивалент RIGHT JOIN
FROM orders o
LEFT JOIN users u ON u.id = o.user_id

Выбирайте вариант, который проще читать команде: чаще всего это «факты в FROM, справочники через LEFT JOIN».

Для чего используют FULL OUTER JOIN и как быстро найти расхождения?

FULL OUTER JOIN сохраняет строки с обеих сторон: совпавшие склеиваются, несовпавшие остаются с NULL на отсутствующей стороне.

Это удобно для сверок:

  • «есть в источнике A, но нет в B»
  • «есть в B, но нет в A»

Чтобы отфильтровать расхождения, обычно проверяют ключи:

WHERE a.id IS NULL OR b.id IS NULL
Как имитировать FULL OUTER JOIN в СУБД без поддержки (например, в MySQL)?

Если FULL OUTER JOIN недоступен, собирают результат из двух частей:

  1. «всё слева» (LEFT JOIN)
  2. «только справа» (ещё один LEFT JOIN в обратную сторону + фильтр IS NULL)

И объединяют через UNION ALL.

Ключевая идея: (всё слева) + (всё справа, чего не было слева).

Когда CROSS JOIN полезен и как не «взорвать» объём результата?

CROSS JOIN даёт декартово произведение: каждая строка первой таблицы соединяется с каждой строкой второй.

Чтобы не получить взрыв строк:

  • делайте хотя бы одну сторону маленькой (справочник, список статусов, календарь за период);
  • ограничивайте входные данные до CROSS JOIN (даты/статусы);
  • проверяйте ожидаемый размер: rows(A) × rows(B).

Частая причина «случайного» CROSS JOIN — забытое условие ON в обычном JOIN.

Что такое SELF JOIN и какие у него типичные сценарии?

SELF JOIN нужен, когда связь хранится в той же таблице (иерархия или сравнение записей).

Два частых паттерна:

  • «сотрудник → руководитель»:
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id
  • поиск дублей (и защита от зеркальных пар):
JOIN users b ON a.email = b.email AND a.id < b.id

Всегда используйте алиасы и внимательно проверяйте условие ON, чтобы не получить самосовпадения или лишние пары.

Содержание
Что такое JOIN и как читать результат соединенияINNER JOIN: берём только совпаденияLEFT JOIN: все слева, даже без парыRIGHT JOIN: все справа и как его заменитьFULL OUTER JOIN: объединяем всё и ищем расхожденияCROSS JOIN: все комбинации и контроль объёмаSELF JOIN: связи внутри одной таблицыКлючи и условия ON: как не получить лишние строкиТипичные ошибки JOIN и как их быстро найтиПроизводительность JOIN: простые и безопасные улучшенияШпаргалка и практика: закрепляем 6 JOINFAQ
Поделиться
ТакПросто.ai
Создайте свое приложение с ТакПросто сегодня!

Лучший способ понять возможности ТакПросто — попробовать самому.

Начать бесплатноЗаказать демо