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