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