Получить доступ

Почему после JOIN суммы в отчёте оказались больше, чем должны быть?

JuniorSQL для аналитикаSQL
Ещё спрашивают так:
  • Что такое «размножение строк» при JOIN?
Коротко

Связь один-ко-многим размножает строки: заказ с тремя позициями после JOIN с позициями превращается в три строки, и SUM(order.amount) учитывает сумму заказа трижды. Решение — сначала агрегировать каждую таблицу до нужной гранулярности, потом соединять; или считать по правильной таблице.

  • Проверка: количество строк до и после JOIN, COUNT(DISTINCT ключ)
  • Два JOIN «ко многим» из одной таблицы дают декартово произведение между ними
  • COUNT(DISTINCT) спасает подсчёт, но не суммы

А ещё Дубли в справочнике (две записи на один ключ) дают тот же эффект молча

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

Гранулярность. У каждой таблицы своя «единица строки»: клиент, заказ, позиция заказа, событие. 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;

Что хотят услышать

  • Причина: связь один-ко-многим и гранулярность.
  • Решение: агрегировать до соединения.
  • Как проверять результат.

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем