← Все курсы · Курс SQL

Почему ваш JOIN дублирует строки, и общая сумма слишком велика

Майкл Носито · Обновлено в августе 2026 г. · Каждый запрос на этой странице был выполнен до его публикации.

Запрос был выполнен, цифры вернулись, и доход оказался выше, чем предполагалось в финансовом отделе. Ничего не ошибочно. Где-то в запросе соединение сопоставляло одну строку с несколькими, и каждое значение на одной стороне учитывалось более одного раза.

Что вы делаете: подсчитываете строки до и после объединения. Если число выросло, значит, произошло разветвление, и каждая сумма в запросе неверна.

Краткая версия. Соединение умножает строки всякий раз, когда ключ повторяется на другой стороне.

Две таблицы

CREATE TABLE orders(order_id INT, customer_id INT, amount INT, status TEXT);
INSERT INTO orders VALUES
(101,1,120,'shipped'),(102,1,80,'shipped'),(103,2,200,'cancelled'),
(104,2,50,'shipped'),(105,3,300,'shipped'),(106,4,90,'pending'),
(107,4,60,'shipped'),(108,5,150,'cancelled');

CREATE TABLE payments(payment_id INT, order_id INT, amount INT);
INSERT INTO payments VALUES
(1,101,60),(2,101,60),(3,102,80),(4,104,50),(5,105,300),(6,107,60);

Восемь заказов на общую сумму 1050 штук. Шесть платежей. Заказ 101 был оплачен двумя частями по 60, это единственная необычность в данных и этого достаточно.

Запрос, который идет не так

SELECT COUNT(*), SUM(o.amount)
FROM orders o
JOIN payments p ON p.order_id = o.order_id;
-- 6 rows, 730

Пять заказов имеют выплаты: 101, 102, 104, 105 и 107. Их истинная сумма равна 120 + 80 + 50 + 300 + 60 = 610. В запросе указано 730, поскольку порядок 101 появляется дважды и дважды вносит свои 120.

Шесть рядов для пяти заказов. Эта единственная дополнительная строка и есть вся ошибка, а 730 — достаточно правдоподобное число, поэтому никто не ставит его под сомнение, пока его с чем-то не сравнят.

Обнаружение за десять секунд

SELECT COUNT(*) FROM orders;                        -- 8
SELECT COUNT(*) FROM orders o
JOIN payments p ON p.order_id = o.order_id;         -- 6, from 5 orders

SELECT order_id, COUNT(*) FROM payments
GROUP BY order_id HAVING COUNT(*) > 1;              -- 101, 2

Последний запрос следует сохранить. Он называет повторяющийся ключ, который превращает «цифры слишком велики» в «заказ 101 имеет две строки платежей», и это предложение, по которому кто-то может действовать.

Разветвленные соединения. Объедините три таблицы, две из которых разветвляются, а умножение равно 2 х 3, а не 2 + 3. Вот почему общая сумма оказывается в шесть раз больше, и именно поэтому количество строк следует проверять после каждого соединения, а не только в конце.

Три правильных исправления

1. Сначала агрегируйте множество сторон

SELECT o.order_id, o.amount, COALESCE(p.paid, 0) AS paid
FROM orders o
LEFT JOIN (
    SELECT order_id, SUM(amount) AS paid
    FROM payments
    GROUP BY order_id
) p ON p.order_id = o.order_id;

Подзапрос возвращает одну строку на каждый заказ, поэтому соединение не может ничего умножать. Заказ 101 поступает в виде одной строки со 120 оплаченными заказами. Это ответ по умолчанию, к которому следует обратиться в первую очередь.

2. Используйте EXISTS, когда вам нужно знать, существует ли совпадение.

SELECT COUNT(*), SUM(amount)
FROM orders o
WHERE EXISTS (SELECT 1 FROM payments p WHERE p.order_id = o.order_id);
-- 5 rows, 610

Пять заказов, 610. EXISTS задает вопрос «да» или «нет», поэтому он не может дублировать строку, сколько бы платежей ни было. Если вы присоединяетесь только для фильтрации, это правильно и обычно быстрее.

3. Агрегируйте с правильной функцией

SELECT COUNT(DISTINCT o.order_id) AS orders,
       SUM(p.amount)               AS paid
FROM orders o
JOIN payments p ON p.order_id = o.order_id;

Суммируем столбец из Много сторона, которая имеет одну строку на каждый платеж и поэтому не дублируется, а с одной стороны считается отдельной. Если вы их перепутаете, это исправление пойдет не так: SUM(o.amount) все равно 730, сколько бы вы ни считали.

Почему DISTINCT не является решением проблемы

SELECT SUM(DISTINCT o.amount) FROM ...   -- wrong, and quietly so

SELECT DISTINCT удаляет повторяющиеся строки, что выглядит так, как будто это решает проблему, но это не так. Два разных порядка ровно по 60 объединяются в один, поэтому общая сумма теперь слишком мала. И совсем не может помочь, когда дублирующиеся строки различаются в каком-либо столбце, что обычно происходит, когда вы выбираете еще несколько полей.

Достижение DISTINCT фиксирование суммы — самый явный признак того, что уровень детализации запроса не определено. Вместо этого решите: одна строка на заказ или одна строка на платеж, и ориентируйтесь на это.

Привычка, которая мешает этому

Прежде чем присоединиться, спросите затем
Какова одна строка моего результата?Запишите это. Это уровень детализации.
Является ли ключ соединения уникальным на другой стороне?Проверьте с помощью GROUP BY и HAVING.
Число строк увеличилось?Считайте до и после каждого соединения
Соответствует ли моя сумма известному числу?Примириться с чем-то внешним

Как применить это в своей работе

  1. Возьмите запрос, на который вы полагаетесь, и посчитайте строки после каждого соединения. Добавьте их в качестве комментария в запросе.
  2. Запустите проверку дубликатов ключей для каждой таблицы, к которой вы присоединяетесь. Уникальность является предположением, пока оно не проверено.
  3. Замените любой DISTINCT это было добавлено для фиксации суммы с помощью предварительно агрегированного подзапроса.
  4. Если соединение существует только для фильтрации, перепишите его как EXISTS.
  5. Сверьте полученную сумму с числом из другой системы. Это единственная проверка, позволяющая обнаружить разветвление, которое вы даже не подумали искать.

Единственная привычка, которую нужно сохранить

Скажите, что представляет собой одна строка результата, прежде чем писать FROM. Разветвление на самом деле не является проблемой объединения; это то, что происходит, когда запрос пишется без определения его глубины.

Отчет, который вы отправляете чаще всего, объединяется с таблицей, уникальность ключа которой вы никогда не проверяли?

Каждый номер здесь был проведён до того, как был опубликован. Восемь заказов на общую сумму 1050 штук. Соединение возвращает 6 строк и сумму 730, где сумма пяти совпавших заказов действительно равна 610.
Сложнее всего заметить ошибку, превышающую сумму ровно на одну строку.

SQL для аналитиков — это 458 страниц, которые считывают запросы построчно в повседневных словах, поэтому ошибочный результат нужно где-то отслеживать, а не перепечатывать, пока он не станет выглядеть лучше.

SQL для аналитиков, 19 $ →
Прежде чем суммировать, посчитайте.

Объяснение SQL JOIN охватывает четыре типа, Согласование количества строк это чек, и SQL-практикум дает вам повторения.

Читать SQL JOIN →