ЯдроКодаподготовка к экзаменам
Учебная платформа

Загружаем материалы

Подготавливаем материалы и навигацию по разделу.

SQL JOIN простыми словами: INNER, LEFT, RIGHT и FULL с примерами

Автор: · Обновлено

JOIN объединяет строки двух источников по логическому условию. Главное различие видов соединения — не синтаксис, а судьба строк, для которых пара не нашлась: INNER их…

JOIN объединяет строки двух источников по логическому условию. Главное различие видов соединения — не синтаксис, а судьба строк, для которых пара не нашлась: INNER их отбрасывает, LEFT сохраняет слева, RIGHT — справа, а FULL сохраняет с обеих сторон. Ниже один пример последовательно показывает все четыре варианта без создания отдельных страниц под синонимы запроса.

Исходные таблицы и ожидаемый результат

Пусть в магазине есть клиенты и заказы. У Анны два заказа, у Бориса ни одного, а один импортированный заказ пока не связан с клиентом. Последняя ситуация специально добавлена, чтобы разница между LEFT и FULL была видна на данных.

CREATE TABLE customers (
  id bigint PRIMARY KEY,
  name text NOT NULL
);

CREATE TABLE orders (
  id bigint PRIMARY KEY,
  customer_id bigint,
  total numeric(12, 2) NOT NULL
);

INSERT INTO customers VALUES (1, 'Анна'), (2, 'Борис'), (3, 'Вера');
INSERT INTO orders VALUES
  (101, 1, 1200.00),
  (102, 1, 800.00),
  (103, 3, 500.00),
  (104, NULL, 300.00);

В рабочей схеме для известных связей обычно добавляют внешний ключ. Здесь NULL оставлен допустимым ради сценария «заказ ещё не сопоставлен». Условие соединения будет одинаковым: customers.id = orders.customer_id.

INNER JOIN: только найденные пары

Внутреннее соединение возвращает комбинации строк, для которых условие ON истинно. У Анны появятся две строки, потому что одной строке клиента соответствуют два заказа. Борис исчезнет, а заказ 104 не попадёт в результат.

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

Слово INNER разрешено опустить: JOIN без уточнения означает внутреннее соединение. Важно помнить о кратности. JOIN не «приклеивает один объект», а строит все подходящие пары; связь один-ко-многим поэтому размножает строку стороны «один».

LEFT JOIN: все строки слева

LEFT JOIN сначала формирует найденные пары, затем добавляет каждого клиента без заказа. Поля правой таблицы для такой строки получают NULL. Это подходящий вариант для отчёта «все клиенты и, если есть, их заказы».

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

Чтобы найти клиентов без заказов, после соединения проверяют отсутствующий ключ правой стороны: WHERE o.id IS NULL. Лучше проверять гарантированно непустой первичный ключ, а не произвольный столбец, в котором NULL мог храниться и у реального заказа.

RIGHT JOIN и эквивалентный LEFT JOIN

RIGHT JOIN сохраняет все строки правого источника. Поэтому в выборке останется заказ 104, а колонки клиента будут NULL. Почти любой RIGHT JOIN можно переписать как более привычный LEFT JOIN, поменяв источники местами.

SELECT c.name, o.id AS order_id, o.total
FROM customers AS c
RIGHT JOIN orders AS o ON o.customer_id = c.id;

-- Тот же смысл с направлением чтения слева направо:
SELECT c.name, o.id AS order_id, o.total
FROM orders AS o
LEFT JOIN customers AS c ON c.id = o.customer_id;

Единого правила «RIGHT JOIN плох» нет. Выбирайте запись, в которой базовая сущность запроса заметна сразу и которую команде проще проверить при ревью.

FULL JOIN: несовпадения с обеих сторон

FULL JOIN объединяет поведение LEFT и RIGHT: возвращает найденные пары, клиентов без заказов и заказы без клиента. Он полезен для сверки двух наборов, миграций и поиска расхождений.

SELECT c.id AS customer_id, c.name, o.id AS order_id, o.total
FROM customers AS c
FULL JOIN orders AS o ON o.customer_id = c.id
ORDER BY customer_id NULLS LAST, order_id;

Для обычного экрана приложения FULL JOIN нужен реже, чем INNER или LEFT. Его наличие в запросе должно следовать из требования сохранить обе группы несовпавших строк, а не из желания «показать вообще всё».

Почему фильтр в WHERE меняет внешний JOIN

Условия ON определяют, какие строки считаются парой. Условия WHERE фильтруют уже получившийся результат. Поэтому перенос проверки правой таблицы из ON в WHERE может незаметно удалить строки с NULL и превратить LEFT JOIN по смыслу в INNER JOIN.

-- Сохраняет всех клиентов, но присоединяет только крупные заказы:
SELECT c.name, o.id, o.total
FROM customers AS c
LEFT JOIN orders AS o
  ON o.customer_id = c.id AND o.total >= 1000;

-- Оставляет только клиентов, у которых нашёлся крупный заказ:
SELECT c.name, o.id, o.total
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.id
WHERE o.total >= 1000;

Оба запроса корректны, но отвечают на разные вопросы. Перед написанием SQL сформулируйте, какие строки обязаны сохраниться даже без соответствия.

ON, USING и неоднозначные имена

USING (customer_id) короче, когда столбец связи имеет одинаковое имя с обеих сторон, и выводит одну общую колонку вместо двух. ON гибче: он позволяет связывать столбцы с разными именами и добавлять условия. NATURAL JOIN в прикладном коде рискован: набор общих имён может измениться после добавления столбца, хотя текст запроса останется прежним.

Квалифицируйте повторяющиеся имена алиасами: c.id, o.id. Не используйте SELECT * в стабильном API-контракте — явный список полей не меняется от добавления технической колонки.

Производительность без магии

Индекс часто нужен на столбце, по которому ищутся соответствия, например orders(customer_id), но его польза зависит от размера таблиц, селективности и полного запроса. Внешний ключ сам по себе выражает целостность, а решение о плане принимает PostgreSQL. Сначала измерьте реальный запрос через EXPLAIN, затем меняйте индекс и сравнивайте тот же сценарий.

Практика

  1. Выполните четыре соединения на тестовых данных и вручную предскажите число строк до запуска.
  2. Напишите запрос «все клиенты и сумма их заказов», сохранив клиентов без покупок. Используйте COALESCE(SUM(o.total), 0) и корректный GROUP BY.
  3. Найдите отдельно клиентов без заказов и заказы без клиента.
  4. Добавьте условие «учитывать только заказы от 1000» сначала в ON, затем в WHERE и объясните различие результатов.

Критерий готовности: вы можете назвать, какая сторона обязана сохраниться, предсказать появление NULL и объяснить размножение строк до выполнения запроса.

Что важно запомнить

Выбор вида JOIN начинается с вопроса о несовпавших строках. INNER оставляет только пары, LEFT и RIGHT сохраняют одну заданную сторону, FULL — обе. Условия ON управляют сопоставлением, WHERE — итоговой фильтрацией, а кратность данных определяет число строк результата.

Источники