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

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

При работе с независимыми регистрами сведений в 1С:Предприятие программисты часто сталкиваются с необходимостью массового обновления данных. Типичная задача — загрузить информацию из внешней таблицы значений или другого источника в регистр СоставыСемейФизическихЛиц. Однако при попытке программной записи может возникнуть ошибка: «Запись не верна! Значение поля не соответствует установленному отбору». В этой статье мы подробно разберем, почему возникает эта проблема, как её избежать и какими методами лучше всего перезаписывать записи регистра.

Анализируем причину ошибки «Значение поля не соответствует установленному отбору»

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

Проанализируем фрагмент проблемного кода:


НаборЗаписей = РегистрыСведений.СоставыСемейФизическихЛиц.СоздатьНаборЗаписей();
// ... в цикле ...
новаязапись = НаборЗаписей.Добавить();
новаязапись.ФизическоеЛицо = текФизЛицо; // Ошибка здесь, если ранее был установлен отбор

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

Решение №1: Использование метода Загрузить()

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

Рассмотрим правильный алгоритм действий:

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

Пример кода:


НаборЗаписей = РегистрыСведений.СоставыСемейФизическихЛиц.СоздатьНаборЗаписей();
// Важно: если отбор не установлен, Записать(Истина) удалит ВСЕ записи регистра
НаборЗаписей.Загрузить(ТаблицаСДанными);
Попытка
    НаборЗаписей.Записать(Истина);
Исключение
    Сообщить("Ошибка при записи регистра: " + ОписаниеОшибки());
КонецПопытки;

Решение №2: Пообъектная перезапись в цикле (более безопасный метод)

Если данных очень много, запись всего регистра одним набором может привести к длительным блокировкам таблицы и нехватке оперативной памяти. В таких случаях мы рекомендуем использовать «пакетную» запись по каждому ключевому объекту (например, по каждому физическому лицу).

Проанализируем ситуацию по шагам:

  1. Сгруппируем данные в исходной таблице значений по ФизическоеЛицо.
  2. Для каждого уникального физического лица создаем отдельный набор записей.
  3. Устанавливаем отбор по конкретному ФизическоеЛицо.
  4. Наполняем набор только теми строками из ТЗ, которые относятся к этому лицу.
  5. Записываем набор. При этом из базы удалятся старые составы семей только для этого человека, а данные по другим людям останутся нетронутыми.

Пример реализации:


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

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

// Не забываем записать последний набор
Если Не Набор.Количество() = 0 Тогда
    Набор.Записать(Истина);
КонецЕсли;

Важные рекомендации при массовой записи данных

Рассмотрим несколько нюансов, которые помогут сделать ваш код более профессиональным и надежным:

1. Контроль уникальности. Помните, что в независимом регистре сведений комбинация измерений (в нашем случае ФизическоеЛицо + НомерПоПорядку) должна быть уникальной. Если в вашей таблице значений окажутся две строки с одинаковым физлицом и одинаковым номером, метод Записать() вызовет исключение. Перед загрузкой обязательно проверьте таблицу на наличие дублей.

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

3. Поиск ссылок. В исходном коде использовался метод НайтиПоКоду() внутри цикла. Это крайне неоптимально, так как порождает отдельный запрос к базе данных на каждой итерации. Посмотрим, как это ускорить: лучше заранее получить все соответствия кодов и ссылок одним запросом во временную таблицу, а затем соединить её с вашими данными. Это ускорит выполнение обработки в десятки раз.

4. Активность записей. Не забывайте устанавливать свойство Активность = Истина, если это требуется логикой вашего учета, иначе записи будут созданы, но не будут учитываться в отчетах и запросах, использующих виртуальные таблицы.

Таким образом, для исправления ошибки достаточно либо полностью отказаться от отборов при перезаписи всего регистра через Загрузить(), либо тщательно следить за тем, чтобы данные в наборе соответствовали установленному фильтру НаборЗаписей.Отбор.

← На главную