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