Урок курса
Соединение трёх и более таблиц
SQL с нуля: бесплатный курс с практикойРазберём, как соединить три таблицы в SQL и проследить, откуда берётся каждая строка результата.
Синтаксис двух JOIN подряд и логика цепочки соединений
Представьте, что нужно получить даты заказов, названия купленных товаров и количество единиц. Эти данные лежат в трёх таблицах: orders, order_items и products. Прямой связи между заказом и товаром нет: её хранит промежуточная таблица order_items. Такая цепочка описывается двумя JOIN подряд:
SELECT o.order_date, p.title, oi.quantity
FROM orders AS o
JOIN order_items AS oi ON o.order_id = oi.order_id
JOIN products AS p ON oi.product_id = p.product_id;
Первый JOIN прикрепляет позиции заказа к orders по условию o.order_id = oi.order_id. На этом шаге каждая строка результата описывает одну позицию конкретного заказа. Второй JOIN добавляет сведения о товаре по условию oi.product_id = p.product_id. Он опирается на oi.product_id — столбец таблицы, которая уже находится в цепочке.
Здесь ключевое правило: каждое условие ON связывает новую таблицу с теми, что уже упомянуты выше. Написать ON p.product_id = o.order_id синтаксически допустимо, но это сопоставит разные сущности и даст случайные пары либо пустой результат.
Логически каждый JOIN добавляет к цепочке одну таблицу. Физически оптимизатор может выбрать другой порядок выполнения для скорости, но на смысл результата это не влияет: запрос описывает, какие ключи связываются.
Если к этой цепочке нужно добавить клиента, перед order_items появляется ещё один JOIN: customers → orders → order_items → products. Число JOIN само по себе не говорит о плохой схеме. Проверять нужно другое: понятна ли цепочка ключей, верна ли кратность связей, не дублируется ли логика и что показывает план выполнения.
Как меняются строки после каждого JOIN
У заказа №1 две позиции: товар №10 в количестве 2 и товар №20 в количестве 1. В справочнике товаров №10 — клавиатура, №20 — мышь.
| Этап | Строки результата |
|---|---|
| orders | 1 · 2026-11-01 |
| orders JOIN order_items | 1 · 2026-11-01 · 10 · 2 1 · 2026-11-01 · 20 · 1 |
| Ещё JOIN products | 2026-11-01 · Клавиатура · 2 2026-11-01 · Мышь · 1 |
После первого соединения заказ занимает две строки, потому что в нём две позиции. Второе соединение добавляет названия товаров. Здесь ключ products.product_id уникален, поэтому строк больше не становится.
Этот пример можно запустить отдельно в SQLite — данные уже заданы через CTE:
WITH orders(order_id, order_date) AS (VALUES (1, '2026-11-01')),
order_items(order_id, product_id, quantity) AS (VALUES (1, 10, 2), (1, 20, 1)),
products(product_id, title) AS (VALUES (10, 'Клавиатура'), (20, 'Мышь'))
SELECT o.order_date, p.title, oi.quantity
FROM orders AS o
JOIN order_items AS oi ON o.order_id = oi.order_id
JOIN products AS p ON oi.product_id = p.product_id
ORDER BY p.product_id;Результат — две строки из последней строки таблицы выше. Если справочник содержит несколько записей с одним product_id, JOIN размножит соответствующую позицию заказа; дальше в уроке разберём, как это влияет на суммы.
Псевдонимы таблиц и ошибка ambiguous column
Когда в запросе участвуют три таблицы, одно и то же имя столбца почти неизбежно встречается в нескольких из них. Классический пример — столбец id или суррогатные ключи вида customer_id, order_id, product_id: они есть и в основной таблице, и в связанной.
Если написать просто order_id без указания источника, SQLite не знает, из какой таблицы брать значение, и возвращает ошибку:
ambiguous column name: order_id
Решение — квалифицировать имя псевдонимом: o.order_id, oi.order_id. Псевдоним объявляется прямо в FROM или JOIN через ключевое слово AS:
SELECT c.name AS клиент,
o.order_date AS дата,
p.title AS товар
FROM customers AS c
JOIN orders AS o ON c.customer_id = o.customer_id
JOIN order_items AS oi ON o.order_id = oi.order_id
JOIN products AS p ON oi.product_id = p.product_id
ORDER BY c.name;
Здесь к разобранной выше цепочке добавлена четвёртая таблица customers, поэтому получилось три JOIN. Псевдонимы c, o, oi, p объявляются один раз и дальше используются везде: в SELECT, в условиях ON, в WHERE и ORDER BY. Полное имя таблицы после объявления псевдонима уже нельзя использовать — только псевдоним.
Столбцы, уникальные среди всех участвующих таблиц, технически можно писать без квалификатора. Но на практике единый стиль — всегда псевдоним.столбец — избавляет от ошибок при добавлении новой таблицы в запрос: имя, которое раньше было уникальным, может перестать им быть, и тогда SQLite завершит запрос ошибкой неоднозначного имени. Проще не делать исключений.
WHERE и ORDER BY применяются к результату всех JOIN
WHERE и ORDER BY в многотабличном запросе — не фильтр отдельной таблицы, а операция над уже собранным результатом всех соединений. Логически сначала формируется результат соединений, затем WHERE отсекает лишние строки, а ORDER BY сортирует остаток. Физический план может применить фильтр раньше или переставить JOIN ради скорости, сохраняя тот же результат.
Позиция клозов жёстко фиксирована синтаксисом: WHERE идёт после последнего JOIN, ORDER BY — в самом конце:
SELECT c.name AS клиент,
o.order_date AS дата,
p.title AS товар
FROM customers AS c
JOIN orders AS o ON c.customer_id = o.customer_id
JOIN order_items AS oi ON o.order_id = oi.order_id
JOIN products AS p ON oi.product_id = p.product_id
WHERE p.price > 10000
ORDER BY c.name;
Здесь p.price > 10000 фильтрует строки объединённого набора — те сочетания клиент–заказ–товар, где цена товара не превышает порог, просто не попадут в итог. ORDER BY c.name сортирует оставшееся по имени клиента.
В условии WHERE и в ORDER BY используются те же псевдонимы, что объявлены в FROM/JOIN. Сейчас WHERE price > 10000 тоже сработает: в показанной схеме имя price однозначно. Запись p.price предпочтительнее для ясности и защитит запрос, если позже в цепочке появится другой одноимённый столбец.
Почему сумма заказа удваивается после JOIN
Проверьте кратность до расчёта итогов. Если один заказ на 100 рублей содержит две позиции, JOIN с order_items создаст две строки: сумма заказа 100 повторится в каждой. Поэтому SUM(o.total_amount) после соединения даст 200, хотя заказ был один. Это не ошибка SQL: соединение сохраняет по строке на каждую позицию.
WITH orders(order_id, total_amount) AS (VALUES (1, 100)),
order_items(order_id, quantity) AS (VALUES (1, 1), (1, 2))
SELECT COUNT(*) AS rows_after_join,
SUM(o.total_amount) AS inflated_total
FROM orders AS o
JOIN order_items AS oi ON oi.order_id = o.order_id;Запрос вернёт 2 | 200. Для выручки по заказам агрегируйте таблицу orders без присоединения позиций; если нужна сумма товаров, считайте её на уровне строк order_items с соответствующей ценой. SUM(DISTINCT o.total_amount) не исправляет проблему: два разных заказа с одинаковой суммой схлопнутся. Перед добавлением третьего JOIN полезно сравнить количество строк до и после каждого соединения. Отдельная задача на условие JOIN помогает заметить случаи, когда число строк выросло из-за неверного ON.
ORDER BY может сортировать по любому столбцу из результата, в том числе по столбцу, которого нет в SELECT. Это работает, потому что сортировка применяется к строкам объединённого набора, а не только к тому, что выводится.
Задача 1
Таблицы: students (student_id PK, name), enrollments (enrollment_id PK, student_id FK, course_id FK), courses (course_id PK, title).
Какой запрос корректно возвращает имя студента и название курса, использует псевдонимы s, e, c и уточняет ими все обращения к столбцам?
Задача 2
В запросе с тремя таблицами — orders (o), order_items (oi), products (p) — нужно оставить только строки, где quantity > 1, и отсортировать по цене товара по убыванию. Как правильно добавить эти условия?
Задача 3
Запрос выполнится без синтаксической ошибки, если написать ON o.order_date = p.title вместо ON oi.product_id = p.product_id (оба столбца типа TEXT). Будет ли результат корректным? Ответьте одним словом: да или нет.
Задача 4
Таблица employees (employee_id PK, name, department_id FK), departments (department_id PK, dept_name, location_id FK), locations (location_id PK, city).
Сколько JOIN нужно написать, чтобы получить name сотрудника, dept_name отдела и city города в одном запросе?
Задача 5
Строки заказов с книгами
Используйте три таблицы: orders(order_id, order_date), order_items(item_id, order_id, product_id, quantity) и products(product_id, title, category). Верните order_date, title, quantity только для строк с товарами категории Книги. Соедините таблицы по цепочке orders → order_items → products, задайте алиасы o, oi, p, а затем отсортируйте по o.order_date, o.order_id, oi.item_id по возрастанию.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- JOIN в SQL: запрос выполняется, но условие ON неверное
Запрос соединяет таблицы по текстовым столбцам вместо ключей. Определите, достаточно ли отсутствия синтаксической ошибки для верного результата.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
