При разработке алгоритмов проведения документов разработчики нередко сталкиваются с исключением платформы: «Запись с такими ключевыми полями уже существует». Появление этой ошибки часто вызывает недоумение, особенно когда создается совершенно новый документ с уникальной датой и новым номером. Разберем механику этой ошибки, выясним истинные причины её возникновения и рассмотрим надежные способы её устранения.
Сообщение об ошибке означает, что платформа попыталась записать в базу данных строку, первичный ключ которой совпадает с уже имеющейся строкой в этой же транзакции или в таблице базы. Чтобы понять, почему это происходит, проанализируем различия в структуре ключей разных типов регистров:
(Регистратор, НомерСтроки). Комбинация измерений здесь не обязана быть уникальной: регистр накопления штатно допускает сколько угодно строк с одинаковой номенклатурой, складом и датой. Однако если нарушается уникальность номеров строк или документ параллельно делает запись в сопутствующий регистр сведений с дублями аналитики, возникает рассматриваемая коллизия.Рассмотрим самую распространенную ситуацию, с которой столкнулся автор вопроса. В документе построчно обходится табличная часть, и на каждой итерации формируется движение. Если пользователь указал одинаковую номенклатуру в нескольких строках табличной части, либо если алгоритм заполняет движения сопутствующего регистра сведений, возникает исключение.
Посмотрим на пример ошибочного кода, в котором строки табличной части переносятся в набор записей без предварительной проверки:
Для Каждого СтрТЧ Из ТабличнаяЧасть Цикл
Движение = Движения.ОстаткиНоменклатуры.Добавить();
Движение.Период = Дата;
Движение.Номенклатура = СтрТЧ.Номенклатура;
Движение.Количество = СтрТЧ.Количество;
КонецЦикла;
Если в аналогичном цикле выполняется запись в регистр сведений, где измерениями являются Номенклатура и Склад, то первая же повторяющаяся строка приведет к аварийной остановке проведения с ошибкой ключевых полей.
Стандартом разработки на платформе «1С:Предприятие» является отказ от простого перебора табличной части в цикле в пользу выполнения запроса. Использование конструкции СГРУППИРОВАТЬ ПО гарантирует, что все одинаковые позиции схлопнутся в одну запись, а их количественные и суммовые показатели просуммируются.
Разберем пример правильного формирования движений через выборку запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДокТЧ.Номенклатура КАК Номенклатура,
| СУММА(ДокТЧ.Количество) КАК Количество
|ИЗ
| Документ.РасходнаяНакладная.Товары КАК ДокТЧ
|ГДЕ
| ДокТЧ.Ссылка = &Ссылка
|СГРУППИРОВАТЬ ПО
| ДокТЧ.Номенклатура";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Движение = Движения.ОстаткиНоменклатуры.ДобавитьРасход();
Движение.Период = Дата;
Движение.Номенклатура = Выборка.Номенклатура;
Движение.Количество = Выборка.Количество;
КонецЦикла;
Если данные для движений рассчитываются программно в памяти и помещаются в объект ТаблицаЗначений, перед загрузкой или переносом строк в набор записей необходимо применить метод Свернуть(). Разберем, как это работает на практике:
ТаблицаДвижений = ТабличнаяЧасть.Выгрузить();
ТаблицаДвижений.Свернуть("Номенклатура, Склад", "Количество, Сумма");
Для Каждого Стр Из ТаблицаДвижений Цикл
Движение = Движения.ОстаткиНоменклатуры.Добавить();
Движение.Период = Дата;
Движение.Номенклатура = Стр.Номенклатура;
Движение.Склад = Стр.Склад;
Движение.Количество = Стр.Количество;
КонецЦикла;
Если ошибка возникает непосредственно в регистре накопления, обратим внимание на поле НомерСтроки. Платформа формирует его автоматически, но при определенных манипуляциях эта логика может нарушаться:
Загрузить() для другого набора, в нем окажутся дублирующиеся значения колонки НомерСтроки. Чтобы избежать ошибки, удалим колонку номеров перед загрузкой: ТаблицаИсточник.Колонки.Удалить("НомерСтроки");.НомерСтроки, любая ошибка в счетчике итераций приведет к созданию двух строк с одинаковым индексом, что вызовет конфликт ключа таблицы движений. Не заполняйте этот реквизит вручную без крайней необходимости.АвтоУстановкаНомераСтроки установлено в значение Истина.Еще один источник конфликта уникальности — повторная запись набора без очистки старых данных. Если документ перепроводится, а движения записываются методом Записать(Ложь) (без замещения), новые строки попытаются добавиться к уже существующим в базе движениям этого регистратора.
В процедуре ОбработкаПроведения всегда управляйте наборами записей стандартным образом: используйте свойство Движения.ИмяРегистра.Записывать = Истина; либо вызывайте запись с явным замещением существующих записей:
Движения.ОстаткиНоменклатуры.Записать(Истина);
Если в документе задействовано множество регистров и неочевидно, какой именно из них вызывает исключение, применим отладку: