Глиф Учебники

Урок 6. Объединение таблиц — JOIN

Курс «SQL» · урок 6 из 10 · 4 минуты чтения

В учебном магазине пока одна таблица — товары. Теперь появятся покупатели и заказы. В заказе не повторяют имя покупателя и название товара, а хранят только их номера: customer_id и product_id. Чтобы увидеть заказ целиком, таблицы нужно соединить — это делает JOIN.

Новые таблицы

Создайте их и заполните:

SQL
CREATE TABLE customers (
    id   INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    city TEXT
);

CREATE TABLE orders (
    id          INTEGER PRIMARY KEY,
    customer_id INTEGER NOT NULL,   -- номер покупателя из customers
    product_id  INTEGER NOT NULL,   -- номер товара из products
    qty         INTEGER NOT NULL,
    order_date  TEXT    NOT NULL
);

INSERT INTO customers (name, city) VALUES
    ('Анна', 'Минск'),
    ('Борис', 'Гомель'),
    ('Вера', 'Минск'),
    ('Глеб', 'Брест'),
    ('Дарья', 'Гродно');

INSERT INTO orders (customer_id, product_id, qty, order_date) VALUES
    (1, 1, 10, '2026-09-01'),
    (1, 3, 1, '2026-09-01'),
    (2, 6, 1, '2026-09-03'),
    (3, 2, 20, '2026-09-05'),
    (3, 1, 5, '2026-09-10'),
    (4, 7, 1, '2026-09-12'),
    (2, 4, 2, '2026-09-15');

Сама по себе таблица заказов мало что говорит:

idcustomer_idproduct_idqtyorder_date
111102026-09-01
21312026-09-01
32612026-09-03
432202026-09-05
53152026-09-10
64712026-09-12
72422026-09-15

JOIN … ON

SQL
SELECT orders.id, customers.name, orders.qty
FROM orders
JOIN customers ON customers.id = orders.customer_id
ORDER BY orders.id;
idnameqty
1Анна10
2Анна1
3Борис1
4Вера20
5Вера5
6Глеб1
7Борис2

JOIN берёт каждую строку orders и ищет к ней строки customers, для которых верно условие после ON: номер покупателя совпадает. Найденные пары склеиваются в одну строку ответа.

orders id customer_id qty 1 1 10 2 1 1 3 2 1 4 3 20 5 3 5 6 4 1 7 2 2 customers id name 1 Анна 2 Борис 3 Вера 4 Глеб 5 Дарья нет пары строка ответа id 4 name Вера qty 20 ON customers.id = orders.customer_id
JOIN … ON сводит каждую строку orders с покупателем, у которого id равен её customer_id: заказ 4 и Вера склеиваются в строку ответа, а у Дарьи заказов нет, и пары ей не находится.

Столбцы записаны с именем таблицы через точку: orders.id, customers.name. Это обязательно, когда столбец с таким именем есть в обеих таблицах: id есть и там, и там. Без уточнения SQLite ответит ошибкой ambiguous column name: id («неоднозначное имя столбца»).

Псевдонимы и три таблицы

Длинные имена таблиц сокращают псевдонимами: orders AS o или просто orders o. Присоединяется и третья таблица, товары, — JOIN повторяют сколько угодно раз:

SQL
SELECT o.id, c.name AS customer, p.name AS product, o.qty
FROM orders AS o
JOIN customers AS c ON c.id = o.customer_id
JOIN products AS p ON p.id = o.product_id
ORDER BY o.id;
idcustomerproductqty
1АннаРучка10
2АннаРюкзак1
3БорисКалькулятор1
4ВераТетрадь20
5ВераРучка5
6ГлебНаушники1
7БорисПенал2

После соединения работает всё, что уже знакомо: WHERE, выражения, сортировка. Заказы из Минска с суммой:

