🔗 Урок 4.1: INNER JOIN
Модуль 4: JOIN — соединяем таблицы
👋 До сих пор мы работали с одной таблицей. Но в реальной жизни данные всегда разбросаны по разным таблицам. Клиенты — в одной, заказы — в другой, товары — в третьей. Чтобы собрать полную картину, нужно соединять таблицы. И здесь на помощь приходит JOIN!
🧠 Зачем нужен JOIN?
Представь, что ты ведёшь интернет-магазин. У тебя есть:
- Таблица customers — ФИО, email, телефон, адрес.
- Таблица orders — дата заказа, сумма, id клиента.
- Таблица products — название, цена, категория.
Когда приходит заказ, ты не хранишь в таблице orders имя клиента заново. Ты хранишь customer_id — ссылку на таблицу клиентов. Зачем дублировать имя? Оно уже есть в customers! Это называется нормализация — умное хранение данных без повторов.
Но как тогда получить список заказов вместе с именами клиентов? Вот тут и нужен JOIN — он соединяет таблицы по общему полю (обычно по id).
📦 Создаём таблицы для примеров
Давай создадим структуру небольшого интернет-магазина.
-- Таблица клиентов
CREATE TABLE customers ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT
); INSERT INTO customers (id, name, email) VALUES
(1, 'Анна Петрова', 'anna@pizza.ru'),
(2, 'Борис Иванов', 'boris@mail.ru'),
(3, 'Виктория Соколова', 'vika@yandex.ru'),
(4, 'Глеб Новиков', NULL); -- email не указан -- Таблица заказов
CREATE TABLE orders ( id INTEGER PRIMARY KEY, customer_id INTEGER, product_name TEXT, amount REAL, order_date DATE
); INSERT INTO orders (id, customer_id, product_name, amount, order_date) VALUES
(1, 1, 'Ноутбук', 75000, '2024-02-01'),
(2, 1, 'Мышь', 1500, '2024-02-01'),
(3, 2, 'Клавиатура', 3500, '2024-02-03'),
(4, 3, 'Наушники', 4500, '2024-02-05'),
(5, 1, 'Кружка', 500, '2024-02-07'),
(6, NULL, 'Чайник', 2500, '2024-02-10'); -- заказ без клиента -- Таблица категорий
CREATE TABLE categories ( id INTEGER PRIMARY KEY, name TEXT NOT NULL
); INSERT INTO categories (id, name) VALUES
(1, 'Электроника'),
(2, 'Посуда'),
(3, 'Книги'); -- Таблица товаров (связана с категориями)
CREATE TABLE products ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, price REAL, category_id INTEGER
); INSERT INTO products (id, name, price, category_id) VALUES
(1, 'Ноутбук', 75000, 1),
(2, 'Мышь', 1500, 1),
(3, 'Клавиатура', 3500, 1),
(4, 'Кружка', 500, 2),
(5, 'Тарелка', 300, 2),
(6, 'SQL-бестселлер', 1200, 3),
(7, 'Python для профи', 1500, 3),
(8, 'Наушники', 4500, 1),
(9, 'Чайник', 2500, 2),
(10, 'Стол', 10000, NULL); -- товар без категории 🔗 INNER JOIN — базовый синтаксис
INNER JOIN — самый распространённый тип соединения. Он показывает только те строки, где есть совпадение в обеих таблицах. Если в левой таблице есть строка, но в правой для неё нет пары — она не попадёт в результат.
-- INNER JOIN: заказы + клиенты
SELECT *
FROM orders
INNER JOIN customers ON orders.customer_id = customers.id; Обрати внимание: заказ №6 (customer_id = NULL) не попал в результат. У него нет клиента — нет и строки в customers для соединения. INNER JOIN показывает только то, что есть в обеих таблицах.
Также смотри: столбцы с одинаковыми названиями (id) получили суффикс таблицы. В результатах есть id (из orders) и id (из customers). Это неудобно. Давай выберем только нужные столбцы.
🎯 Выбираем конкретные столбцы
Вместо SELECT * давай укажем, какие столбцы нам нужны и с какими названиями.
-- Только нужные столбцы
SELECT orders.id AS order_id, orders.product_name, orders.amount, orders.order_date, customers.name AS customer_name, customers.email
FROM orders
INNER JOIN customers ON orders.customer_id = customers.id; Уже гораздо лучше! Видно, кто что заказал. Анна Петрова — наш самый активный клиент: она сделала 3 заказа.
product_name. Если есть в обеих (например, id есть и в orders, и в customers) — нужно писать таблица.столбец, чтобы SQL не запутался.
🏷️ Псевдонимы таблиц (алиасы)
Писать orders.customer_id и customers.name долго. Можно дать таблицам короткие имена — алиасы.
-- С псевдонимами гораздо короче
SELECT o.id AS order_id, o.product_name, o.amount, c.name AS customer_name, c.email
FROM orders AS o
INNER JOIN customers AS c ON o.customer_id = c.id; Можно даже без AS:
-- Ещё короче — без AS
SELECT o.id, o.product_name, c.name AS customer
FROM orders o
JOIN customers c ON o.customer_id = c.id; Обрати внимание: INNER JOIN можно писать просто JOIN. Это одно и то же. INNER — опциональное слово.
orders o JOIN customers c JOIN products p JOIN categories cat. С полными именами получится каша.
🛠️ Пример: товары с названием категории
Классический пример: в таблице products есть category_id, а название категории — в categories. Давай соединим!
-- Товары с названиями категорий
SELECT p.id AS product_id, p.name AS product_name, p.price, c.name AS category_name
FROM products p
INNER JOIN categories c ON p.category_id = c.id; Видишь? Стол (товар №10) не попал в результат — у него category_id = NULL, и в categories нет строки с id = NULL. INNER JOIN его отбросил.
📊 JOIN + GROUP BY — соединяем и группируем
А теперь давай объединим силу JOIN с GROUP BY. Получим аналитику!
-- Сколько заказов сделал каждый клиент?
SELECT c.id, c.name AS customer, COUNT(o.id) AS "Количество заказов", ROUND(SUM(o.amount), 0) AS "Сумма заказов"
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
ORDER BY "Сумма заказов" DESC; Анна — королева покупателей! А Глеб Новиков (клиент №4) не попал в результат — у него нет заказов. INNER JOIN его не показал. Об этом мы поговорим в следующем уроке про LEFT JOIN.
А вот сколько товаров в каждой категории:
-- Количество товаров в каждой категории
SELECT c.name AS category, COUNT(p.id) AS product_count, ROUND(AVG(p.price), 0) AS avg_price
FROM categories c
INNER JOIN products p ON c.id = p.category_id
GROUP BY c.id, c.name
ORDER BY product_count DESC; 🧩 Соединение по нескольким условиям
Иногда нужно соединить таблицы по сложному условию — не только по id, но и с дополнительным фильтром.
-- INNER JOIN с дополнительным условием
SELECT o.id AS order_id, o.product_name, o.amount, c.name AS customer
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id AND o.amount > 2000; -- только заказы дороже 2000 Но чаще дополнительное условие пишут в WHERE — это логичнее и читаемее:
-- То же самое, но WHERE смотрится лучше
SELECT o.id AS order_id, o.product_name, o.amount, c.name AS customer
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
WHERE o.amount > 2000; 🔄 Порядок выполнения с JOIN
Когда в запросе есть JOIN, порядок выполнения немного меняется:
🍕 Пример: пиццерия с клиентами
Вернёмся к нашей пиццерии. Добавим таблицу клиентов и свяжем с заказами.
-- Таблица клиентов пиццерии
CREATE TABLE pizza_customers ( id INTEGER PRIMARY KEY, name TEXT, phone TEXT
); INSERT INTO pizza_customers VALUES
(1, 'Иван', '+7-901-111-11-11'),
(2, 'Мария', '+7-902-222-22-22'),
(3, 'Пётр', '+7-903-333-33-33'); -- Таблица заказов с ссылкой на клиента
CREATE TABLE pizza_orders ( id INTEGER PRIMARY KEY, customer_id INTEGER, pizza_name TEXT, price REAL, quantity INTEGER
); INSERT INTO pizza_orders VALUES
(1, 1, 'Маргарита', 450, 2),
(2, 1, 'Пепперони', 650, 1),
(3, 2, 'Маргарита', 550, 3),
(4, 3, 'Гавайская', 500, 1),
(5, 2, 'Пепперони', 500, 2),
(6, NULL, 'Маргарита', 350, 1); -- заказ без клиента -- Все заказы с именем клиента
SELECT po.id AS order_id, pc.name AS client, po.pizza_name, po.price, po.quantity, (po.price * po.quantity) AS total
FROM pizza_orders po
INNER JOIN pizza_customers pc ON po.customer_id = pc.id
ORDER BY total DESC; Заказ №6 не попал — у него нет клиента. INNER JOIN его отбросил.
Аналитика по клиентам:
-- Сколько денег потратил каждый клиент?
SELECT pc.name AS client, COUNT(po.id) AS orders_count, SUM(po.price * po.quantity) AS total_spent
FROM pizza_customers pc
INNER JOIN pizza_orders po ON pc.id = po.customer_id
GROUP BY pc.id, pc.name
ORDER BY total_spent DESC; Мария — чемпион по тратам! Но что, если бы у нас был клиент, который ничего не заказал? Он бы не попал в INNER JOIN. Как это исправить — в следующем уроке про LEFT JOIN.
⚠️ Частые ошибки
❌ Ошибка 1: Забыли условие ON
-- КАТАСТРОФА! Без ON — декартово произведение!
SELECT *
FROM orders o
JOIN customers c; -- ❌ Каждая строка orders соединится с КАЖДОЙ строкой customers!
-- 6 заказов × 4 клиента = 24 строки (ерунда) Всегда проверяй, что у тебя есть ON с правильным условием!
❌ Ошибка 2: Неправильное условие ON
-- Неправильно: соединяем не те столбцы
SELECT *
FROM orders o
JOIN customers c ON o.id = c.id;
-- ❌ Соединили заказ №1 с клиентом №1 (повезло, что id совпали),
-- но заказ №2 с клиентом №2 — это уже ерунда! Правильно: ON o.customer_id = c.id — внешний ключ из orders соединяется с первичным ключом из customers.
❌ Ошибка 3: Неоднозначные имена столбцов
-- Ошибка: id есть и в orders, и в customers
SELECT id, product_name
FROM orders o
JOIN customers c ON o.customer_id = c.id;
-- ❌ SQL не знает, какой id вы брать -- Правильно: уточняем таблицу
SELECT o.id, o.product_name, c.name
FROM orders o
JOIN customers c ON o.customer_id = c.id; 🏋️ Практические задания
📝 Задание 1. Заказы с именем клиента
Напиши запрос, который покажет все заказы (product_name, amount) вместе с именем клиента. Используй INNER JOIN.
👀 Показать ответ
SELECT o.product_name, o.amount, c.name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id;
📝 Задание 2. Товары с названием категории
Выведи все товары с их названием и названием категории. Используй псевдонимы таблиц.
👀 Показать ответ
SELECT p.name AS product, p.price, c.name AS category
FROM products p
INNER JOIN categories c ON p.category_id = c.id;
📝 Задание 3. Сумма заказов по клиентам
Используя JOIN и GROUP BY, посчитай, на какую сумму заказал каждый клиент. Отсортируй от большей суммы к меньшей.
👀 Показать ответ
SELECT c.name, ROUND(SUM(o.amount), 0) AS total
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
ORDER BY total DESC;
📝 Задание 4. Количество товаров по категориям
Посчитай, сколько товаров в каждой категории. Выведи название категории и количество товаров.
👀 Показать ответ
SELECT c.name AS category, COUNT(p.id) AS product_count
FROM categories c
INNER JOIN products p ON c.id = p.category_id
GROUP BY c.id, c.name;
📝 Задание 5. Клиенты с суммой заказов > 4000
Найди клиентов, у которых сумма заказов больше 4000 рублей. Используй JOIN, GROUP BY и HAVING.
👀 Показать ответ
SELECT c.name, SUM(o.amount) AS total
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
HAVING total > 4000;
📝 Задание 6. Пиццерия: отчёт по клиентам
Используя таблицы pizza_customers и pizza_orders, выведи каждого клиента с количеством заказов и суммой. Отсортируй по сумме.
👀 Показать ответ
SELECT pc.name, COUNT(po.id) AS orders, SUM(po.price * po.quantity) AS total
FROM pizza_customers pc
INNER JOIN pizza_orders po ON pc.id = po.customer_id
GROUP BY pc.id, pc.name
ORDER BY total DESC;
📋 Шпаргалка: INNER JOIN
🚀 Что дальше?
INNER JOIN — это база. Но что, если мы хотим показать всех клиентов, даже тех, у кого нет заказов? Или все товары, даже без категории? Для этого существует LEFT JOIN — он берёт все строки из левой таблицы и присоединяет к ним данные из правой, а если совпадения нет — вставляет NULL. Увидимся в уроке 4.2! 🔗
Урок 4.1 — INNER JOIN | Модуль 4: JOIN — соединяем таблицы
ТЕСТ — Урок 4.1: INNER JOIN
5 вопросов