Что это и зачем

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'
);