SQL
SELECT o.id, c.name AS customer, p.name AS product, p.price * o.qty AS total
FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN products p ON p.id = o.product_id
WHERE c.city = 'Минск'
ORDER BY o.id;
idcustomerproducttotal
1АннаРучка20
2АннаРюкзак85
4ВераТетрадь60
5ВераРучка10

JOIN и GROUP BY

Сколько потратил каждый покупатель:

SQL
SELECT c.name, SUM(p.price * o.qty) AS spent
FROM customers c
JOIN orders o ON o.customer_id = c.id
JOIN products p ON p.id = o.product_id
GROUP BY c.id, c.name
ORDER BY spent DESC;
namespent
Анна105
Вера70
Глеб60
Борис54

Группировка идёт по c.id, а не только по имени: два разных покупателя могут оказаться тёзками. c.name тоже указан в GROUP BY, чтобы его можно было вывести — по правилу из прошлого урока.

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

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

SQL
SELECT c.name, o.id AS order_id
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
ORDER BY c.id, o.id;
nameorder_id
Анна1
Анна2
Борис3
Борис7
Вера4
Вера5
Глеб6
ДарьяNULL
JOIN — только пары customers c JOIN orders o name order_id Анна 1 Анна 2 Борис 3 Борис 7 Вера 4 Вера 5 Глеб 6 Дарья у Дарьи заказов нет — строки нет LEFT JOIN — все покупатели customers c LEFT JOIN orders o name order_id Анна 1 Анна 2 Борис 3 Борис 7 Вера 4 Вера 5 Глеб 6 Дарья NULL у Дарьи заказов нет — ставится NULL
Обычный JOIN выводит только покупателей, у которых есть заказы, а LEFT JOIN — всех покупателей левой таблицы customers и Дарье без заказов ставит NULL в order_id.

Отсюда частый приём — найти строки без пары. Покупатели без заказов:

SQL
SELECT c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;
name
Дарья

Так же находятся товары, которые никто не заказывал: products p LEFT JOIN orders o ON o.product_id = p.id и WHERE o.id IS NULL — это глобус и сумка для ноутбука. Подробнее о разнице — справка Чем LEFT JOIN отличается от INNER JOIN.

Подсчёт с LEFT JOIN

Число заказов у каждого покупателя, включая тех, у кого их нет:

SQL
SELECT c.name, COUNT(o.id) AS orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY c.id;
nameorders
Анна2
Борис2
Вера2
Глеб1
Дарья0

Здесь важно COUNT(o.id), а не COUNT(*). У Дарьи в соединении одна строка (с NULL вместо заказа), и COUNT(*) насчитал бы ей 1 заказ. COUNT(o.id) пропускает NULL и даёт честный 0.

строки после LEFT JOIN c.name o.id COUNT(*) COUNT(o.id) Анна 1 Анна 2 2 2 Глеб 6 1 1 Дарья NULL 1 0 строка с NULL посчитана как заказ NULL пропущен — честный 0
После LEFT JOIN у Дарьи одна строка с NULL вместо заказа: COUNT(*) насчитывает ей один заказ, а COUNT(o.id) пропускает NULL и даёт 0.

Другие виды JOIN

Если забыть ON, SQLite и MySQL соединят каждую строку с каждой: 5 покупателей и 7 заказов дадут 35 строк бессмыслицы. PostgreSQL без ON выдаёт ошибку. Увидели в ответе намного больше строк, чем ожидали, — проверьте условия соединения.

Встречается и старая запись: таблицы через запятую, а условие — в WHERE: FROM orders o, customers c WHERE c.id = o.customer_id. Она работает так же, как JOIN, но условие соединения легко потерять среди других условий. Пишите JOIN … ON.

Коротко

Проверьте себя

1. Что выведет customers c JOIN orders o ON o.customer_id = c.id для покупателя без заказов?
2. Как найти покупателей, у которых нет ни одного заказа?
3. Что значит ошибка ambiguous column name: id?
4. Почему с LEFT JOIN для подсчёта заказов пишут COUNT(o.id), а не COUNT(*)?