Как записать в регистр сведений только те данные документа, которых еще нет в базе?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При проведении документов разработчики нередко сталкиваются с платформенной ошибкой: «Запись с такими ключевыми полями существует!». Эта ситуация возникает, когда формируемые движения дублируют ключевые поля уже имеющихся записей в регистре сведений либо повторяются внутри одного и того же набора движений документа. Разберем детально, почему возникает эта ошибка и как корректно отобрать для записи только новые данные.

Причины возникновения ошибки уникальности записей

Прежде чем приступать к фильтрации данных, проанализируем архитектуру регистра сведений и структуру его уникального ключа:

  1. Регистр подчинен регистратору: В таких регистрах измерение Регистратор входит в состав первичного ключа. Это означает, что два разных документа физически не могут конфликтовать между собой по уникальности, так как ссылки на регистраторы у них различаются. Если ошибка возникает при проведении подчиненного регистра, причина кроется в дублировании ключевых полей (измерений и периода) внутри самого набора записей проводимого документа.
  2. Регистр независимый: Если документ программно формирует записи в независимый регистр сведений, уникальность определяется комбинацией всех измерений (а также поля Период для периодических регистров). Если хотя бы одна строка с таким же набором измерений уже записана в базе, платформенная транзакция записи завершится аварийно.

Важное ограничение: Не пытайтесь обойти ошибку с помощью конструкции Попытка ... Исключение при стандартном проведении. Запись движений документа происходит в транзакции. Ошибка СУБД из-за нарушения уникальности переводит всю транзакцию в статус невосстановимой, поэтому выборочно пропустить сбойную запись внутри одной транзакции невозможно.

Способ 1. Фильтрация существующих записей через ЛЕВОЕ СОЕДИНЕНИЕ в запросе

Наиболее надежный и производительный способ отобрать только отсутствующие записи — соединить исходную таблицу документа с физической таблицей регистра сведений в запросе проведения.

Рассмотрим по шагам формирование движений:

  1. Передаем табличную часть документа в запрос как параметр (или считываем ее напрямую из документа).
  2. Соединяем данные документа с таблицей регистра оператором ЛЕВОЕ СОЕДИНЕНИЕ по всем ключевым полям (период и измерения).
  3. Устанавливаем условие ГДЕ РегистрСведений.ЛюбоеИзмерение IS NULL, отсекая строки, которые уже есть в базе данных.

Посмотрим на пример текста запроса для формирования движений:


ВЫБРАТЬ
    ДокТовары.Номенклатура КАК Номенклатура,
    ДокТовары.Характеристика КАК Характеристика,
    ДокТовары.Цена КАК Цена,
    &ПериодДвижений КАК Период
ПОМЕСТИТЬ ВТ_ДанныеДокумента
ИЗ
    &ТаблицаТовары КАК ДокТовары
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ.Период КАК Период,
    ВТ.Номенклатура КАК Номенклатура,
    ВТ.Характеристика КАК Характеристика,
    ВТ.Цена КАК Цена
ИЗ
    ВТ_ДанныеДокумента КАК ВТ
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Регистр
        ПО ВТ.Период = Регистр.Период
        И ВТ.Номенклатура = Регистр.Номенклатура
        И ВТ.Характеристика = Регистр.Характеристика
ГДЕ
    Регистр.Номенклатура IS NULL

После выполнения запроса мы выгружаем только уникальные строки в набор записей и сохраняем их без риска получить конфликт блокировок или исключение уникальности.

Способ 2. Устранение дубликатов внутри документа (Сворачивание данных)

Если регистр подчинен регистратору, но ошибка все равно появляется, выясним причину: в строках табличной части документа несколько раз встречается одинаковая номенклатура (или одинаковая комбинация измерений).

Для устранения дублирования внутри документа обязательно выполняем группировку в запросе:


ВЫБРАТЬ
    Док.Номенклатура КАК Номенклатура,
    Док.Склад КАК Склад,
    СУММА(Док.Количество) КАК Количество
ИЗ
    Документ.ПоступлениеТоваров.Товары КАК Док
ГДЕ
    Док.Ссылка = &Ссылка
СГРУППИРОВАТЬ ПО
    Док.Номенклатура,
    Док.Склад

Если формирование таблицы выполняется в коде без запроса, перед записью набора применяем метод Свернуть() к таблице значений:


ТаблицаДвижений.Свернуть("Период, Номенклатура, Характеристика", "Количество, Сумма");
Движения.ОстаткиТоваров.Загрузить(ТаблицаДвижений);

Способ 3. Работа с независимым регистром через Менеджер Записи

Если задача состоит в программном заполнении независимого регистра сведений вне проведения документа, и требуется перезаписывать существующие записи или пропускать их, используем объект РегистрСведенийМенеджерЗаписи.

Разберем алгоритм построчной проверки:


Для Каждого Стр Из ТаблицаДанных Цикл
    МенеджерЗаписи = РегистрыСведений.ЦеныНоменклатуры.СоздатьМенеджерЗаписи();
    МенеджерЗаписи.Период = Стр.Период;
    МенеджерЗаписи.Номенклатура = Стр.Номенклатура;
    
    // Считываем текущую запись из базы
    МенеджерЗаписи.Прочитать();
    
    // Если запись не найдена, заполняем ресурсы и записываем
    Если НЕ МенеджерЗаписи.Выбран() Тогда
        МенеджерЗаписи.Период = Стр.Период;
        МенеджерЗаписи.Номенклатура = Стр.Номенклатура;
        МенеджерЗаписи.Цена = Стр.Цена;
        МенеджерЗаписи.Записать(Истина);
    КонецЕсли;
КонецЦикла;

Контроль параллельности и управляемые блокировки

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


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ЦеныНоменклатуры");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = ТаблицаДанных;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
Блокировка.Заблокировать();

Блокировка гарантирует, что отобранные запросом данные останутся уникальными вплоть до успешного завершения транзакции проведения документа.

← На главную