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