Как массово обновить данные в табличной части документов 1С?

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

При развитии информационной системы нередко возникает ситуация, когда в существующую табличную часть (ТЧ) документа добавляются новые реквизиты. Чтобы эти поля не оставались пустыми в уже существующих документах за прошлые периоды, программисту необходимо написать обработку для их заполнения. В этой статье мы подробно рассмотрим различные подходы к решению этой задачи, проанализируем типичные ошибки и выясним, как сделать процесс обновления максимально быстрым и безопасным для базы данных.

Постановка задачи и анализ первичного подхода

Представим ситуацию: в документ ижЗаказПокупателя в табличную часть Товары были добавлены два новых поля: ижВерхнийОпцион и ижНижнийОпцион. Нам необходимо пройтись по всем документам, где эти поля еще не заполнены, и установить в них значения, зависящие от реквизита контрагента. Разберем ситуацию на примере кода.

Многие начинающие разработчики пытаются использовать метод ЗагрузитьКолонку. Рассмотрим, почему это может не сработать. Метод ЗагрузитьКолонку принимает массив данных, и его размер должен строго совпадать с количеством строк в табличной части. Если в документе 10 строк, а вы передаете массив из 5 элементов, возникнет ошибка или данные будут загружены некорректно. Кроме того, этот метод требует предварительного формирования массивов в правильном порядке, что усложняет логику.

Решение 1: Простой перебор строк объекта

Самый понятный и надежный способ — это получение объекта документа и цикл по его табличной части. Проанализируем этот вариант. Он хорош тем, что мы работаем напрямую с данными объекта, и нам не нужно заботиться о соответствии индексов в разных структурах данных.


// Выбираем документы, которые требуют обновления
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |   Товары.Ссылка КАК Ссылка
    |ИЗ
    |   Документ.ижЗаказПокупателя.Товары КАК Товары
    |ГДЕ
    |   Товары.ижВерхнийОпцион = 0 И Товары.ижНижнийОпцион = 0";

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    // Получаем объект для изменения
    ДокументОбъект = Выборка.Ссылка.ПолучитьОбъект();
    
    // Определяем значение опциона на основании данных шапки
    Опцион = ?(ДокументОбъект.Контрагент.КодПоОКПО = "77304466", 5, 10);
    
    // Перебираем строки табличной части
    Для Каждого СтрокаТЧ Из ДокументОбъект.Товары Цикл
        СтрокаТЧ.ижВерхнийОпцион = Опцион;
        СтрокаТЧ.ижНижнийОпцион = -Опцион;
    КонецЦикла;
    
    // Записываем изменения
    ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЦикла;

В данном примере мы используем РежимЗаписиДокумента.Запись. Это критически важный момент: если мы не хотим, чтобы документ заново формировал движения по регистрам (что может «уплыть» в закрытых периодах), следует использовать именно этот режим, а не Проведение.

Решение 2: Использование запроса с итогами и метода ЗаполнитьЗначениеСвойств

Если логика заполнения сложнее и зависит от данных конкретной строки, эффективнее использовать запрос, который сразу подготовит все нужные значения. Рассмотрим подробнее, как применить обход по группировкам (итогам), чтобы минимизировать количество обращений к базе данных.


