SQLJOININNER JOINtutorial

Что такое INNER JOIN в SQL?

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

13 мин чтенияСправочникSQL · JOIN · INNER JOIN · tutorial · beginner

INNER JOIN — это способ соединить две таблицы по общему полю и оставить только те строки, для которых нашлась пара в обеих таблицах.

Чаще всего слово INNER даже не пишут. В SQL обычный JOIN почти всегда означает именно INNER JOIN.

Представь интернет-магазин. Есть таблица пользователей:

  • users — кто зарегистрировался на сайте;
  • orders — какие заказы были оформлены.

В таблице orders обычно не хранят имя покупателя. Там хранится только user_id — ссылка на пользователя из таблицы users.

Почему так? Чтобы не дублировать имя, email и другие данные пользователя в каждом заказе.

Но когда нам нужен отчёт «покупатель — сумма заказа», данные приходится собрать обратно. Вот здесь и нужен INNER JOIN.

Зачем нужен JOIN

В хорошей реляционной базе данные обычно разложены по разным таблицам.

Например:

  • данные пользователя лежат в users;
  • заказы лежат в orders;
  • товары лежат в products;
  • категории товаров лежат в categories;
  • платежи лежат в payments.

Так база не хранит одно и то же по сто раз.

Если пользователь сделал 20 заказов, его имя не нужно копировать в каждую строку заказа. Достаточно хранить имя один раз в users, а в orders записывать только user_id.

Но в реальных задачах почти всегда нужно смотреть данные вместе:

  • имя пользователя и его заказы;
  • товар и его категория;
  • заказ и статус платежа;
  • сотрудник и название отдела;
  • книга и имя автора.

Для этого таблицы соединяют через JOIN.

Простая идея INNER JOIN

INNER JOIN ищет совпадения между двумя таблицами.

Например:

  • в users.id лежит идентификатор пользователя;
  • в orders.user_id лежит идентификатор пользователя, который сделал заказ.

Если users.id = orders.user_id, значит эти строки связаны.

SQL берёт строку пользователя, находит подходящие строки заказов и склеивает их в одну результирующую строку.

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

Это главное свойство INNER JOIN:

В результат попадают только те строки, у которых есть совпадение в обеих таблицах.

Базовый синтаксис

Общий вид такой:

SELECT
  columns
FROM table_a
INNER JOIN table_b
  ON table_a.key = table_b.key;

Слово INNER можно опустить:

SELECT
  columns
FROM table_a
JOIN table_b
  ON table_a.key = table_b.key;

Оба варианта означают одно и то же.

В реальном коде чаще пишут просто JOIN, потому что короче.

Пример с пользователями и заказами

Есть таблица users:

id name email
1 Anna anna@example.com
2 Boris boris@example.com
3 Vera vera@example.com
4 Gleb gleb@example.com

Есть таблица orders:

id user_id amount created_at
10 1 500 2026-03-01
11 1 1500 2026-03-05
12 2 200 2026-03-07
13 5 3000 2026-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
Anna 500 2026-03-01
Anna 1500 2026-03-05
Boris 200 2026-03-07

Что произошло:

  • у Anna два заказа, поэтому она появилась в результате два раза;
  • у Boris один заказ, поэтому он появился один раз;
  • Vera и Gleb не попали в результат, потому что у них нет заказов;
  • заказ с user_id = 5 не попал в результат, потому что пользователя с id = 5 нет в users.

Это и есть поведение INNER JOIN: показывать только совпавшие пары.

Как SQL соединяет строки

Возьмём первую строку из users:

id name
1 Anna

SQL смотрит в orders и ищет строки, где orders.user_id = 1.

Находит две строки:

id user_id amount
10 1 500
11 1 1500

Значит, в результате будет две строки:

name amount
Anna 500
Anna 1500

Теперь берём пользователя Boris с id = 2.

В orders есть один заказ с user_id = 2, поэтому будет одна строка.

А у Vera с id = 3 заказов нет. Совпадения нет — строки в результате тоже нет.

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

Очень важно: INNER JOIN не показывает «одинокие» строки.

Если пользователь есть, но заказов нет, он не попадёт в результат.

Если заказ есть, но пользователя для него нет, этот заказ тоже не попадёт в результат.

Можно представить так:

Ситуация Попадёт в результат INNER JOIN?
Пользователь есть и заказ есть Да
Пользователь есть, заказа нет Нет
Заказ есть, пользователя нет Нет

Поэтому INNER JOIN удобно использовать, когда тебе нужны только связанные данные.

Например:

  • только пользователи, у которых есть заказы;
  • только товары, у которых есть категория;
  • только платежи, у которых есть заказ;
  • только книги, у которых есть автор в базе.

Если нужно показать пользователей даже без заказов, нужен уже не INNER JOIN, а LEFT JOIN.

Алиасы таблиц

