Разберите ситуацию: при обновлении таблицы по данным другой таблицы одному целевому ключу соответствуют несколько строк-источников. Можно ли ожидать, что целевая строка будет обновлена для каждой такой строки?
Нет. При нескольких совпадениях источника нельзя переносимо ожидать последовательного обновления целевой строки для каждого совпадения. Поведение зависит от диалекта: строка может быть изменена один раз, выбранное значение может оказаться неопределённым, а некоторые конструкции могут завершиться ошибкой.
Базовая модель SQL рассматривает обновление как изменение строк целевой таблицы, удовлетворяющих условию. На практике часто требуется вычислять новое значение по другой таблице, поэтому СУБД поддерживают разные формы связывания источника с целью: конструкции обновления через соединение, коррелированные подзапросы и специализированные операторы.
Эти формы имеют различающиеся правила обработки ситуации, когда одной целевой строке соответствует несколько строк источника. Поэтому сам факт совпадения не определяет однозначно, какое значение должно попасть в целевую строку.
Предположим, для одного товара в таблице цен найдено несколько актуальных записей, хотя бизнес-правило допускает только одну. Если обновить каталог через такое соединение, возникает неоднозначность: какую цену использовать?
Попытка полагаться на порядок строк, физический порядок хранения или план выполнения опасна. После изменения индекса, статистики или версии СУБД результат может измениться без изменения данных и текста запроса.
Сначала нужно явно определить правило выбора источника: например, выбрать запись с максимальной датой, использовать приоритет, агрегировать значения или отклонить данные как ошибочные. Затем запрос должен гарантировать не более одной строки источника на каждый целевой ключ.
В PostgreSQL при обновлении через FROM, если одной целевой строке соответствуют несколько строк источника, целевая строка обновляется один раз, но конкретная строка-источник не гарантируется. Это поведение нельзя превращать в переносимое правило для SQL в целом.
Здесь ROW_NUMBER задаёт детерминированный выбор: сначала берётся самая поздняя запись, а price_id разрешает совпадение дат. Важны оба условия: без полного порядка даже выбор первой строки может оставаться неоднозначным.
Альтернатива — предварительно обеспечить уникальность источника ограничением или отдельной проверкой. Это лучше защищает данные, но может потребовать изменения модели и не всегда подходит для исторических таблиц, где несколько записей допустимы.
Команда рассчитывала обновлять остатки товаров по журналу движений. Для одного товара в журнале иногда находилось несколько строк, и итоговый остаток зависел от того, какая строка оказывалась использована.
Рассматривались три варианта: принять произвольное значение, выбрать строку с максимальным временем или сначала агрегировать все движения. Первый вариант был отклонён из-за недетерминированности. Выбор последней строки подходил только для хранения снимков, а для журнала движений корректным оказался расчёт суммы по товару.
Команда сначала агрегировала журнал до одной строки на товар, затем выполняла обновление. В результате повторные запуски стали давать одинаковый результат, а дубликаты источника перестали скрывать ошибку в исходных данных.
Что произойдёт, если в источнике нет совпадения с целевой строкой?
Такая целевая строка обычно не попадает в набор строк для обновления при обновлении через внутреннее связывание с источником. Она не получает значение по умолчанию автоматически и не изменяется, если отдельная логика явно этого не предусматривает.
Это отличается от обновления с условным подзапросом или внешним соединением, где отсутствие источника может быть преобразовано в NULL либо обработано через функцию подстановки. Поэтому способ связывания влияет не только на дубликаты, но и на поведение при отсутствии данных.
Достаточно ли добавить сортировку источника, чтобы выбрать нужную строку?
Нет. Сортировка результата источника сама по себе не является надёжным правилом выбора строки при обновлении. Оптимизатор может изменить план, а оператор обновления не обязан использовать порядок как механизм разрешения множественных совпадений.
Нужно сформировать ровно одну строку на целевой ключ — например, через оконную нумерацию с полным порядком, агрегирование или ограничение уникальности. Только после этого обновление становится детерминированным.
Почему проверка количества строк после обновления не всегда выявляет проблему?
Во многих реализациях целевая строка при нескольких совпадениях обновляется один раз, поэтому число изменённых строк может выглядеть нормальным. Ошибка проявляется не в количестве строк, а в том, какое значение было выбрано.
Надёжнее отдельно проверять уникальность источника: группировать его по целевому ключу и искать группы с количеством больше одного. Так проверяется сама предпосылка обновления, а не только его итоговый счётчик.