Что это и зачем
JOIN объединяет строки из двух таблиц по условию. В аналитике без JOIN-ов никуда — продуктовые метрики почти всегда требуют объединения orders + users + products.
Виды JOIN
-- INNER JOIN — только совпавшие строки
SELECT u.name, o.amount
FROM users u
INNER JOIN orders o ON o.user_id = u.id;
-- LEFT JOIN — все из левой + матч из правой (NULL если нет)
SELECT u.name, COALESCE(o.amount, 0) AS amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id;
-- FULL JOIN — все из обеих, NULL где не совпало
SELECT u.id, o.id
FROM users u
FULL JOIN orders o ON o.user_id = u.id;
Cardinality bug
Самая частая ошибка джунов: JOIN раздувает строки если в правой таблице несколько матчей.
-- Если у юзера 5 заказов, эта query вернёт 5 строк с одним name
SELECT u.name, o.amount
FROM users u
JOIN orders o ON o.user_id = u.id;
Проверь: SELECT COUNT(*) FROM left_table должно совпадать с SELECT COUNT(*) FROM left_table LEFT JOIN right_table USING (key) если связь 1:1.
Когда что использовать
- INNER: нужны только юзеры с заказами
- LEFT: все юзеры, заказы опциональны (для retention)
- FULL: редко, обычно для сверки данных
Кейс из Каспи
Аналитик считал retention. Один юзер с 100 заказами стал "100 активных юзеров" в отчёте. Retention вырос в 3 раза — пока CFO не спросил почему. Фикс: использовать EXISTS или JOIN + GROUP BY.
SELECT u.id
FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
AND o.created_at > now() - interval '30 days'
);