В реальных запросах редко пишут полные имена таблиц каждый раз.

Вот так работает, но выглядит длинно:

SELECT
  users.name,
  orders.amount,
  orders.created_at
FROM users
JOIN orders
  ON users.id = orders.user_id;

Обычно используют алиасы — короткие имена таблиц:

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

Здесь:

  • users u означает: таблицу users дальше называем u;
  • orders o означает: таблицу orders дальше называем o.

Логика запроса не меняется. Просто он становится короче и удобнее для чтения.

Особенно алиасы полезны, когда в запросе три, четыре или пять таблиц.

Зачем указывать имя таблицы перед колонкой

После JOIN в результате участвуют колонки сразу из нескольких таблиц.

Часто в разных таблицах есть одинаковые названия колонок.

Например, и в users, и в orders есть колонка id.

Если написать так:

SELECT
  id,
  name,
  amount
FROM users
JOIN orders
  ON users.id = orders.user_id;

база может не понять, какой именно id нужен: users.id или orders.id.

Поэтому лучше писать явно:

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

Так запрос читается без догадок:

  • u.id — идентификатор пользователя;
  • o.id — идентификатор заказа.

JOIN трёх таблиц

JOIN можно цеплять один за другим.

Допустим, у нас есть три таблицы:

  • users — пользователи;
  • orders — заказы;
  • products — товары.

Таблица users:

id name
1 Anna
2 Boris

Таблица products:

id name price
100 Kettle 4500
101 Phone 90000
102 Book 2000

Таблица orders:

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
JOIN orders o
  ON u.id = o.user_id
JOIN products p
  ON p.id = o.product_id;

Результат:

buyer product amount
Anna Kettle 4500
Anna Book 2000
Boris Kettle 4500

Что произошло:

  1. users соединилась с orders по пользователю.
  2. Потом результат соединился с products по товару.
  3. На выходе получились строки, где есть и покупатель, и заказ, и товар.

Таких соединений может быть много. В реальных проектах запросы с несколькими JOIN — обычное дело.

JOIN и WHERE

JOIN соединяет таблицы.

WHERE фильтрует результат.

Например, нужно найти заказы Anna на сумму больше 1000.

SELECT
  u.name,
  o.amount,
  o.created_at
FROM users u
JOIN orders o
  ON u.id = o.user_id
WHERE u.name = 'Anna'
  AND o.amount > 1000;

Здесь:

  • JOIN склеивает пользователей с заказами;
  • WHERE оставляет только строки, где имя Anna и сумма больше 1000.

Результат:

name amount created_at
Anna 1500 2026-03-05

В реальной аналитике запрос часто строится именно так:

  1. соединить нужные таблицы;
  2. отфильтровать лишние строки;
  3. отсортировать или посчитать агрегаты.

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

После JOIN всегда есть важная часть — ON.

FROM users u
JOIN orders o
  ON u.id = o.user_id

ON объясняет базе, как именно связаны таблицы.

Это не просто фильтр. Это правило соединения.

Если написать неправильное условие, результат будет неправильным.

Например, правильно:

SELECT
  u.name,
  o.amount
FROM users u
JOIN orders o
  ON u.id = o.user_id;

А вот так почти наверняка ошибка:

SELECT
  u.name,
  o.amount
FROM users u
JOIN orders o
  ON u.id = o.id;

Почему?

Потому что u.id — это идентификатор пользователя, а o.id — идентификатор заказа. Они могут случайно совпасть по числам, но логической связи между ними нет.

Правильная связь обычно такая:

первичный ключ одной таблицы соединяется с внешним ключом другой таблицы.

В нашем случае:

  • users.id — первичный ключ пользователя;
  • orders.user_id — внешний ключ на пользователя.

INNER JOIN и дубликаты

Новички часто удивляются: почему после JOIN стало больше строк?

Например, в таблице users пользователь Anna один:

id name
1 Anna

Но в таблице orders у неё два заказа:

id user_id amount
10 1 500
11 1 1500

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

name amount
Anna 500
Anna 1500

Это не ошибка.

JOIN показывает пары. Если у одной строки слева нашлось несколько строк справа, строк в результате тоже будет несколько.

Если тебе нужны не заказы, а уникальные пользователи, можно использовать DISTINCT:

SELECT DISTINCT
  u.id,
  u.name
FROM users u
JOIN orders o
  ON u.id = o.user_id;

А если нужно посчитать количество заказов у каждого пользователя, нужен GROUP BY:

SELECT
  u.id,
  u.name,
  COUNT(*) AS orders_count
FROM users u
JOIN orders o
  ON u.id = o.user_id
GROUP BY u.id, u.name;

Результат:

id name orders_count
1 Anna 2
2 Boris 1

Пример: книги и авторы

Посмотрим на другой пример — библиотеку.

Таблица books:

