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