Почему возникает ошибка «Запись с такими ключевыми полями уже существует» при проведении документа?

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

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

Природа ошибки и механизм контроля уникальности

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

  1. Регистры сведений. Для независимого регистра уникальность определяется набором измерений (а также периодом, если регистр периодический). Если в коде формируются две строки с полностью идентичными измерениями и одинаковым периодом, СУБД отвергает запись. Для подчиненных регистратору регистров сведений в ключ входит регистратор, но дублирование измерений в рамках одного документа также недопустимо.
  2. Регистры накопления. На уровне базы данных уникальным ключом таблицы движений регистра накопления выступает пара (Регистратор, НомерСтроки). Комбинация измерений здесь не обязана быть уникальной: регистр накопления штатно допускает сколько угодно строк с одинаковой номенклатурой, складом и датой. Однако если нарушается уникальность номеров строк или документ параллельно делает запись в сопутствующий регистр сведений с дублями аналитики, возникает рассматриваемая коллизия.

Причина 1: Дублирование строк и запись одинаковых движений в цикле

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

Посмотрим на пример ошибочного кода, в котором строки табличной части переносятся в набор записей без предварительной проверки:


Для Каждого СтрТЧ Из ТабличнаяЧасть Цикл
    Движение = Движения.ОстаткиНоменклатуры.Добавить();
    Движение.Период = Дата;
    Движение.Номенклатура = СтрТЧ.Номенклатура;
    Движение.Количество = СтрТЧ.Количество;
КонецЦикла;

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

Способ решения 1: Формирование движений запросом с группировкой

Стандартом разработки на платформе «1С:Предприятие» является отказ от простого перебора табличной части в цикле в пользу выполнения запроса. Использование конструкции СГРУППИРОВАТЬ ПО гарантирует, что все одинаковые позиции схлопнутся в одну запись, а их количественные и суммовые показатели просуммируются.

Разберем пример правильного формирования движений через выборку запроса:


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

Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    Движение = Движения.ОстаткиНоменклатуры.ДобавитьРасход();
    Движение.Период = Дата;
    Движение.Номенклатура = Выборка.Номенклатура;
    Движение.Количество = Выборка.Количество;
КонецЦикла;

Способ решения 2: Сворачивание промежуточной таблицы значений

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


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

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

Причина 2: Проблемы со свойством «НомерСтроки» в наборе записей

Если ошибка возникает непосредственно в регистре накопления, обратим внимание на поле НомерСтроки. Платформа формирует его автоматически, но при определенных манипуляциях эта логика может нарушаться:

  1. Загрузка из таблицы значений с готовой колонкой номеров. Если выгрузить строки из одного набора в таблицу значений, а затем использовать метод Загрузить() для другого набора, в нем окажутся дублирующиеся значения колонки НомерСтроки. Чтобы избежать ошибки, удалим колонку номеров перед загрузкой: ТаблицаИсточник.Колонки.Удалить("НомерСтроки");.
  2. Ручное назначение номеров строк. Если в цикле вручную переопределяется свойство НомерСтроки, любая ошибка в счетчике итераций приведет к созданию двух строк с одинаковым индексом, что вызовет конфликт ключа таблицы движений. Не заполняйте этот реквизит вручную без крайней необходимости.
  3. Отключение автоматической нумерации. Убедимся, что свойство набора записей АвтоУстановкаНомераСтроки установлено в значение Истина.

Способ решения 3: Корректное использование режима замещения

Еще один источник конфликта уникальности — повторная запись набора без очистки старых данных. Если документ перепроводится, а движения записываются методом Записать(Ложь) (без замещения), новые строки попытаются добавиться к уже существующим в базе движениям этого регистратора.

В процедуре ОбработкаПроведения всегда управляйте наборами записей стандартным образом: используйте свойство Движения.ИмяРегистра.Записывать = Истина; либо вызывайте запись с явным замещением существующих записей:

Движения.ОстаткиНоменклатуры.Записать(Истина);

Как локализовать ошибку при отладке

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

  1. В конфигураторе откроем меню Отладка — Остановка по ошибке.
  2. Включим флаг Останавливаться по ошибке.
  3. Повторим проведение документа в режиме предприятия.
  4. Платформа прервет выполнение ровно на той строке, где вызывается запись проблемного набора. В окне стека вызовов и локальных переменных сразу будет виден конкретный регистр, вызвавший исключение.
← На главную