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