Запрос = Новый Запрос("
|ВЫБРАТЬ
|   Товары.Ссылка КАК Ссылка,
|   Товары.НомерСтроки КАК НомерСтроки,
|   ВЫБОР
|       КОГДА Заказ.Контрагент.КодПоОКПО = ""77304466"" ТОГДА 5 ИНАЧЕ 10
|   КОНЕЦ КАК ижВерхнийОпцион,
|   ВЫБОР
|       КОГДА Заказ.Контрагент.КодПоОКПО = ""77304466"" ТОГДА -5 ИНАЧЕ -10
|   КОНЕЦ КАК ижНижнийОпцион
|ИЗ
|   Документ.ижЗаказПокупателя.Товары КАК Товары
|   ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ижЗаказПокупателя КАК Заказ ПО Товары.Ссылка = Заказ.Ссылка
|ГДЕ
|   Товары.ижВерхнийОпцион = 0
|ИТОГИ ПО Ссылка");

ВыборкаДокументы = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаДокументы.Следующий() Цикл
    ДокОбъект = ВыборкаДокументы.Ссылка.ПолучитьОбъект();
    Товары = ДокОбъект.Товары;
    
    ВыборкаСтроки = ВыборкаДокументы.Выбрать();
    Пока ВыборкаСтроки.Следующий() Цикл
        // Важно: индекс строки в коллекции начинается с 0, а НомерСтроки с 1
        ТекущаяСтрока = Товары[ВыборкаСтроки.НомерСтроки - 1];
        ЗаполнитьЗначениеСвойств(ТекущаяСтрока, ВыборкаСтроки, "ижВерхнийОпцион, ижНижнийОпцион");
    КонецЦикла;
    
    // Оптимизация записи
    ДокОбъект.ОбменДанными.Загрузка = Истина;
    ДокОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЦикла;

Обратите внимание на конструкцию Товары[ВыборкаСтроки.НомерСтроки - 1]. Проанализируем ситуацию: в 1С свойство НомерСтроки табличной части всегда начинается с единицы. Однако обращение к коллекции строк по индексу требует отсчета от нуля. Если забыть вычесть единицу, программа либо изменит не ту строку, либо выдаст ошибку «Индекс находится вне границ массива».

Оптимизация производительности: важные рекомендации

Когда мы работаем с массовым изменением данных (сотни или тысячи документов), обычного цикла может быть недостаточно. Процесс может затянуться на часы или вызвать блокировки базы данных. Разберем по шагам, как ускорить выполнение задачи:

  1. Использование ОбменДанными.Загрузка = Истина. Перед выполнением метода Записать() установите это свойство объекта. Это позволит пропустить все проверки в модуле документа (процедуры ПередЗаписью, ПриЗаписи), а также отключит срабатывание подписок на события. Это не только ускоряет процесс в разы, но и позволяет записывать старые документы, которые могут не проходить текущие проверки (например, если за это время обязательными стали другие поля).
  2. Управление памятью. После того как ДокументОбъект.Записать() выполнен, объект остается в оперативной памяти до завершения процедуры. При обработке огромных массивов данных это может привести к переполнению памяти. Рекомендуется явно очищать переменную: ДокументОбъект = Неопределено; в конце каждой итерации цикла.
  3. Метод ЗаполнитьЗначения для коллекций. Если вам нужно установить одинаковое значение во всей колонке табличной части, используйте встроенный метод коллекции: ДокументОбъект.Товары.ЗаполнитьЗначения(ЗначениеОпциона, "ижВерхнийОпцион");. Это работает быстрее, чем ручной перебор строк в цикле Для Каждого.

Проверка необходимости записи

Чтобы избежать лишней нагрузки на сервер базы данных и лог транзакций, полезно внедрить проверку на реальное изменение данных. Зачем записывать документ, если новые значения совпадают с теми, что уже там записаны? Посмотрим на пример реализации такой проверки:


НужноЗаписывать = Ложь;
Для Каждого Стр Из ДокументОбъект.Товары Цикл
    Если Стр.ижВерхнийОпцион <> НовоеЗначение Тогда
        Стр.ижВерхнийОпцион = НовоеЗначение;
        НужноЗаписывать = Истина;
    КонецЕсли;
КонецЦикла;

Если НужноЗаписывать Тогда
    ДокументОбъект.Записать();
КонецЕсли;

Этот простой прием позволяет значительно сократить время работы обработки при повторных запусках или в случаях, когда часть данных уже была обновлена другими средствами.

Подводя итог, можно сказать, что наиболее сбалансированным решением является сочетание запроса для отбора документов и прямого перебора строк объекта с обязательным использованием флага Загрузка = Истина. Это гарантирует точность данных и высокую скорость обработки без риска нарушения целостности базы данных.

← На главную