Короткий ответ
Скорее всего произошёл «fan-out» — соединение таблицы заказов (одна строка на заказ) с таблицей позиций (несколько строк на заказ) размножило каждую строку заказа на число позиций в нём, и последующий SUM(orders.total_amount) просуммировал одно и то же значение несколько раз.
Как это работает подробнее
Как это работает: JOIN по своей природе образует по одной строке результата на каждую пару совпавших строк с обеих сторон; если на один order_id в order_items приходится 3 строки, то после JOIN поле orders.total_amount окажется повторено 3 раза — и SUM по нему будет втрое больше реального.
Пример кода
PostgreSQL: устранение fan-out
WITH item_totals AS (
SELECT order_id,
SUM(quantity) AS items_cnt,
SUM(quantity * unit_price) AS items_amount
FROM order_items
GROUP BY order_id
)
SELECT o.order_id, o.total_amount,
i.items_cnt, i.items_amount
FROM orders AS o
LEFT JOIN item_totals AS i USING (order_id);
SELECT SUM(total_amount) AS revenue
FROM orders;Что использовать на практике
- На практике: правильный способ зависит от задачи: если нужна сумма заказов — считать
SUM(DISTINCT ...)небезопасно (может отфильтровать легитимные совпадающие суммы у разных заказов), поэтому лучше либо агрегироватьorder_itemsдо одной строки на заказ заранее (в CTE), либо считать суммуorders.total_amountиз отдельного запроса без JOIN к позициям, а детализацию по товарам получать отдельно. - Проверка: Проверка: сравнить оба результата — если первый кратно больше второго, это явный признак fan-out, кратность примерно равна среднему числу позиций на заказ.
Подводные камни
- Частые ошибки: не проверяют соотношение «один-к-одному» или «один-к-многим» перед JOIN (см. вопрос про проверку уникальности ключа) и обнаруживают fan-out только постфактум, когда итоговая цифра выглядит подозрительно большой.