Какой тип join использовать, чтобы вывести всех пользователей, даже если у них нет продаж
Что проверяют
Знаете ли вы разницу между внутренним и внешним соединением на практике, а не по диаграмме Венна. Настоящая проверка начинается на уточнении: посчитайте сумму продаж по каждому пользователю, включая тех, у кого их нет, или только за текущий месяц.
Как отвечать
Нужен LEFT JOIN от таблицы пользователей: все её строки останутся, а у пользователей без продаж колонки из sales будут NULL.
SELECT u.id, u.name, COALESCE(SUM(s.amount), 0) AS total FROM users u LEFT JOIN sales s ON s.user_id = u.id GROUP BY u.id, u.name;
На этом запросе стоит проговорить три детали:
COALESCE: сумма по пустому набору даётNULL, а не ноль;- *
COUNT(s.id)вместо `COUNT()`**: звёздочка посчитает строку пользователя без продаж как одну продажу; - фильтр по правой таблице в
ON: условие «продажи за сентябрь» пишется какON s.user_id = u.id AND s.created_at >= '2026-09-01'. Если перенести его вWHERE, строки сNULLотсеются, и соединение фактически станет внутренним.
В Python то же самое выражается через ORM. В SQLAlchemy 2.0: select(User.id, func.coalesce(func.sum(Sale.amount), 0)).outerjoin(Sale).group_by(User.id). В Django агрегация по обратной связи, например User.objects.annotate(total=Coalesce(Sum("sales__amount"), 0)), сама строит LEFT OUTER JOIN. Условие по периоду кладите внутрь агрегата через filter=Q(...), тогда соединение останется внешним. В pandas аналог: users.merge(sales, how="left", left_on="id", right_on="user_id").
RIGHT JOIN с переставленными таблицами даст тот же результат, но читается хуже, поэтому его почти не пишут.
Где спотыкаются
Ставят условие на sales в WHERE и не понимают, почему пропали пользователи без продаж.
Считают COUNT(*) после внешнего соединения и получают единицу там, где должен быть ноль.
В Django фильтруют через .filter(sales__created_at__gte=...) и незаметно теряют строки: такой фильтр превращает соединение во внутреннее, хотя слова join в коде нет.