Как избежать дублирования записей при записи в регистр сведений в 1С?

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

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

Выясним причину возникновения ошибки

В платформе «1С:Предприятие» уникальность записей в регистре сведений обеспечивается совокупностью всех его измерений. Если регистр является периодическим, то в ключ уникальности добавляется еще и Период. Ошибка возникает в двух случаях:

  1. Мы пытаемся добавить в набор записей (РегистрСведенийНаборЗаписей) строку, которая уже физически есть в базе данных, при этом параметр Замещать установлен в значение Ложь.
  2. В самом коде программы в цикле в набор записей дважды добавляется одна и та же комбинация измерений.

Рассмотрим по шагам основные стратегии решения этой задачи.

Способ 1. Использование таблицы значений и метода Свернуть

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

Разберем алгоритм действий:

  1. Создадим таблицу значений с колонками, идентичными измерениям и ресурсам регистра.
  2. Наполним таблицу всеми необходимыми данными, включая новые и потенциально старые.
  3. Используем метод Свернуть(), чтобы избавиться от дублей в самой таблице.

Посмотрим на пример кода:


// Создаем таблицу значений
ТЗ_Данные = Новый ТаблицаЗначений;
ТЗ_Данные.Колонки.Добавить("Сотрудник");
ТЗ_Данные.Колонки.Добавить("Подразделение");
ТЗ_Данные.Колонки.Добавить("Оклад");

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

// Удаляем дубликаты по измерениям (Сотрудник, Подразделение)
// Суммируем ресурсы (Оклад), если это необходимо
ТЗ_Данные.Свернуть("Сотрудник, Подразделение", "Оклад");

После того как таблица «свернута», мы можем смело загружать её в регистр, будучи уверенными, что внутри набора данных дублей нет.

Способ 2. Работа с менеджером записи для единичных изменений

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

Рассмотрим подробнее программную реализацию:


МенеджерЗаписи = РегистрыСведений.НашРегистр.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Сотрудник = ВыбранныйСотрудник;
МенеджерЗаписи.Период = ТекущаяДата();

// Пытаемся найти существующую запись
МенеджерЗаписи.Прочитать();

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

Метод Прочитать() заполняет менеджер записи данными из БД. Если запись найдена, свойство Выбран() вернет Истина, и вы сможете обновить данные без ошибки уникальности.

Способ 3. Управление параметром «Замещать» в наборе записей

Важно понимать, как работает метод Записать() у объекта НаборЗаписей. У этого метода есть необязательный параметр Замещать.

Если мы устанавливаем НаборЗаписей.Записать(Истина), система работает следующим образом:

  1. Сначала платформа анализирует Отбор, установленный для набора.
  2. Удаляет из регистра все записи, которые подходят под этот отбор.
  3. Записывает новые строки из коллекции набора в базу.

Если же мы вызываем Записать(Ложь), система пытается просто «доложить» новые строки поверх существующих. Именно здесь чаще всего возникает ошибка «Запись уже существует». Если ваша бизнес-логика предполагает полную замену данных по конкретному сотруднику или объекту, всегда используйте установку отбора и запись с замещением.

Способ 4. Предварительная проверка существования запросом

Когда данных очень много, выполнять Прочитать() для каждой записи в цикле — плохая практика с точки зрения производительности. Проанализируем более эффективный подход — использование запроса.

Алгоритм действий следующий:

  1. Выгружаем измерения всех записей, которые планируем добавить, в массив или временную таблицу.
  2. Выполняем запрос к регистру сведений с условием по этим измерениям.
  3. Получаем список уже существующих в базе ключей.
  4. При обходе основного цикла добавляем в набор записей только те строки, которых нет в результате запроса.

Это значительно ускоряет работу системы, так как обращение к базе данных происходит один раз вместо сотен или тысяч.

Способ 5. Особенности периодических регистров

В периодических регистрах ошибка может возникнуть, если вы пытаетесь записать два разных состояния объекта на один и тот же момент времени. Помните, что минимальная дискретность времени в 1С — одна секунда.

Если вам необходимо записывать данные очень часто, рассмотрите возможность использования режима Независимый для регистра или добавление дополнительного измерения (например, НомерСтроки или УникальныйИдентификатор), чтобы разделить записи, сделанные в одну и ту же секунду.

Безопасность в многопользовательской среде

Проанализируем ситуацию «состояния гонки» (race condition). Представьте, что ваш код проверил отсутствие записи, и в этот же микроскопический момент другой пользователь успел её записать. Когда ваш код дойдет до команды Записать(), он получит ошибку.

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

Резюмируем: для предотвращения дублей всегда четко определяйте область ответственности вашего кода. Если вы обновляете существующие данные — используйте Отбор и параметр Замещать = Истина. Если вы только добавляете новые — предварительно проверяйте их наличие запросом или используйте МенеджерЗаписи с методом Прочитать().

← На главную