Собесов

DataLearn SQL-101: Виды JOIN — INNER / LEFT / RIGHT / FULL / CROSS

SQLJOINЛёгкаяJunior

Условие

Есть две таблицы:

customers(customer_id, name, city)            -- 100 строк
orders(order_id, customer_id, amount, dt)     -- 1000 строк, некоторые customer_id отсутствуют в customers

Напишите запросы:

  1. Сколько строк вернёт INNER / LEFT / RIGHT / FULL JOIN customers и orders по customer_id?
  2. Клиенты, у которых нет ни одного заказа.
  3. Заказы от клиентов, которых нет в customers (orphan orders).
  4. Декартово произведение (зачем оно вообще нужно?).

Решение

Подход

Пусть в customers 100 строк, в orders 1000, из которых 50 строк имеют customer_id, отсутствующий в customers. У 30 клиентов нет заказов.

JOIN Строк Что включает
INNER 950 только совпавшие в обеих
LEFT 950 + 30 = 980 + клиенты без заказов
RIGHT 950 + 50 = 1000 + сиротские заказы
FULL 950 + 30 + 50 = 1030 оба «хвоста»
CROSS 100 × 1000 = 100К все пары

Запросы

1. Демонстрация

-- INNER
SELECT COUNT(*) FROM customers c INNER JOIN orders o ON o.customer_id = c.customer_id;
-- LEFT
SELECT COUNT(*) FROM customers c LEFT  JOIN orders o ON o.customer_id = c.customer_id;
-- FULL
SELECT COUNT(*) FROM customers c FULL  JOIN orders o ON o.customer_id = c.customer_id;

2. Клиенты без заказов

SELECT c.customer_id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL;          -- ключевой признак «нет совпадения»
 
-- альтернатива через NOT EXISTS (часто быстрее):
SELECT c.customer_id, c.name
FROM customers c
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);

3. Orphan orders

SELECT o.order_id, o.customer_id, o.amount
FROM orders o
LEFT JOIN customers c ON c.customer_id = o.customer_id
WHERE c.customer_id IS NULL;
 
-- NOT EXISTS:
SELECT o.*
FROM orders o
WHERE NOT EXISTS (SELECT 1 FROM customers c WHERE c.customer_id = o.customer_id);

4. CROSS JOIN — реальный кейс

Календарная матрица «город × дата» для plot fill:

SELECT c.city, d.dt, COALESCE(SUM(o.amount), 0) AS gmv
FROM (SELECT DISTINCT city FROM customers) c
CROSS JOIN generate_series(date '2025-01-01', date '2025-12-31', INTERVAL '1 day') d(dt)
LEFT JOIN customers cu ON cu.city = c.city
LEFT JOIN orders    o  ON o.customer_id = cu.customer_id
                       AND o.dt::date = d.dt::date
GROUP BY c.city, d.dt
ORDER BY c.city, d.dt;

— чтобы в дашборде не было «дырок» для дней без продаж.

Подводные камни

  1. WHERE o.amount > 0 на LEFT JOIN превращает его в INNER: NULL-строки фильтруются. Условия по правой таблице — в ON, не в WHERE.
-- Правильно: NULL-клиенты сохраняются
LEFT JOIN orders o ON o.customer_id = c.customer_id AND o.amount > 0
 
-- Неправильно:
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.amount > 0
  1. FULL JOIN не существует в MySQL — эмулируется через LEFT UNION RIGHT.

  2. NOT IN + NULL ломается:

SELECT * FROM customers
WHERE customer_id NOT IN (SELECT customer_id FROM orders);
-- если в orders есть NULL — вернёт 0 строк!

Использовать NOT EXISTS или WHERE customer_id NOT IN (SELECT customer_id FROM orders WHERE customer_id IS NOT NULL).

  1. CROSS JOIN на больших таблицах: 1M × 1M = 1 трлн строк — никогда не запускать без предварительной фильтрации.

  2. Cartesian product «по ошибке»: забыли ON или ON-условие всегда TRUE → CROSS JOIN. Результат: 10 минут + OOM.

  3. USING (col) vs ON a.col = b.col: с USING колонка col появляется один раз. Удобно, но иногда теряется явность.

Эталонный ответ

INNER 950, LEFT 980, RIGHT 1000, FULL 1030, CROSS 100000.

Клиенты без заказов: LEFT JOIN + WHERE order_id IS NULL либо NOT EXISTS. Orphan orders: симметрично — LEFT JOIN customers + WHERE c.customer_id IS NULL. CROSS JOIN — для календарных матриц / комбинаторных задач.

Главное: условие по правой таблице в LEFT JOIN — в ON, не в WHERE. И всегда NOT EXISTS вместо NOT IN при возможном NULL.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти