Урок курса

INNER JOIN: соединение двух таблиц

SQL с нуля: бесплатный курс с практикой

INNER JOIN: какой результат получится

Пусть в customers есть Анна (customer_id=1) и Борис (customer_id=2), а в orders — заказы 10 и 11, оба с customer_id=1. Запрос соединяет строки по равенству ключей:

SELECT c.name, o.order_id
FROM customers AS c
INNER JOIN orders AS o
  ON c.customer_id = o.customer_id
ORDER BY o.order_id;

Результат — две строки: Анна | 10 и Анна | 11. Бориса в выдаче нет: у него не нашлось заказа. Одна строка клиента может дать несколько строк результата, потому что совпадение проверяется для каждого заказа. Именно поэтому подсчёт клиентов через COUNT(*) после такого соединения считает заказы, а не уникальных людей. Для числа покупателей понадобится COUNT(DISTINCT c.customer_id).

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

В прошлом уроке мы разобрали, как внешний ключ связывает строки двух таблиц на уровне схемы. Теперь научимся использовать эту связь прямо в запросе — соединять таблицы так, чтобы получить данные из обеих сразу.

INNER JOIN: синтаксис ON и механизм совпадения строк

До этого момента каждый запрос работал с одной таблицей. Но данные в реляционной базе намеренно разнесены по разным таблицам: имена клиентов — в customers, их заказы — в orders. Чтобы получить и то и другое в одном результате, нужен JOIN.

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

SELECT *
FROM customers
INNER JOIN orders ON customers.customer_id = orders.customer_id;

После INNER JOIN указывается вторая таблица, а после ON — условие: какие столбцы двух таблиц должны совпасть, чтобы строки объединились. Слово INNER можно опустить — просто JOIN без префикса работает точно так же, это синонимы.

Механизм такой: SQLite берёт каждую строку из customers и ищет в orders строки, у которых customer_id совпадает. Если совпадение нашлось — строки склеиваются в одну строку результата. Если нет — строка из customers в результат не попадает вообще.

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

Отсюда вытекает самый частый сюрприз для новичков: результат содержит меньше строк, чем ожидалось. Клиент есть в customers, но заказов не делал — и в результате JOIN его не будет. Заказ есть в orders, но customer_id там указывает на несуществующего клиента — этот заказ тоже пропадёт. Если видите неожиданно короткий результат, первым делом проверяйте именно это: у каждой ли строки есть пара в другой таблице.

Анимация сопоставления customers.id с orders.customer_id для INNER JOIN, при котором в результат попадают только строки с найденной парой
INNER JOIN добавляет только найденные пары

Псевдоним таблицы через AS и уточнённое имя столбца

Когда в запросе участвуют две таблицы, сразу возникают две проблемы. Первая — длина: писать customers.customer_id и orders.customer_id в каждом условии утомительно. Вторая — неоднозначность: если столбец с одним именем есть в обеих таблицах, SQLite не поймёт, какой именно вы имеете в виду, и выдаст ошибку.

Оба вопроса решают псевдонимы таблиц:

SELECT *
FROM customers AS c
INNER JOIN orders AS o ON c.customer_id = o.customer_id;

AS c после имени таблицы объявляет псевдоним, который действует на весь запрос. Теперь везде — в ON, WHERE, SELECT — вместо customers пишем c, вместо orders — o.

Важное ограничение: после того как псевдоним объявлен, смешивать его с полным именем нельзя. Запись вроде c.customer_id = orders.customer_id вызовет ошибку, потому что orders уже «переименован» в o внутри этого запроса.

Запись псевдоним.столбец называется уточнённым именем столбца. Она нужна не только для сокращения, но и для явного указания источника. В SELECT это особенно важно: столбец customer_id есть в обеих таблицах, и если написать просто customer_id без уточнения, SQLite откажется выполнять запрос с сообщением об ambiguous column. Уточнение снимает неоднозначность:

SELECT c.name, o.order_date, o.total_amount
FROM customers AS c
INNER JOIN orders AS o ON c.customer_id = o.customer_id;

Здесь c.name — столбец name из customers, o.order_date и o.total_amount — столбцы из orders. Каждый столбец однозначно привязан к источнику, и запрос читается без двусмысленности.

Сквозной запрос: customers и orders с псевдонимами

Соберём всё вместе в рабочий запрос:

SELECT c.name AS клиент,
       o.order_date AS дата,
       o.total_amount AS сумма
FROM customers AS c
INNER JOIN orders AS o ON c.customer_id = o.customer_id;

Пройдём по каждому элементу:

  • FROM customers AS c — начинаем с таблицы клиентов и сразу даём ей псевдоним.
  • INNER JOIN orders AS o — подключаем таблицу заказов с псевдонимом o.
  • ON c.customer_id = o.customer_id — условие соединения: строки склеиваются, когда customer_id совпадает в обеих таблицах. Это именно та связь внешнего ключа, которую мы разбирали раньше: orders.customer_id ссылается на customers.customer_id.
  • SELECT c.name, o.order_date, o.total_amount — берём имя клиента из одной таблицы и данные заказа из другой. Псевдонимы AS клиент, AS дата, AS сумма дают столбцам результата читаемые заголовки — это тот же AS, который использовался для псевдонимов столбцов ещё в ранних уроках.

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

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

Задача 1

Таблица customers содержит 4 строки с customer_id: 1, 2, 3, 4. Таблица orders содержит 5 строк с customer_id: 1, 1, 2, 2, 3. Выполняется запрос:

SELECT c.customer_id, o.customer_id
FROM customers AS c
INNER JOIN orders AS o ON c.customer_id = o.customer_id;

Сколько строк вернёт этот запрос?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 2

Какое утверждение об INNER JOIN всегда верно?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 3

Какой запрос вернёт order_id и имя клиента только для заказов, у которых найден клиент с тем же customer_id?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 4

Зачем при INNER JOIN двух таблиц использовать псевдонимы таблиц и уточнённые имена вида псевдоним.столбец?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 5

Заказы с именами клиентов

В базе есть customers(customer_id, name) и orders(order_id, customer_id, order_date, total_amount). Напишите один SELECT, который через INNER JOIN сопоставляет заказ с клиентом и возвращает name, order_date, total_amount. Используйте алиасы c и o, а столбцы укажите через эти алиасы. Отсортируйте результат по дате заказа, а при одинаковой дате — по order_id по возрастанию. Клиенты без заказов и заказы без подходящего клиента в результат не входят.

РешениеSQL · SQLite

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку