Почему JOIN увеличивает выручку

В отчёте было 1 500 рублей. После соединения таблиц стало 2 500. Не обязательно сломались данные: возможно, изменилась единица строки.

SQL Studio / Разборы SQL / Почему JOIN увеличивает выручку

Откуда берутся лишние строки

Для воспроизводимого примера создадим две маленькие таблицы прямо внутри SQL-запроса: sample_orders содержит заказы на 1 000 ₽ и 500 ₽, а sample_items — две позиции первого заказа и одну второго. Соединение размножит сумму первого заказа.

WITH sample_orders(order_id, total_rub) AS (
  VALUES (1, 1000), (2, 500)
),
sample_items(order_id) AS (
  VALUES (1), (1), (2)
)
SELECT SUM(o.total_rub) AS wrong_rub
FROM sample_orders AS o
JOIN sample_items AS i USING (order_id);

Результат такого запроса — 2 500 ₽ вместо 1 500 ₽. Данные не обязательно неверны: соединение изменило уровень детализации с «одна строка — заказ» на «одна строка — позиция заказа».

Как посчитать правильно

Для правильной суммы этих двух заказов достаточно одной строки на заказ. Не нужно соединять их с позициями:

WITH sample_orders(order_id, total_rub) AS (
  VALUES (1, 1000), (2, 500)
)
SELECT SUM(total_rub) AS correct_rub
FROM sample_orders;

На настоящих учебных таблицах SQL Studio суммы хранятся в копейках в столбце orders.gross_kopecks. Если вместе с суммой заказов нужно получить количество позиций, сначала сверните позиции до одной строки на заказ, затем соединяйте:

WITH items_per_order AS (
  SELECT order_id, COUNT(*) AS items_n
  FROM order_items
  GROUP BY order_id
)
SELECT
  SUM(o.gross_kopecks) / 100.0 AS gross_rub,
  SUM(COALESCE(i.items_n, 0)) AS positions_n
FROM orders AS o
LEFT JOIN items_per_order AS i
  ON i.order_id = o.order_id
WHERE o.status = 'delivered';

Полученное gross_rub — сумма стоимости доставленных заказов, а не подтверждённая сумма оплат за вычетом возвратов. Для финансовой выручки проверяйте таблицы payments и refunds отдельно.

Как быстро проверить размножение строк на реальных данных

Не начинайте исправление с SELECT DISTINCT или SUM(DISTINCT amount). Второй вариант вообще потеряет один из заказов, если у разных заказов совпадает сумма. Сначала найдите ключи, для которых соединение вернуло больше одной строки.

SELECT o.order_id, COUNT(*) AS rows_after_join
FROM orders AS o
JOIN order_items AS i ON i.order_id = o.order_id
GROUP BY o.order_id
HAVING COUNT(*) > 1
ORDER BY o.order_id;

На маленькой базе SQL Studio запрос покажет заказы 201, 203 и 205: у каждого по две товарные позиции. Это не ошибка таблицы заказов — это ожидаемая детализация таблицы позиций.

Контроль: сравните COUNT(*) со значением COUNT(DISTINCT o.order_id). Если они отличаются, перед суммированием стоимости заказа верните детализацию к одной строке на заказ.

Отдельно про ошибку фильтра: LEFT JOIN и WHERE. Похожие проблемы: дубликаты, платежные попытки и DISTINCT · агрегирование GROUP BY.

Попрактикуйтесь

В бесплатном тренажёре откройте задачу на соединение заказов с клиентами. Затем попробуйте самостоятельно посчитать заказы по городам и проверить количество строк.


Для последовательной практики на MySQL 8 — курс «SQL для аналитика» на Stepik с итоговой работой и возможностью получить сертификат.