Мира · маркетолог магазина настолок «Кубик»
Хочу запустить письмо «вернись и купи»: для тех, кто зарегистрировался, но так ничего и не заказал. Сколько таких людей и откуда они пришли? И заодно: есть ли в каталоге игры, которые вообще никто не брал?
В прошлом уроке мы склеивали заказы с клиентами, и 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 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.