INNER JOIN (или просто JOIN) — это команда для соединения двух таблиц по общему полю. Самый базовый и самый частый тип JOIN, без него в реляционных БД никуда.
Простая аналогия. У тебя есть две таблицы:
users — пользователи (id, name)
orders — заказы (id, user_id, amount)
В orders нет имени покупателя — там только user_id. Чтобы получить пары «имя покупателя — сумма заказа», нужно «склеить» эти таблицы по общему полю. Этот общий ключ — users.id и orders.user_id. И склейка делается через INNER JOIN.
Зачем нужен JOIN
В нормальной БД данные разбиты по таблицам, чтобы не дублировать информацию. Имя пользователя хранится только в users. В orders — только user_id, ссылающийся на пользователя. Это называется нормализация.
Когда нужно показать «имя + заказ» — приходится собирать обратно. Для этого и есть JOIN. Это одна из самых важных тем SQL — без понимания JOIN ты не напишешь ни одного реального запроса в проекте.
Базовый синтаксис
SELECT колонки
FROM таблица_A
INNER JOIN таблица_B
ON таблица_A.ключ = таблица_B.ключ;
Слово INNER можно опускать — JOIN без префикса означает именно INNER JOIN. Часто пишут просто JOIN.
Пример: магазин
Таблица users:
Таблица orders:
| id |
user_id |
amount |
created_at |
| 10 |
1 |
500 |
2024-03-01 |
| 11 |
1 |
1500 |
2024-03-05 |
| 12 |
2 |
200 |
2024-03-07 |
| 13 |
5 |
3000 |
2024-03-10 |
Запрос — соединить и показать имя покупателя с суммой:
SELECT users.name, orders.amount, orders.created_at
FROM users
INNER JOIN orders
ON users.id = orders.user_id;
Результат:
| name |
amount |
created_at |
| Анна |
500 |
2024-03-01 |
| Анна |
1500 |
2024-03-05 |
| Борис |
200 |
2024-03-07 |
Что произошло:
- Каждая строка из
users спарилась с каждой строкой из orders, у которой users.id = orders.user_id.
- У Анны два заказа → две строки в результате, имя дублируется.
- У Бориса один заказ → одна строка.
- Вера и Григорий не попали — у них нет заказов в
orders. Это и есть особенность INNER JOIN: только пары, у которых есть совпадение в обеих таблицах.
- Заказ #13 не попал —
user_id = 5 не существует в users. Так бывает, например, когда пользователь удалён, а заказ остался.
Если хочется тоже показать пользователей без заказов — это уже не INNER JOIN, а LEFT JOIN (отдельная статья).
Алиасы — короткие имена таблиц
Каждый раз писать users.name, orders.amount — длинно. Поэтому в реальном коде используют алиасы:
SELECT u.name, o.amount, o.created_at
FROM users u
INNER JOIN orders o
ON u.id = o.user_id;
users u означает «дай таблице users псевдоним u». Это ничего не меняет в логике — просто короче. На больших запросах с тремя-пятью таблицами без алиасов читать невозможно.
Примечание: алиасы становятся обязательными, если в двух таблицах есть колонки с одинаковыми именами. Иначе база не поймёт, чей id ты имеешь в виду.
JOIN трёх и более таблиц
JOIN-ы можно цеплять цепочкой: A JOIN B JOIN C. Например, добавим таблицу products:
| id |
name |
price |
| 100 |
Чайник Bosch |
4500 |
| 101 |
iPhone 15 |
90000 |
| 102 |
Книга «Чистый код» |
2000 |
И изменим orders, добавив product_id:
| id |
user_id |
product_id |
amount |
| 10 |
1 |
100 |
4500 |
| 11 |
1 |
102 |
2000 |
| 12 |
2 |
100 |
4500 |
Запрос — кто что купил:
SELECT u.name AS buyer, p.name AS product, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id
INNER JOIN products p ON p.id = o.product_id;
Результат:
| buyer |
product |
amount |
| Анна |
Чайник Bosch |
4500 |
| Анна |
Книга «Чистый код» |
2000 |
| Борис |
Чайник Bosch |
4500 |
Как это работает:
- Сначала база соединяет
users и orders по user_id.
- Потом — результат соединения с
products по product_id.
- На выходе — три строки, где в обеих JOIN-парах есть совпадения.
Таких цепочек может быть сколько угодно. Запрос «пользователи, их заказы, товары в заказах, категории товаров и магазины» — это пять JOIN-ов подряд.
JOIN с условием WHERE
JOIN обычно идёт вместе с WHERE — JOIN склеивает таблицы, WHERE фильтрует результат. Например, заказы Анны на сумму больше 1000:
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.name = 'Анна'
AND o.amount > 1000;
Крайне частая комбинация — почти любой бизнес-запрос состоит из «соединить таблицы JOIN-ом + отфильтровать WHERE-ом + посчитать агрегат».
INNER JOIN — это пересечение
Если мыслить теорией множеств: INNER JOIN — это пересечение двух таблиц по ключу. В результат попадает только то, что есть И там, И там.
users orders
┌──┐ ┌──┐
│ │ │ │
│ ●│ ← → │● │ ← пары: только эти попадают в результат
│ │ │ │
└──┘ └──┘
Если у пользователя нет заказов — он не в результате. Если у заказа нет пользователя (FK битый) — заказ не в результате.
Большой пример: библиотека
books:
| id |
title |
author_id |
| 1 |
Война и мир |
10 |
| 2 |
Анна Каренина |
10 |
| 3 |
Преступление и наказание |
11 |
| 4 |
Идиот |
11 |
| 5 |
(без автора в БД) |
99 |
authors:
| id |
full_name |
country |
| 10 |
Лев Толстой |
Россия |
| 11 |
Фёдор Достоевский |
Россия |
| 12 |
Эрих Мария Ремарк |
Германия |
Запрос — список всех книг с именем автора и страной:
SELECT b.title, a.full_name AS author, a.country
FROM books b
JOIN authors a ON a.id = b.author_id
ORDER BY a.full_name, b.title;
Результат:
| title |
author |
country |
| Война и мир |
Лев Толстой |
Россия |
| Анна Каренина |
Лев Толстой |
Россия |
| Идиот |
Фёдор Достоевский |
Россия |
| Преступление и наказание |
Фёдор Достоевский |
Россия |
«(без автора в БД)» не попала — у неё author_id = 99, а в authors такого нет. Ремарка не попал — у него нет ни одной книги в нашей books. INNER JOIN отфильтровал и тех, и других.
Частые ошибки новичков
1. Забыть условие ON. Если написать JOIN orders без ON, многие БД либо ругнутся, либо сделают CROSS JOIN — декартово произведение всех строк (миллион × миллион). Это либо ошибка, либо очень-очень медленный запрос.
2. Условие в ON не на тех колонках. ON u.id = o.id вместо ON u.id = o.user_id — частая опечатка. Запрос не упадёт, но вернёт ерунду — сравнятся user.id = order.id (то есть пользователь №5 склеится с заказом №5, что бессмысленно).
3. Двусмысленные имена колонок. Если и users, и orders имеют колонку id, и ты пишешь SELECT id, name FROM users JOIN orders ON … — будет ошибка «column reference "id" is ambiguous». Используй users.id или алиасы.
4. Дубликаты в результате. Если у пользователя 5 заказов, в результате будет 5 строк с одним и тем же именем. Это не баг JOIN-а, а его суть. Нужны уникальные пользователи — оборачивай в SELECT DISTINCT u.name или комбинируй с GROUP BY.
5. Думать, что JOIN сам по себе медленный. Сами по себе JOIN-ы быстрые, если есть индексы на колонках, по которым джойнят. Поэтому user_id и подобные FK-колонки в продакшене всегда индексируют.
6. Путать JOIN и WHERE. Технически в старом синтаксисе можно было писать FROM users, orders WHERE users.id = orders.user_id. Так делать не надо — современный синтаксис JOIN ... ON ... читается лучше и явно различает фильтрацию и соединение.
Мини-резюме
INNER JOIN соединяет две таблицы по общему ключу.
- В результат попадают только пары, у которых есть совпадение в обеих таблицах.
- Условие соединения — после
ON.
- Слово
INNER опционально, обычно пишут просто JOIN.
- Можно цеплять много JOIN-ов подряд (трёх- и пятитабличные запросы — норма).
- Для удобства используй алиасы (
users u).
- Если нужны и пользователи без заказов — это
LEFT JOIN, отдельный сценарий.
INNER JOIN(или простоJOIN) — это команда для соединения двух таблиц по общему полю. Самый базовый и самый частый тип JOIN, без него в реляционных БД никуда.Простая аналогия. У тебя есть две таблицы:
users— пользователи (id, name)orders— заказы (id, user_id, amount)В
ordersнет имени покупателя — там толькоuser_id. Чтобы получить пары «имя покупателя — сумма заказа», нужно «склеить» эти таблицы по общему полю. Этот общий ключ —users.idиorders.user_id. И склейка делается черезINNER JOIN.Зачем нужен JOIN
В нормальной БД данные разбиты по таблицам, чтобы не дублировать информацию. Имя пользователя хранится только в
users. Вorders— толькоuser_id, ссылающийся на пользователя. Это называется нормализация.Когда нужно показать «имя + заказ» — приходится собирать обратно. Для этого и есть JOIN. Это одна из самых важных тем SQL — без понимания JOIN ты не напишешь ни одного реального запроса в проекте.
Базовый синтаксис
SELECT колонки FROM таблица_A INNER JOIN таблица_B ON таблица_A.ключ = таблица_B.ключ;Слово
INNERможно опускать —JOINбез префикса означает именноINNER JOIN. Часто пишут простоJOIN.Пример: магазин
Таблица
users:Таблица
orders:Запрос — соединить и показать имя покупателя с суммой:
SELECT users.name, orders.amount, orders.created_at FROM users INNER JOIN orders ON users.id = orders.user_id;Результат:
Что произошло:
usersспарилась с каждой строкой изorders, у которойusers.id = orders.user_id.orders. Это и есть особенность INNER JOIN: только пары, у которых есть совпадение в обеих таблицах.user_id = 5не существует вusers. Так бывает, например, когда пользователь удалён, а заказ остался.Если хочется тоже показать пользователей без заказов — это уже не INNER JOIN, а LEFT JOIN (отдельная статья).
Алиасы — короткие имена таблиц
Каждый раз писать
users.name,orders.amount— длинно. Поэтому в реальном коде используют алиасы:SELECT u.name, o.amount, o.created_at FROM users u INNER JOIN orders o ON u.id = o.user_id;users uозначает «дай таблицеusersпсевдонимu». Это ничего не меняет в логике — просто короче. На больших запросах с тремя-пятью таблицами без алиасов читать невозможно.Примечание: алиасы становятся обязательными, если в двух таблицах есть колонки с одинаковыми именами. Иначе база не поймёт, чей
idты имеешь в виду.JOIN трёх и более таблиц
JOIN-ы можно цеплять цепочкой: A JOIN B JOIN C. Например, добавим таблицу
products:И изменим
orders, добавивproduct_id:Запрос — кто что купил:
SELECT u.name AS buyer, p.name AS product, o.amount FROM users u INNER JOIN orders o ON u.id = o.user_id INNER JOIN products p ON p.id = o.product_id;Результат:
Как это работает:
usersиordersпоuser_id.productsпоproduct_id.Таких цепочек может быть сколько угодно. Запрос «пользователи, их заказы, товары в заказах, категории товаров и магазины» — это пять JOIN-ов подряд.
JOIN с условием WHERE
JOINобычно идёт вместе сWHERE— JOIN склеивает таблицы, WHERE фильтрует результат. Например, заказы Анны на сумму больше 1000:SELECT u.name, o.amount FROM users u JOIN orders o ON u.id = o.user_id WHERE u.name = 'Анна' AND o.amount > 1000;Крайне частая комбинация — почти любой бизнес-запрос состоит из «соединить таблицы JOIN-ом + отфильтровать WHERE-ом + посчитать агрегат».
INNER JOIN — это пересечение
Если мыслить теорией множеств: INNER JOIN — это пересечение двух таблиц по ключу. В результат попадает только то, что есть И там, И там.
Если у пользователя нет заказов — он не в результате. Если у заказа нет пользователя (FK битый) — заказ не в результате.
Большой пример: библиотека
books:authors:Запрос — список всех книг с именем автора и страной:
SELECT b.title, a.full_name AS author, a.country FROM books b JOIN authors a ON a.id = b.author_id ORDER BY a.full_name, b.title;Результат:
«(без автора в БД)» не попала — у неё
author_id = 99, а вauthorsтакого нет. Ремарка не попал — у него нет ни одной книги в нашейbooks. INNER JOIN отфильтровал и тех, и других.Частые ошибки новичков
1. Забыть условие ON. Если написать
JOIN ordersбезON, многие БД либо ругнутся, либо сделают CROSS JOIN — декартово произведение всех строк (миллион × миллион). Это либо ошибка, либо очень-очень медленный запрос.2. Условие в ON не на тех колонках.
ON u.id = o.idвместоON u.id = o.user_id— частая опечатка. Запрос не упадёт, но вернёт ерунду — сравнятсяuser.id = order.id(то есть пользователь №5 склеится с заказом №5, что бессмысленно).3. Двусмысленные имена колонок. Если и
users, иordersимеют колонкуid, и ты пишешьSELECT id, name FROM users JOIN orders ON …— будет ошибка «column reference "id" is ambiguous». Используйusers.idили алиасы.4. Дубликаты в результате. Если у пользователя 5 заказов, в результате будет 5 строк с одним и тем же именем. Это не баг JOIN-а, а его суть. Нужны уникальные пользователи — оборачивай в
SELECT DISTINCT u.nameили комбинируй сGROUP BY.5. Думать, что
JOINсам по себе медленный. Сами по себе JOIN-ы быстрые, если есть индексы на колонках, по которым джойнят. Поэтомуuser_idи подобные FK-колонки в продакшене всегда индексируют.6. Путать JOIN и WHERE. Технически в старом синтаксисе можно было писать
FROM users, orders WHERE users.id = orders.user_id. Так делать не надо — современный синтаксисJOIN ... ON ...читается лучше и явно различает фильтрацию и соединение.Мини-резюме
INNER JOINсоединяет две таблицы по общему ключу.ON.INNERопционально, обычно пишут простоJOIN.users u).LEFT JOIN, отдельный сценарий.