В отчёте одна строка заказа повторяется после соединения с таблицей позиций. Какой механизм определяет числ...

В отчёте одна строка заказа повторяется после соединения с таблицей позиций. Какой механизм определяет число получившихся строк?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Число строк после JOIN определяется количеством совпадающих пар строк, а не количеством строк в одной из таблиц. Если один заказ связан с тремя позициями, он появится в результате три раза; при соединении двух неуникальных сторон количество строк может вырасти как произведение числа совпадений.

Исторический контекст

JOIN в реляционной модели нужен для восстановления связанных данных, которые хранятся в отдельных отношениях. Разделение данных уменьшает дублирование, но при чтении требует явно определить, какие строки образуют пары.

Результат соединения — это набор таких пар. Поэтому SQL не воспринимает соединение как простое добавление столбцов к одной строке: каждая подходящая комбинация строк становится отдельной строкой результата.

Постановка проблемы

Предположим, в таблице заказов один заказ представлен одной строкой, а в таблице позиций для него есть несколько строк. Соединение по идентификатору заказа вернёт одну строку результата для каждой позиции.

Если затем присоединить ещё одну таблицу с несколькими совпадениями, строки размножатся повторно. Например, один заказ с тремя позициями и двумя платежами может дать шесть строк при соединении позиций и платежей напрямую.

Такое размножение опасно для агрегатов: сумма заказа, количество товаров или сумма платежей могут быть завышены. Добавление DISTINCT иногда скрывает дубли, но не устраняет ошибочную кардинальность и может удалить действительно различные строки.

Подробное решение

Для каждой строки левой таблицы SQL проверяет строки правой таблицы, удовлетворяющие условию соединения. В результат попадает каждая подходящая пара, поэтому при m совпадениях слева и n совпадениях справа образуется до m × n пар для соответствующей группы ключа.

WITH orders(id) AS ( VALUES (10) ), items(order_id, product) AS ( VALUES (10, 'A'), (10, 'B'), (10, 'C') ) SELECT o.id, i.product FROM orders AS o JOIN items AS i ON i.order_id = o.id;

Для заказа с идентификатором 10 результат содержит три строки. Если требуется одна строка на заказ, сначала нужно определить требуемую гранулярность результата: агрегировать позиции до одной строки на заказ, выбрать одну запись по явному правилу или использовать EXISTS, если нужна только проверка наличия.

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

INNER JOIN исключает строки без совпадений. LEFT JOIN сохраняет строку левой таблицы без совпадений, но при наличии нескольких совпадений всё равно создаёт несколько строк; тип соединения не отменяет размножение.

Ситуация из практики

В отчёте требовалась одна строка на заказ с общей суммой позиций и суммой платежей. Прямое соединение заказов одновременно с позициями и платежами дало завышенные суммы: три позиции и два платежа превратились в шесть комбинаций.

Рассматривались три варианта. DISTINCT мог убрать только полностью одинаковые строки и не исправлял суммы. Соединение всех деталей сохраняло максимум информации, но требовало группировки с учётом произведения строк и усложняло расчёт. Предварительная агрегация каждой детали до одной строки на заказ устраняла размножение, хотя добавляла отдельные этапы обработки.

Был выбран третий вариант: позиции и платежи сначала агрегировались отдельно по идентификатору заказа, затем присоединялись к заказам. В результате сохранялась гранулярность одна строка на заказ, а суммы рассчитывались корректно.

Что кандидаты часто упускают

  1. Всегда ли повторение строки означает ошибку в данных?

Нет. Повторение может быть правильным результатом связи один-ко-многим. Ошибка возникает не из-за самого повторения, а когда ожидаемая гранулярность результата не совпадает с фактической или когда размножение искажает последующие вычисления.

  1. Почему соединение двух таблиц с уникальными ключами не гарантирует уникальность результата?

Уникальность должна быть релевантна именно условию соединения. Если ключ уникален только в одной таблице, а в другой встречается несколько раз, результат будет один-ко-многим. Если ключ не уникален с обеих сторон, возникает многие-ко-многим и число комбинаций растёт перемножением совпадений.

  1. Когда вместо JOIN следует использовать EXISTS?

Когда нужно проверить наличие связанной строки, но не выводить её атрибуты и не создавать по одной строке на каждое совпадение. EXISTS логически проверяет факт существования хотя бы одной подходящей строки, поэтому дополнительные совпадения не размножают результат внешнего запроса. Если же данные связанной таблицы нужны в результате, EXISTS не заменяет соединение без дополнительной логики выбора или агрегации.