JOIN в SQL: виды соединений таблиц с примерами запросов

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

INNER JOIN их отбрасывает, LEFT JOIN сохраняет все строки левой таблицы, FULL OUTER JOIN сохраняет обе стороны. В MySQL полного соединения нет, его собирают через UNION.

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

Статью написал:
Ваня Буявец, продюсер, основатель Checkroi
Ваня Буявец
Основатель Checkroi, продюсер, эксперт в выборе онлайн-курсов
Все 3100 статей автора Подписаться на Телеграм-канал
Одобрено экспертом:
Наташа Буявец, основатель Checkroi, эксперт по онлайн-курсам
Наташа Буявец
Основательница Checkroi, продюсер Youtube-каналов, эксперт по онлайн-курсам
Все 3754 экспертных мнения Подписаться на Телеграм-канал
Обложка: JOIN в SQL: виды соединений таблиц с примерами запросов

JOIN в SQL соединяет строки двух таблиц по условию: вы пишете FROM таблица1 JOIN таблица2 ON таблица1.ключ = таблица2.ключ, и база склеивает каждую строку первой таблицы с подходящими строками второй. Просто JOIN означает INNER JOIN и оставляет только пары с совпадением. Если нужны все строки одной из таблиц, даже без пары, берите LEFT JOIN.

Ниже разбираем все виды соединений на одних и тех же маленьких таблицах. Запросы работают в MySQL 8.0 и PostgreSQL 16, а там, где синтаксис расходится, это отмечено отдельно. Если вы только начинаете, сначала пригодится разбор команды SELECT от WHERE до GROUP BY, а общий обзор языка лежит в статье «Язык SQL: что это такое и зачем его учить».

CheckroiCheckroiПодборка курсов по SQL422 курса • 40 школСравните цены, школы, программу и найдите выгодные предложения по обучениюСравнить→

Как устроен JOIN: синтаксис и учебные таблицы

Рой соединяет две таблицы по общему ключу

Общая форма запроса одна для всех видов соединения:

SELECT столбцы
FROM левая_таблица AS a
[INNER | LEFT | RIGHT | FULL] JOIN правая_таблица AS b
  ON a.ключ = b.ключ;

Левая таблица стоит после FROM, правая после JOIN. Условие в ON обычно сравнивает внешний ключ одной таблицы с первичным ключом другой. Псевдонимы AS a и AS b необязательны, но без них длинные запросы читать тяжело.

В разговоре соединения называют джойнами или джоинами: «сделай джоин с заказами», «тут нужен левый джойн». Как использовать JOIN, проще всего понять на живых данных.

Для примеров возьмём три таблицы. В отделе «Склад» нет сотрудников, у Глеба не указан отдел, а у Анны два заказа:

CREATE TABLE departments (id INT PRIMARY KEY, name VARCHAR(50));
INSERT INTO departments VALUES (1, 'Продажи'), (2, 'Аналитика'), (3, 'Склад');

CREATE TABLE employees (id INT PRIMARY KEY, name VARCHAR(50), dept_id INT, manager_id INT);
INSERT INTO employees VALUES
  (1, 'Анна', 1, NULL), (2, 'Борис', 1, 1), (3, 'Вера', 2, 1), (4, 'Глеб', NULL, 3);

CREATE TABLE orders (id INT PRIMARY KEY, employee_id INT, amount INT);
INSERT INTO orders VALUES (101, 1, 5000), (102, 1, 7000), (103, 3, 4000);

Как создать такие таблицы у себя, показано в инструкциях про создание базы данных в SQL и про таблицу в pgAdmin 4.

Виды соединений таблиц: шпаргалка

Разноцветные карточки строк, разложенные по видам соединений

Если нужно быстро вспомнить, какой JOIN что возвращает, хватит этой таблицы. Подробности и вывод каждого запроса ниже.

Вид Что возвращает Строк на наших данных
INNER JOIN (или просто JOIN) только пары с совпадением 3
LEFT JOIN все строки левой таблицы, справа NULL, если пары нет 4
RIGHT JOIN все строки правой таблицы, слева NULL, если пары нет 4
FULL OUTER JOIN все строки обеих таблиц 5
CROSS JOIN каждую строку с каждой, без условия 12
SELF JOIN таблицу, соединённую сама с собой (любым из видов выше) зависит от вида

Слова INNER и OUTER можно не писать: LEFT JOIN и LEFT OUTER JOIN означают одно и то же. В документации PostgreSQL это сказано прямо: «Слова INNER и OUTER необязательны во всех формах» (раздел о табличных выражениях).

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

SELECT e.name, d.name AS dept
FROM employees AS e
INNER JOIN departments AS d ON e.dept_id = d.id
ORDER BY e.id;
 name  |   dept