id title author_id
1 War and Peace 10
2 Anna Karenina 10
3 Crime and Punishment 11
4 The Idiot 11
5 Unknown Book 99

Таблица authors:

id full_name country
10 Leo Tolstoy Russia
11 Fyodor Dostoevsky Russia
12 Erich Maria Remarque Germany

Нужно вывести книги вместе с именем автора и страной.

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
Anna Karenina Leo Tolstoy Russia
War and Peace Leo Tolstoy Russia
Crime and Punishment Fyodor Dostoevsky Russia
The Idiot Fyodor Dostoevsky Russia

Книга Unknown Book не попала в результат, потому что у неё author_id = 99, а автора с id = 99 нет в authors.

Автор Erich Maria Remarque тоже не попал, потому что в таблице books у него нет книг.

Так работает INNER JOIN: в результате остаются только совпадения.

INNER JOIN и связи один-ко-многим

Очень часто INNER JOIN используют для связи один-ко-многим.

Например:

  • один пользователь может иметь много заказов;
  • один автор может иметь много книг;
  • одна категория может иметь много товаров;
  • один отдел может иметь много сотрудников.

В такой связи одна строка из первой таблицы может соединиться с несколькими строками из второй таблицы.

Пример:

SELECT
  c.name AS category,
  p.name AS product
FROM categories c
JOIN products p
  ON c.id = p.category_id;

Если в категории Books лежит 10 товаров, то категория Books появится в результате 10 раз — по одному разу рядом с каждым товаром.

Это нормальное поведение.

INNER JOIN и связи многие-ко-многим

Иногда между сущностями связь многие-ко-многим.

Например:

  • один студент может ходить на много курсов;
  • один курс может иметь много студентов.

Для такой связи обычно делают промежуточную таблицу.

Таблица students:

id name
1 Anna
2 Boris

Таблица courses:

id title
10 SQL Basics
20 Python Basics

Таблица student_courses:

student_id course_id
1 10
1 20
2 10

Нужно получить список: студент — курс.

SELECT
  s.name AS student,
  c.title AS course
FROM students s
JOIN student_courses sc
  ON sc.student_id = s.id
JOIN courses c
  ON c.id = sc.course_id
ORDER BY s.name, c.title;

Результат:

student course
Anna Python Basics
Anna SQL Basics
Boris SQL Basics

Здесь student_courses — таблица-связка. Она помогает соединить студентов и курсы.

Старый синтаксис через WHERE

Иногда можно встретить старую запись без явного JOIN:

SELECT
  u.name,
  o.amount
FROM users u, orders o
WHERE u.id = o.user_id;

Она делает то же самое, что и:

SELECT
  u.name,
  o.amount
FROM users u
JOIN orders o
  ON u.id = o.user_id;

Но современный вариант с JOIN ... ON лучше.

Почему?

Потому что он явно разделяет две вещи:

  • ON — как соединяются таблицы;
  • WHERE — какие строки нужно отфильтровать.

Такой запрос проще читать, проверять и поддерживать.

Для обучения и реальной работы лучше сразу привыкать к JOIN ... ON.

Что будет, если забыть ON

Одна из самых опасных ошибок — забыть условие соединения.

В некоторых базах такой запрос либо не выполнится, либо превратится в декартово произведение.

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

Если в users 1000 строк, а в orders 10000 строк, результат может содержать 10 миллионов строк.

Обычно это не то, что ты хотел.

Правильный запрос:

SELECT
  u.name,
  o.amount
FROM users u
JOIN orders o
  ON u.id = o.user_id;

Опасная идея:

SELECT
  u.name,
  o.amount
FROM users u
CROSS JOIN orders o;

CROSS JOIN нужен редко и осознанно. Для обычного соединения таблиц почти всегда нужен JOIN с условием ON.

Производительность INNER JOIN

Сам по себе JOIN — не что-то медленное или страшное.

Базы данных умеют хорошо соединять таблицы. Но им нужны нормальные условия и индексы.

Обычно индексируют колонки, по которым часто соединяют таблицы:

  • orders.user_id;
  • products.category_id;
  • books.author_id;
  • payments.order_id.

Например:

CREATE INDEX idx_orders_user_id
ON orders (user_id);

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

В реальных проектах внешние ключи и индексы на них — очень частая и важная история.

Когда использовать INNER JOIN

Используй INNER JOIN, когда тебе нужны только строки, у которых есть пара в другой таблице.

Например:

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

Хороший пример:

SELECT
  u.name,
  o.amount
FROM users u
JOIN orders o
  ON u.id = o.user_id;

Этот запрос не пытается показать всех пользователей. Он показывает только пользователей с заказами.

Когда INNER JOIN не подходит

INNER JOIN не подходит, если нужно сохранить строки без пары.

Например:

Покажи всех пользователей, даже если они ничего не заказали.

