При работе с большими объемами данных в 1С программисты часто сталкиваются с задачей обновления регистра сведений на основании данных из таблицы значений. Стандартный подход с перебором строк и вызовом метода Прочитать() для каждой записи крайне неэффективен, так как порождает огромное количество запросов к базе данных. Разберем, как оптимизировать этот процесс, используя классические методы и новые возможности платформы 1С:Предприятие 8.3.25 и выше.
Рассмотрим типичную ситуацию: у нас есть независимый непериодический регистр сведений с измерениями и ресурсами. Нам нужно обновить ресурс В только в том случае, если изменился ресурс Р. Если мы будем использовать СоздатьМенеджерЗаписи внутри цикла и вызывать Прочитать(), система будет каждый раз обращаться к СУБД. Это создает колоссальную нагрузку. Наша задача — минимизировать количество серверных вызовов и максимально использовать мощь SQL-запросов.
Проанализируем ситуацию: чтобы не читать каждую запись по отдельности, мы можем передать всю таблицу значений в запрос, поместить ее во временную таблицу и одним махом получить из базы данных только те записи, которые реально требуют обновления. Рассмотрим пример кода, который позволяет подготовить данные для записи:
// Предположим, ТЗ_Данные содержит колонки Измерение1, Ресурс1
// Важно: колонки таблицы должны быть типизированы!
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.УстановитьПараметр("ТЗ", ТЗ_Данные);
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Измерение1 КАК Измерение1,
| ТЗ.Ресурс1 КАК Ресурс1
|ПОМЕСТИТЬ ВТ_НовыеДанные
|ИЗ
| &ТЗ КАК ТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_НовыеДанные.Измерение1,
| ВТ_НовыеДанные.Ресурс1,
| Регистр.Ресурс1 КАК СтарыйРесурс1
|ИЗ
| ВТ_НовыеДанные КАК ВТ_НовыеДанные
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ИмяРегистра КАК Регистр
| ПО ВТ_НовыеДанные.Измерение1 = Регистр.Измерение1
|ГДЕ
| Регистр.Измерение1 ЕСТЬ NULL
| ИЛИ ВТ_НовыеДанные.Ресурс1 <> Регистр.Ресурс1";
Результат = Запрос.Выполнить().Выбрать();
Пока Результат.Следующий() Цикл
МенеджерЗаписи = РегистрыСведений.ИмяРегистра.СоздатьМенеджерЗаписи();
ЗаполнитьЗначенияСвойств(МенеджерЗаписи, Результат);
МенеджерЗаписи.ДатаИзменения = ТекущаяДата();
МенеджерЗаписи.Записать();
КонецЦикла;
В этом подходе мы исключаем лишние вызовы Прочитать(). Мы сразу получаем только те строки, которых либо нет в регистре (новые), либо в которых данные отличаются от текущих. Это значительно быстрее, но все же запись происходит по одной строке.
Начиная с версии платформы 8.3.25, в 1С появились революционные изменения для работы с регистрами. Теперь метод Записать() объекта НаборЗаписей поддерживает системное перечисление РежимЗамещения. Это максимально приближает работу 1С к SQL-операциям MERGE или INSERT ... ON DUPLICATE KEY UPDATE.
Рассмотрим два ключевых режима:
Посмотрим на пример использования режима Слияние. Этот способ позволяет избежать цикла с менеджером записи и выполнить операцию массово:
НаборЗаписей = РегистрыСведений.ИмяРегистра.СоздатьНаборЗаписей();
// Загружаем данные из подготовленной таблицы значений
НаборЗаписей.Загрузить(ТЗ_Обновления);
// Выполняем запись в режиме слияния
// Параметр Замещение теперь может принимать РежимЗамещения.Слияние
НаборЗаписей.Записать(РежимЗамещения.Слияние);
Важный нюанс: использование набора записей требует осторожности с памятью. Если в таблице значений сотни тысяч строк, лучше записывать их порциями (пакетами) по 10-50 тысяч строк, чтобы не вызвать переполнение памяти на сервере и не блокировать таблицу на слишком долгое время.
Часто программисты жалуются, что при попытке написать ВЫБРАТЬ * ПОМЕСТИТЬ ВТ ИЗ &ТЗ возникает ошибка «Тип не может быть выбран в запросе». Выясним причину: это происходит из-за отсутствия явной типизации колонок в таблице значений. Чтобы запрос понимал структуру данных без перечисления всех полей, колонки должны быть созданы с указанием ОписаниеТипов.
Рассмотрим правильный способ подготовки таблицы значений для запроса:
ТЗ = Новый ТаблицаЗначений;
КвалификаторыСтроки = Новый КвалификаторыСтроки(100);
ТипСтрока = Новый ОписаниеТипов("Строка", , , КвалификаторыСтроки);
ТипЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
ТЗ.Колонки.Добавить("Ключ", ТипСтрока);
ТЗ.Колонки.Добавить("Значение", ТипЧисло);
// Теперь в запросе можно использовать сокращенный синтаксис
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Данные ИЗ &ТЗ КАК ТЗ";
Если вы получаете таблицу из управляемой формы (типа ДанныеФормыКоллекция), обязательно преобразуйте ее в «настоящую» таблицу значений методом Выгрузить(), иначе запрос не сможет с ней работать.
Если перед вами стоит задача не только обновить существующие, но и «занулить» или удалить те записи в регистре, которых нет в вашей таблице значений, используйте ЛЕВОЕ СОЕДИНЕНИЕ со стороны регистра:
РегистрСведений.ИмяРегистра.ВТ_ТЗ по измерениям.ГДЕ указываем ВТ_ТЗ.Измерение ЕСТЬ NULL.Таким образом, мы получим список «лишних» записей, которые можно массово удалить или модифицировать. Для удаления в версии 8.3.25 также можно использовать РежимЗамещения.Удаление, передав в набор записей только ключи (измерения), которые нужно стереть из базы.
Проанализировав доступный инструментарий, можно сделать следующие выводы:
Для старых версий платформы (ниже 8.3.25) оптимальным остается предварительный отбор данных через запрос к временной таблице ТЗ с последующим циклом по МенеджеруЗаписи (без вызова Прочитать, так как все нужные данные мы уже получили в запросе).
Для современных систем (8.3.25+) лучшим выбором будет использование НабораЗаписей с режимом Слияние. Это позволяет переложить логику сопоставления данных на СУБД, что в разы ускоряет выполнение задачи и упрощает код. Всегда помните о типизации колонок ТаблицыЗначений — это залог стабильной работы запросов и производительности системы.