-------+-----------
 Анна  | Продажи
 Борис | Продажи
 Вера  | Аналитика

Глеб пропал, потому что у него нет отдела. «Склад» тоже пропал: в нём никто не работает. Внутреннее соединение молча отбрасывает строки без пары, и это главный источник вопросов «куда делись данные».

LEFT JOIN: все строки левой таблицы

SELECT e.name, d.name AS dept
FROM employees AS e
LEFT JOIN departments AS d ON e.dept_id = d.id
ORDER BY e.id;
 name  |   dept
-------+-----------
 Анна  | Продажи
 Борис | Продажи
 Вера  | Аналитика
 Глеб  | NULL

Глеб вернулся, а в столбце отдела у него NULL. Левое соединение подходит, когда главная таблица одна и терять её строки нельзя: все клиенты и их заказы, все товары и их продажи. Что делать с этими NULL дальше, разобрано в статье о NULL в SQL.

RIGHT JOIN: зеркало LEFT

SELECT e.name, d.name AS dept
FROM employees AS e
RIGHT JOIN departments AS d ON e.dept_id = d.id
ORDER BY d.id, e.id;
 name  |   dept
-------+-----------
 Анна  | Продажи
 Борис | Продажи
 Вера  | Аналитика
 NULL  | Склад

Теперь сохранились все отделы, а Глеб выпал. Тот же результат даст LEFT JOIN, если поменять таблицы местами. Документация MySQL советует так и делать: «To keep code portable across databases, it is recommended that you use LEFT JOIN instead of RIGHT JOIN» (справка по JOIN). Запрос, где все соединения левые, ещё и читать проще.

FULL OUTER JOIN: обе таблицы целиком

Полное внешнее соединение оставляет строки обеих таблиц: и Глеба без отдела, и отдел без сотрудников. В PostgreSQL, SQL Server и SQLite оно пишется одной строкой:

SELECT e.name, d.name AS dept
FROM employees AS e
FULL OUTER JOIN departments AS d ON e.dept_id = d.id
ORDER BY d.id NULLS LAST, e.id;
 name  |   dept
-------+-----------
 Анна  | Продажи
 Борис | Продажи
 Вера  | Аналитика
 NULL  | Склад
 Глеб  | NULL

В MySQL полного соединения нет. В грамматике JOIN из справки MySQL 8.0 есть только INNER, CROSS, LEFT и RIGHT, а запрос с FULL OUTER JOIN в MySQL 8.0 падает с синтаксической ошибкой 1064. В SQLite правое и полное соединение появились только в версии 3.39.0: «Add (long overdue) support for RIGHT and FULL OUTER JOIN» (журнал изменений SQLite).

Решение для MySQL: склеить левое и правое соединение через UNION.

SELECT e.name, d.name AS dept
FROM employees AS e LEFT JOIN departments AS d ON e.dept_id = d.id
UNION
SELECT e.name, d.name
FROM employees AS e RIGHT JOIN departments AS d ON e.dept_id = d.id;

Вывод тот же, пять строк. UNION убирает повторы так же, как DISTINCT, поэтому три общие строки не задвоятся. Если в данных бывают настоящие дубли, которые нужно сохранить, понадобится UNION ALL и условие WHERE e.id IS NULL во второй половине.

Если ваша основная база MySQL, в каталоге есть отдельная подборка курсов по MySQL.

CROSS JOIN: все комбинации

SELECT COUNT(*)
FROM employees
CROSS JOIN departments;

Результат: 12, то есть четыре сотрудника умножить на три отдела. Условия у перекрёстного соединения нет, оно выдаёт декартово произведение. Пригождается редко и по делу: сетка «каждый магазин × каждый день», все сочетания размеров и цветов товара. На больших таблицах результат растёт как произведение строк, поэтому случайный CROSS JOIN на рабочих данных может надолго загрузить сервер.

SELF JOIN: таблица сама с собой

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

SELECT e.name, m.name AS manager
FROM employees AS e
LEFT JOIN employees AS m ON e.manager_id = m.id
ORDER BY e.id;
 name  | manager
-------+---------
 Анна  | NULL
 Борис | Анна
 Вера  | Анна
 Глеб  | Вера

Здесь стоит LEFT, а не INNER: у Анны руководителя нет, и внутреннее соединение выбросило бы её из списка.

Канал основателя Checkroi Вани Буявца3 700 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединиться

Как найти строки без пары

Рой с лупой радуется, что нашёл карточку без пары

Частая задача на работе и на собеседовании: найти клиентов без заказов, товары без продаж, отделы без сотрудников. Такой приём называют анти-соединением, и делается он через LEFT JOIN с проверкой на NULL:

SELECT d.name
FROM departments AS d
LEFT JOIN employees AS e ON e.dept_id = d.id
WHERE e.id IS NULL;
 name
-------
 Склад

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

Тот же ответ даёт NOT EXISTS, и многим он читается понятнее:

SELECT d.name
FROM departments AS d
WHERE NOT EXISTS (SELECT 1 FROM employees AS e WHERE e.dept_id = d.id);

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

ON, USING и NATURAL: три способа задать условие

По документации PostgreSQL, «Условие соединения указывается в предложении ON или USING, либо неявно задаётся ключевым словом NATURAL». На практике это выглядит так:

  • ON принимает любое условие: равенство ключей, несколько столбцов через AND, даже диапазон дат. Подходит всегда.
  • USING (столбец) подходит, когда ключ в обеих таблицах называется одинаково: JOIN orders USING (employee_id). В выводе такой столбец появится один раз, а не два.
  • NATURAL JOIN соединяет по всем столбцам с одинаковыми именами. В справке PostgreSQL: «NATURAL — сокращённая форма USING: она образует список USING из всех имён столбцов, существующих в обеих входных таблицах».

С NATURAL легко ошибиться. В наших таблицах employees и departments совпадают два имени, id и name, и база будет искать строки, где совпадают оба:

SELECT COUNT(*)
FROM employees
NATURAL JOIN departments;

Ответ: 0. Добавите завтра в обе таблицы столбец created_at, и запрос молча поменяет смысл. Поэтому в рабочем коде пишите ON или USING.

Как объединить три и больше таблиц

Каждое следующее соединение просто дописывается ниже. База соединяет первые две таблицы, к результату цепляет третью и так далее:

SELECT e.name, d.name AS dept, o.amount
FROM employees AS e
JOIN departments AS d ON e.dept_id = d.id
JOIN orders AS o ON o.employee_id = e.id
ORDER BY o.id;
 name |   dept    | amount
------+-----------+--------
 Анна | Продажи   |   5000
 Анна | Продажи   |   7000
 Вера | Аналитика |   4000

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

Отчёт из трёх-пяти таблиц для SQL-аналитика обычная ежедневная задача, и почти каждая ошибка в нём связана с порядком соединений.

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

Условие в ON или в WHERE

Для INNER JOIN разницы нет.

В справке Microsoft по предложению FROM это сформулировано так: «Хотя размещение таких предикатов не имеет разницы для внутренних соединений, они могут привести к другому результату при использовании внешних соединений».

Для LEFT JOIN она меняет результат. Задача: показать всех сотрудников и их заказы дороже 4 500 ₽.

Фильтр в WHERE:

SELECT e.name, o.amount
FROM employees AS e
LEFT JOIN orders AS o ON o.employee_id = e.id
WHERE o.amount > 4500
ORDER BY e.id, o.id;
 name | amount
------+--------
 Анна |   5000
 Анна |   7000

Остальные сотрудники исчезли. WHERE срабатывает после соединения (порядок выполнения частей запроса разобран в статье про SELECT), а у тех, кому пары не нашлось, amount равен NULL, и сравнение NULL > 4500 не проходит. Левое соединение по факту стало внутренним.

Решение: перенести условие в ON.

SELECT e.name, o.amount
FROM employees AS e
LEFT JOIN orders AS o ON o.employee_id = e.id AND o.amount > 4500
ORDER BY e.id, o.id;
 name  | amount
-------+--------
 Анна  |   5000
 Анна  |   7000
 Борис |   NULL
 Вера  |   NULL
 Глеб  |   NULL

Правило простое. Условие про то, какие строки правой таблицы подцеплять, пишите в ON. Условие про то, какие строки оставить в итоге, пишите в WHERE.

Ваня БуявецКанал основателя Checkroi Вани БуявцаЗабирайте промпты и обучение по нейросетям в моём Телеграм-каналеБольше 3 700 человек уже применяют Claude Code, ChatGPT и другие нейросети в работе, учёбе, бизнесе и жизниПерейти в канал

Частые ошибки и что с ними делать

  1. Неоднозначный столбец. SELECT name FROM employees JOIN departments ON … падает: name есть в обеих таблицах. MySQL пишет ERROR 1052 (23000): Column 'name' in field list is ambiguous, PostgreSQL пишет column reference "name" is ambiguous. Решение: указать таблицу или псевдоним, e.name.
  2. Задвоенные суммы и счётчики. После соединения с таблицей «многие» строки размножаются. Запрос «сколько сотрудников с заказами в отделе» через COUNT(e.id) покажет у «Продаж» 2, хотя с заказами там одна Анна. Помогает COUNT(DISTINCT e.id) или агрегирование заказов в подзапросе до соединения. Варианты подсчёта подробно разобраны в статье о функции COUNT.
  3. COUNT(*) после LEFT JOIN. Для отдела без сотрудников COUNT(*) вернёт 1: строка-то в результате есть, просто заполнена NULL. COUNT(e.id) вернёт честный 0.
  4. NULL в ключах не соединяются. Две строки с NULL в столбце соединения не считаются парой. В справке Microsoft: «Если в столбцах таблиц, которые объединяются, есть значения NULL, то эти значения не совпадают друг с другом» (статья «Соединения» для SQL Server).
  5. Забытое ON. В PostgreSQL FROM employees JOIN departments без условия даёт синтаксическую ошибку. MySQL такой запрос выполнит и вернёт все комбинации: в его справке сказано, что JOIN, CROSS JOIN и INNER JOIN «are syntactic equivalents». На двух таблицах по миллиону строк это триллион строк результата.

JOIN или UNION: в чём разница

Оба называют «объединением таблиц», но делают разное:

JOIN UNION
Как складывает данные по горизонтали: к строке добавляются столбцы другой таблицы по вертикали: строки второго запроса идут под строками первого
Что нужно условие связи (ключ) одинаковое число и совместимые типы столбцов
Пример сотрудник + название его отдела клиенты из двух филиалов одним списком

Если нужно приписать к записи сведения из другой таблицы, это JOIN. Если два одинаковых по структуре списка нужно поставить друг под другом, это UNION. Для подсчётов по группам внутри соединённых данных пригодятся оконные функции, а для меток по условию оператор CASE.

Где освоить SQL системно

На соединениях проверяют и стажёров, и аналитиков с опытом: без них не собрать отчёт из нормализованной базы. Чтобы пройти путь от SELECT до оптимизации запросов с практикой и проверкой заданий, посмотрите курсы по SQL в нашем каталоге. Ниже пять программ с ценами и сроками, сравнить их можно на странице подборки.

КурсШколаСтоимость со скидкойВ рассрочкуДлитель­ностьОбзор курса от Checkroi
Нейросети для рабочих задач
Перейти на сайт курса
SkillboxSkillbox31 290 ₽ - 1 месяцОбзор курса
SQL для анализа данных
Перейти на сайт курса
SkillboxSkillbox52 353 ₽6203 ₽/мес.2 месяцаОбзор курса
SQL-разработчик: тариф PRO
Перейти на сайт курса
Академия ЭдюсонЭдюсон89 900 ₽7491 ₽/мес.2 месяцаОбзор курса
SQL-разработчик: тариф Базовый
Перейти на сайт курса
Академия ЭдюсонЭдюсон79 900 ₽3329 ₽/мес.3 месяцаОбзор курса
SQL с нуляSkyproSkypro134 640 ₽5500 ₽/мес.12 месяцевОбзор курса

Больше программ — в полном каталоге курсов по SQL

Если хотите работать с базами данных профессионально, загляните в план «Как стать SQL-разработчиком»: там расписано, что учить по месяцам и с чем идти на первое собеседование.

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

Что делает JOIN в SQL?

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

Чем JOIN отличается от LEFT JOIN?

Просто JOIN означает INNER JOIN и оставляет только строки, у которых нашлась пара. LEFT JOIN сохраняет все строки левой таблицы, а там, где пары нет, ставит NULL в столбцы правой.

Какие виды JOIN бывают?

Основных пять: INNER, LEFT, RIGHT, FULL OUTER и CROSS. Ещё говорят о SELF JOIN, когда таблицу соединяют саму с собой, но это обычное соединение с двумя псевдонимами одной таблицы.

Как объединить две таблицы в SQL?

Если нужно добавить к строкам столбцы из другой таблицы, используйте JOIN … ON по общему ключу. Если нужно поставить строки двух одинаковых по структуре таблиц друг под другом, используйте UNION или UNION ALL.

Есть ли FULL OUTER JOIN в MySQL?

Нет, в MySQL 8.0 такой запрос завершится синтаксической ошибкой. Полное соединение собирают из LEFT JOIN и RIGHT JOIN, склеенных через UNION. В PostgreSQL, SQL Server и SQLite начиная с версии 3.39.0 FULL OUTER JOIN работает напрямую.

Почему после JOIN строк стало больше?

Из-за связи «один ко многим»: строка повторяется столько раз, сколько у неё пар во второй таблице. Сотрудник с двумя заказами даст две строки. Для подсчётов используйте COUNT(DISTINCT …) или агрегируйте вторую таблицу до соединения.

Читайте Checkroi первым в Google
Добавьте Checkroi в избранные источники — и наши разборы курсов и обзоры школ будут показываться выше в вашей выдаче Google.
Оставить комментарий
0 комментариев
Форма комментария

Оставьте комментарий

Напишите, что думаете. Нам важно ваше мнение!