Для такой задачи нужен LEFT JOIN.

Если использовать INNER JOIN, пользователи без заказов исчезнут из результата.

Сравни идею:

SELECT
  u.name,
  o.amount
FROM users u
JOIN orders o
  ON u.id = o.user_id;

Этот запрос покажет только пользователей с заказами.

А вот такой запрос уже покажет всех пользователей:

SELECT
  u.name,
  o.amount
FROM users u
LEFT JOIN orders o
  ON u.id = o.user_id;

У пользователей без заказов в колонках заказа будет NULL.

Частые ошибки новичков

Ошибка 1. Соединить не те колонки

Плохой вариант:

SELECT
  u.name,
  o.amount
FROM users u
JOIN orders o
  ON u.id = o.id;

Здесь сравнивается идентификатор пользователя с идентификатором заказа. Обычно это бессмысленно.

Правильно:

SELECT
  u.name,
  o.amount
FROM users u
JOIN orders o
  ON u.id = o.user_id;

Нужно соединять первичный ключ одной таблицы с внешним ключом другой.

Ошибка 2. Забыть уточнить, из какой таблицы колонка

Плохой вариант:

SELECT
  id,
  name,
  amount
FROM users u
JOIN orders o
  ON u.id = o.user_id;

Если id есть и в users, и в orders, база не поймёт, какой id нужен.

Лучше:

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

Ошибка 3. Удивляться дубликатам

Если у пользователя 5 заказов, после JOIN он появится 5 раз.

Это нормально.

Если нужны уникальные пользователи:

SELECT DISTINCT
  u.id,
  u.name
FROM users u
JOIN orders o
  ON u.id = o.user_id;

Если нужно количество заказов:

SELECT
  u.id,
  u.name,
  COUNT(*) AS orders_count
FROM users u
JOIN orders o
  ON u.id = o.user_id
GROUP BY u.id, u.name;

Ошибка 4. Использовать INNER JOIN, когда нужен LEFT JOIN

Если задача звучит так:

Покажи всех пользователей и их заказы, если они есть.

INNER JOIN не подойдёт, потому что он уберёт пользователей без заказов.

Нужен LEFT JOIN.

SELECT
  u.name,
  o.amount
FROM users u
LEFT JOIN orders o
  ON u.id = o.user_id;

Ошибка 5. Путать JOIN и WHERE

Плохой стиль:

SELECT
  u.name,
  o.amount
FROM users u, orders o
WHERE u.id = o.user_id;

Лучше современный синтаксис:

SELECT
  u.name,
  o.amount
FROM users u
JOIN orders o
  ON u.id = o.user_id;

Так сразу видно, где условие соединения, а где обычная фильтрация.

Ошибка 6. Не понимать, почему исчезли строки

Если строка есть в одной таблице, но нет подходящей строки во второй, INNER JOIN её уберёт.

Это не ошибка базы. Это смысл INNER JOIN.

Например:

  • пользователь без заказов исчезнет;
  • заказ без пользователя исчезнет;
  • книга без автора исчезнет;
  • товар без категории исчезнет.

Если такие строки нужно сохранить, используй внешний join, чаще всего LEFT JOIN.

Как читать запрос с INNER JOIN

Возьмём запрос:

SELECT
  u.name,
  p.name AS product,
  o.amount
FROM users u
JOIN orders o
  ON o.user_id = u.id
JOIN products p
  ON p.id = o.product_id
WHERE o.amount > 1000
ORDER BY o.amount DESC;

Читать его удобно так:

  1. Берём пользователей из users.
  2. Соединяем их с заказами из orders.
  3. Соединяем заказы с товарами из products.
  4. Оставляем только заказы дороже 1000.
  5. Сортируем от самых дорогих к более дешёвым.
  6. Показываем имя пользователя, товар и сумму заказа.

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

Главное из статьи

INNER JOIN соединяет две таблицы по общему условию и оставляет только совпавшие строки.

Базовый пример:

SELECT
  u.name,
  o.amount
FROM users u
JOIN orders o
  ON u.id = o.user_id;

Главные правила:

  • INNER JOIN и просто JOIN обычно означают одно и то же.
  • Условие соединения пишется после ON.
  • В результат попадают только строки, у которых есть совпадение в обеих таблицах.
  • Если у пользователя нет заказов, INNER JOIN его не покажет.
  • Если заказ ссылается на несуществующего пользователя, такой заказ тоже не попадёт в результат.
  • Алиасы вроде u и o делают запрос короче и понятнее.
  • Если в таблицах есть одинаковые имена колонок, указывай таблицу или алиас.
  • Если у одной строки есть несколько совпадений, в результате будет несколько строк.
  • Если нужны строки без пары, используй LEFT JOIN.
  • Если нужны колонки из нескольких связанных таблиц, JOIN — основной инструмент.

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

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр