Раздел 42 · Аналитика данных с нуля

LEFT JOIN: кто ничего не купил

junior~15 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Мира · маркетолог магазина настолок «Кубик»

Хочу запустить письмо «вернись и купи»: для тех, кто зарегистрировался, но так ничего и не заказал. Сколько таких людей и откуда они пришли? И заодно: есть ли в каталоге игры, которые вообще никто не брал?

В прошлом уроке мы склеивали заказы с клиентами, и INNER JOIN оставлял только пары. Сегодня нужны те, у кого пары нет.

Список гостей и подарки

Представь день рождения. Есть список гостей и стопка подарков с подписями «от кого». Если сопоставить подарки с гостями и оставить только совпадения, ты узнаешь, кто что подарил. Но не узнаешь, кто пришёл с пустыми руками: такие гости из «совпадений» выпадут.

Чтобы их увидеть, нужно идти от списка гостей и про каждого писать, что он подарил, а если ничего, то ставить прочерк. Так работает LEFT JOIN: каждая строка левой таблицы остаётся в результате, а если пары справа нет, вместо её столбцов стоит NULL.

Как выглядит пустая пара

SELECT c.id, c.name, o.id AS order_id, o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
ORDER BY c.id, o.id
LIMIT 5;
 id | name           | order_id | status
----+----------------+----------+-----------
  1 | Кирилл Фролов  |     NULL | NULL
  2 | Кирилл Волков  |        1 | delivered
  3 | Алексей Фролов |        4 | delivered
  4 | Дмитрий Попов  |        5 | delivered
  4 | Дмитрий Попов  |      228 | delivered

Клиент №1 зарегистрировался и ничего не купил. С INNER JOIN его бы не было, а LEFT JOIN оставил его и дописал справа NULL. У Дмитрия Попова несколько заказов, и он повторяется в каждой паре.

Сравни размеры: внутреннее соединение клиентов и заказов даёт 6555 строк, а левое 7051. Разница, 496 строк, это клиенты без единого заказа.

«Левая» таблица та, что стоит в FROM, правая та, что в JOIN. Есть и RIGHT JOIN, который сохраняет правую сторону, но его почти не пишут: проще поменять таблицы местами.

Анти-соединение: оставить только пустых

Чтобы получить тех, у кого пары нет, отфильтруем пустые пары:

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

Такой приём называется анти-соединение. Проверять на NULL нужно столбец, который в правой таблице сам по себе пустым не бывает. Лучше всего её первичный ключ: NULL в o.id может появиться только от пустой пары.

Тем же приёмом находим игры, которые никто не купил:

SELECT p.id, p.title, p.released_on
FROM products p
LEFT JOIN order_items oi ON oi.product_id = p.id
WHERE oi.order_id IS NULL;
 id  | title                                | released_on
-----+--------------------------------------+-------------
 119 | Механика Луны                        | 2026-05-18
 120 | Пчелиный улей                        | 2026-06-20
 121 | Седьмая печать                       | 2026-06-03
 122 | Хроники Порта: Коллекционное издание | 2026-05-16

Четыре игры, и все свежие релизы мая и июня 2026 года. Это не неликвид, а новинки, которые ещё не успели купить.

урок из курса

Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.