Как программно изменить или добавить запись в регистр сведений без ошибок дублирования ключей?

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

При разработке в среде 1С:Предприятие часто возникает задача сохранить данные, связанные с определенным объектом (например, справочником или планом видов расчета), в отдельный независимый регистр сведений. Казалось бы, задача тривиальная: создать запись или обновить её, если она уже существует.

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

В этой статье мы подробно разберем, как правильно работать с объектами НаборЗаписей и МенеджерЗаписи, выясним, почему возникает ошибка дублирования, и рассмотрим корректные алгоритмы обновления данных.

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

Регистр сведений — это таблица, где уникальность строки определяется совокупностью значений его Измерений. Нельзя добавить в регистр две строки с абсолютно одинаковым набором измерений.

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

  1. Мы пытаемся добавить новую строку (метод Добавить()), не удалив старую с теми же измерениями.
  2. Мы используем метод записи набора с параметром Ложь (не замещать), когда запись уже есть.

Давайте рассмотрим два основных способа решения задачи: использование набора записей с отбором и использование менеджера записи.

Способ 1. Использование Набора Записей (Рекомендуемый для работы из кода)

Объект РегистрСведенийНаборЗаписей предназначен для работы с группой записей. Главная особенность метода Записать() у набора записей заключается в его параметре Замещать.

Рассмотрим правильный алгоритм изменения записи через набор. Наша цель: прочитать запись по конкретному измерению, изменить ресурсы, а если записи нет — создать новую.

Разберем код по шагам:


// 1. Создаем набор записей
НаборЗаписей = РегистрыСведений.Расш_2_Соответствие.СоздатьНаборЗаписей();

// 2. Устанавливаем ОТБОР. Это критически важно!
// Если не установить отбор, при записи(Истина) мы можем очистить весь регистр,
// либо при чтении вытянуть всю таблицу в память.
НаборЗаписей.Отбор.Соответ_Начисления.Установить(Объект.Ссылка);

// 3. Считываем данные из базы по этому отбору
НаборЗаписей.Прочитать();

// 4. Проверяем, есть ли запись
Если НаборЗаписей.Количество() > 0 Тогда
    // Запись есть, мы её модифицируем.
    // Так как отбор стоял по конкретной ссылке, скорее всего там одна запись.
    Для Каждого Запись Из НаборЗаписей Цикл
        Запись.ОснованиеОМС = ЭтаФорма.ОснованияНеОблагаемыеПФ;
        Запись.ОснованиеСПБ = ЭтаФорма.ОснованияНеОблагаемыеСПБ;
        Запись.ОснованиеПФ  = ЭтаФорма.ОснованияНеОблагаемыеПФ;
    EndDo;
Иначе
    // Записи нет, создаем новую
    НоваяЗапись = НаборЗаписей.Добавить();
    // Обязательно заполняем измерение, по которому делали отбор
    НоваяЗапись.Соответ_Начисления = Объект.Ссылка; 
    
    // Заполняем ресурсы
    НоваяЗапись.ОснованиеОМС = ЭтаФорма.ОснованияНеОблагаемыеПФ;
    НоваяЗапись.ОснованиеСПБ = ЭтаФорма.ОснованияНеОблагаемыеСПБ;
    НоваяЗапись.ОснованиеПФ  = ЭтаФорма.ОснованияНеОблагаемыеПФ;
КонецЕсли;

// 5. Записываем с замещением (Истина - значение по умолчанию)
// Система удалит старую запись (если она была) по нашему отбору 
// и запишет новую (или измененную) из набора.
НаборЗаписей.Записать(); 

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

Способ 2. Использование Менеджера Записи

Если регистр независимый и непериодический (или вы точно знаете период), и вам нужно работать строго с одной строкой, удобно использовать РегистрСведенийМенеджерЗаписи. Он работает похожим на справочники образом: нашли, изменили, записали.

Посмотрим на пример реализации через менеджер:


// 1. Создаем менеджер
МенеджерЗаписи = РегистрыСведений.Расш_2_Соответствие.СоздатьМенеджерЗаписи();

// 2. Устанавливаем значения ключевых полей (Измерений)
МенеджерЗаписи.Соответ_Начисления = Объект.Ссылка;

// 3. Пробуем прочитать запись из базы
МенеджерЗаписи.Прочитать();

// 4. Проверяем, нашлась ли запись
Если МенеджерЗаписи.Выбран() Тогда
    // Запись существует, обновляем ресурсы
    МенеджерЗаписи.ОснованиеОМС = ЭтаФорма.ОснованияНеОблагаемыеПФ;
    МенеджерЗаписи.ОснованиеСПБ = ЭтаФорма.ОснованияНеОблагаемыеСПБ;
Иначе
    // Записи нет. Измерения уже установлены в шаге 2.
    // Просто заполняем ресурсы. При записи создастся новая строка.
    МенеджерЗаписи.ОснованиеОМС = ЭтаФорма.ОснованияНеОблагаемыеПФ;
    МенеджерЗаписи.ОснованиеСПБ = ЭтаФорма.ОснованияНеОблагаемыеСПБ;
КонецЕсли;

// 5. Записываем
МенеджерЗаписи.Записать();

Этот код более читаем для операций с одиночными записями. Однако, если вам нужно изменить 100 записей подряд, использование Менеджера Записи в цикле будет работать медленнее, чем один Набор Записей.

Анализ ошибки с Записать(Ложь)

В ходе обсуждения часто всплывала попытка использовать конструкцию НаборЗаписей.Записать(Ложь). Давайте разберем, почему это приводило к ошибке.

Параметр Ложь означает режим "Добавление" (не замещать). Логика платформы в этом случае такая:

  1. Платформа берет записи из набора.
  2. Пытается выполнить SQL-инструкцию INSERT в таблицу базы данных.
  3. Если в базе уже есть строка с такими же измерениями, СУБД возвращает ошибку уникальности ключа.

Параметр Истина (режим "Замещение") работает иначе:

  1. Платформа смотрит на Отбор набора записей.
  2. Выполняет удаление (DELETE) всех записей, попадающих под этот отбор.
  3. Выполняет вставку (INSERT) записей, которые сейчас находятся в коллекции набора.

Именно поэтому использование Записать(Истина) вместе с правильно установленным отбором является надежным способом обновления данных: старая версия удаляется, новая записывается.

Типичная ошибка: Отбор по ссылке на новый объект

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

Если код выполняется в событии ПередЗаписью на клиенте или сервере для совершенно нового объекта, ссылка может быть еще пустой или "битой" (в зависимости от момента подписки на событие и получения ссылки нового объекта). Рекомендуется писать в зависимые регистры сведений в событии ПриЗаписи, когда основной объект уже гарантированно записан в БД и имеет валидную Ссылку.

Итоговое резюме

  1. Для изменения записей используйте НаборЗаписей с установленным Отбором по ключевым измерениям.
  2. Всегда используйте метод Прочитать() после установки отбора, чтобы получить текущее состояние данных.
  3. Используйте метод Записать() (или Записать(Истина)) для обновления данных. Это безопасно перезапишет данные по установленному отбору.
  4. Избегайте чтения всего регистра без отборов — это убивает производительность.
  5. Для точечной работы с одной записью можно использовать МенеджерЗаписи.
← На главную