JOIN в SQL: INNER, LEFT и ошибки новичка

JOIN — это момент, где SQL становится похож на настоящую работу с базой, а не на фильтр одной таблицы

Пока у нас одна таблица заказов, все просто: выбрали строки, отсортировали, посчитали. Но в реальном проекте данные почти никогда не живут в одной таблице. Клиенты отдельно, заказы отдельно, товары отдельно, платежи отдельно, категории отдельно. JOIN нужен, чтобы собрать эти кусочки в один ответ

Разберем на понятном примере: есть клиенты и заказы. Нужно увидеть, кто что купил, а потом найти клиентов без заказов

Что получится в конце

Мы напишем два главных запроса

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

SELECT c.name, o.amount
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id;

Второй покажет всех клиентов, даже если заказов нет:

SELECT c.name, o.amount
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id;

А еще разберем ловушку:

WHERE o.status = 'paid'

после LEFT JOIN может убрать строки без заказов, хотя мы вроде хотели их сохранить

Готовим данные

Создадим таблицу клиентов:

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

Добавим клиентов:

INSERT INTO customers (id, name, city) VALUES
(1, 'Анна', 'Казань'),
(2, 'Игорь', 'Москва'),
(3, 'Мария', 'Самара'),
(4, 'Олег', 'Екатеринбург');

Теперь заказы:

CREATE TABLE orders (
  id INTEGER PRIMARY KEY,
  customer_id INTEGER,
  amount INTEGER,
  status TEXT
);

Добавим строки:

INSERT INTO orders (id, customer_id, amount, status) VALUES
(1, 1, 7900, 'paid'),
(2, 1, 15000, 'paid'),
(3, 2, 4500, 'pending'),
(4, 3, 1200, 'cancelled');

Связь такая: orders.customer_id указывает на customers.id

INNER JOIN: только совпавшие строки

Напишем первый JOIN:

SELECT customers.name, orders.amount, orders.status
FROM customers
INNER JOIN orders ON orders.customer_id = customers.id;

Результат:

Анна7900paid
Анна15000paid
Игорь4500pending
Мария1200cancelled

Олега нет, потому что у него нет заказа. INNER JOIN возвращает только те строки, где нашлась пара с обеих сторон

Алиасы: чтобы запрос не распухал

В реальных запросах имена таблиц часто длиннее, поэтому используют алиасы:

SELECT c.name, o.amount, o.status
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id;

customers c означает: дальше таблицу customers можно называть c. orders o — таблицу orders можно называть o

Алиасы не только сокращают код, но и снимают неоднозначность. Если в двух таблицах есть столбец id, лучше явно писать c.id и o.id

Почему Анна появилась два раза

У Анны два заказа. JOIN не "склеивает клиента в одну строку", он показывает все найденные пары:

  • Анна + заказ 1;
  • Анна + заказ 2.

Это не ошибка. Это нормальное поведение соединения

Если нужна одна строка на клиента, добавляем группировку:

SELECT c.name, COUNT(o.id) AS orders_count, SUM(o.amount) AS total_amount
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name;

Результат:

Анна222900
Игорь14500
Мария11200

Олега все еще нет, потому что это INNER JOIN

LEFT JOIN: оставляем всех клиентов

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

SELECT c.name, o.amount, o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id;

Результат:

Анна7900paid
Анна15000paid
Игорь4500pending
Мария1200cancelled
ОлегNULLNULL

LEFT JOIN берет все строки из левой таблицы, то есть из customers. Если справа в orders ничего не нашлось, база подставляет NULL

Это очень полезно для задач:

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

Ищем клиентов без заказов

После LEFT JOIN можно оставить только строки, где заказ не найден:

SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;

Результат:

idname
4Олег

Мы говорим: покажи всех клиентов, для которых правая сторона соединения пустая

Главная ошибка с LEFT JOIN и WHERE

Допустим, хотим вывести всех клиентов и их оплаченные заказы. Новичок часто пишет так:

SELECT c.name, o.amount, o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'paid';

На первый взгляд логично. Но есть проблема: WHERE o.status = 'paid' убирает строки, где o.status равен NULL. То есть клиенты без заказов пропадут

Если нужно сохранить всех клиентов, условие по правой таблице лучше перенести в ON:

SELECT c.name, o.amount, o.status
FROM customers c
LEFT JOIN orders o
  ON o.customer_id = c.id
 AND o.status = 'paid';

Теперь результат будет таким:

Анна7900paid
Анна15000paid
ИгорьNULLNULL
МарияNULLNULL
ОлегNULLNULL

Мы оставили всех клиентов, но справа подтянули только оплаченные заказы

JOIN с несколькими таблицами

Добавим товары:

CREATE TABLE products (
  id INTEGER PRIMARY KEY,
  name TEXT
);

Для простоты представим, что в заказе есть product_id. Тогда запрос может выглядеть так:

SELECT c.name AS customer_name, p.name AS product_name, o.amount
FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN products p ON p.id = o.product_id;

SQL читается как цепочка связей. Заказ связан с клиентом по customer_id, заказ связан с товаром по product_id

Главное не пытаться соединять таблицы "потому что они рядом". У каждого JOIN должно быть понятное условие связи

Мини-задания

  1. Выведи всех клиентов и их заказы через LEFT JOIN.
  2. Найди клиентов без заказов.
  3. Найди клиентов, у которых есть только неоплаченные или отмененные заказы.
  4. Посчитай количество заказов по каждому клиенту.
  5. Перенеси условие o.status = 'paid' из WHERE в ON и сравни результат.

Возможное решение четвертого задания:

SELECT c.name, COUNT(o.id) AS orders_count
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY orders_count DESC;

Здесь LEFT JOIN нужен, чтобы клиент без заказов тоже попал в отчет с количеством 0

Когда JOIN не нужен

Иногда человек тянется к JOIN, хотя ему нужен только факт существования связи. Например: "покажи клиентов, у которых есть хотя бы один оплаченный заказ". Это можно сделать через JOIN, но часто лучше подходит EXISTS

SELECT c.id, c.name
FROM customers c
WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.customer_id = c.id
    AND o.status = 'paid'
);

Если не нужны поля из orders, а нужен только да/нет-фильтр, EXISTS может быть чище

Ответы на эти вопросы могут быть для вас полезными

Что делает JOIN в SQL?

JOIN соединяет строки из разных таблиц по условию связи. Например, заказ можно связать с клиентом через orders.customer_id = customers.id

Чем INNER JOIN отличается от LEFT JOIN?

INNER JOIN возвращает только совпавшие строки. LEFT JOIN сохраняет все строки из левой таблицы и подставляет NULL, если справа ничего не нашлось

Почему после JOIN появляются дубли?

Это не всегда дубли. Если у одного клиента несколько заказов, соединение вернет несколько пар "клиент + заказ". Чтобы получить одну строку на клиента, нужна группировка или другая логика

Почему LEFT JOIN не показывает строки без совпадений?

Частая причина — условие по правой таблице стоит в WHERE. Оно убирает строки с NULL. Если нужно сохранить левую таблицу полностью, часть условий стоит переносить в ON

Нужно ли всегда писать INNER JOIN полностью?

Во многих базах JOIN без уточнения означает INNER JOIN. Но в учебных материалах я бы писал явно: так легче читать запрос и меньше путаницы

Что почитать дальше по SQL

Если вы собираете тему по шагам, рядом лучше открыть:

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x