Почему после JOIN суммы в отчёте оказались больше, чем должны быть?
- Что такое «размножение строк» при JOIN?
Связь один-ко-многим размножает строки: заказ с тремя позициями после JOIN с позициями превращается в три строки, и SUM(order.amount) учитывает сумму заказа трижды. Решение — сначала агрегировать каждую таблицу до нужной гранулярности, потом соединять; или считать по правильной таблице.
- Проверка: количество строк до и после JOIN, COUNT(DISTINCT ключ)
- Два JOIN «ко многим» из одной таблицы дают декартово произведение между ними
- COUNT(DISTINCT) спасает подсчёт, но не суммы
А ещё Дубли в справочнике (две записи на один ключ) дают тот же эффект молча
Подробно
Гранулярность. У каждой таблицы своя «единица строки»: клиент, заказ, позиция заказа, событие. JOIN к более мелкой гранулярности повторяет строки крупной. Аналитик всегда держит в голове, на каком уровне сейчас находится результат.
Как обнаружить:
- сравнить
COUNT(*)до и после соединения; - сверить итог с независимым источником (сумма выручки в системе платежей);
- проверить уникальность ключа справочника перед JOIN.
Связанная ловушка — LEFT JOIN и фильтр в WHERE: условие на правую таблицу в WHERE отбрасывает строки без пары и превращает LEFT JOIN в INNER — клиенты без заказов исчезают из отчёта.
Пример
-- ошибка: amount заказа повторяется по числу позиций
SELECT c.id, SUM(o.amount)
FROM customers c
JOIN orders o ON o.customer_id = c.id
JOIN order_items i ON i.order_id = o.id
GROUP BY c.id;
-- правильно: агрегировать каждую сторону отдельно
WITH ord AS (SELECT customer_id, SUM(amount) AS revenue, COUNT(*) AS orders
FROM orders GROUP BY customer_id),
itm AS (SELECT o.customer_id, COUNT(*) AS items
FROM order_items i JOIN orders o ON o.id = i.order_id
GROUP BY o.customer_id)
SELECT c.id, ord.revenue, ord.orders, itm.items
FROM customers c
LEFT JOIN ord ON ord.customer_id = c.id
LEFT JOIN itm ON itm.customer_id = c.id;Что хотят услышать
- Причина: связь один-ко-многим и гранулярность.
- Решение: агрегировать до соединения.
- Как проверять результат.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✓Связь один-ко-многим размножает строки: сначала агрегировать до нужной гранулярности, потом соединять
- ✗Достаточно написать SUM(DISTINCT amount) вместо SUM(amount): повторы строк исчезнут, и сумма станет верной
- ✗Ошибка СУБД при JOIN больших таблиц
- ✗Нужно заменить JOIN на LEFT JOIN — суммы станут правильными
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.