Как разбить таблицу значений на порции и обработать ее пакетами в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При выполнении групповых операций в 1С (например, создании документов корректировки регистров или проведении тяжелых процедур) часто возникает необходимость ограничить объем данных, обрабатываемых за один раз. Если попытаться записать в один документ или набор записей слишком большой массив данных (например, более 100 000 строк), система может заблокировать таблицы СУБД или выдать ошибку превышения памяти.

Рассмотрим, как разделить имеющуюся коллекцию данных ТаблицаЗначений на независимые порции заданного размера (например, по 200 или 500 строк) и обработать каждую из них в отдельном документе.

Ограничения языка запросов 1С при работе с портами данных

Начинающие разработчики часто пытаются решить эту задачу единым запросом к таблице значений. Однако здесь мы сталкиваемся с архитектурными ограничениями платформы 1С:

  1. Отсутствие конструкций смещения (OFFSET/SKIP): В языке запросов 1С есть предложение ВЫБРАТЬ ПЕРВЫЕ N, но отсутствуют ключевые слова для пропуска первых N записей.
  2. Отсутствие поля НомерСтроки у параметра-таблицы: Если передать объект ТаблицаЗначений в запрос как параметр, служебное поле НомерСтроки в нем отсутствует. Попытка обратиться к нему напрямую без помещения во временную таблицу вызовет синтаксическую ошибку.

Поэтому выборку данных по порциям эффективнее всего выполнять алгоритмически в коде либо сочетать запросы с механизмами нумерации строк во временных таблицах.

Решение 1: Выгрузка порций через массив и метод Скопировать() (Рекомендуемый способ)

Проанализируем самый чистый и производительный подход. Чтобы не перебирать таблицы вручную по индексам и не тратить ресурсы на точечное добавление колонок, воспользуемся возможностью метода Скопировать() объекта ТаблицаЗначений, который умеет принимать массив конкретных строк СтрокаТаблицыЗначений.

Разберем алгоритм по шагам:

  1. Обходим исходную таблицу значений в цикле Для каждого.
  2. Добавляем текущую строку в промежуточный Массив.
  3. Как только количество элементов в массиве кратно размеру порции (используем оператор остатка от деления %), создаем копию порции методом Скопировать(Массив), передаем ее в процедуру записи и очищаем массив.
  4. После завершения цикла проверяем, остались ли необработанные строки в массиве, и записываем последний пакет.

Посмотрим на пример реализации данного кода:


МассивСтрок = Новый Массив;
РазмерПорции = 200; // Лимит строк на один документ

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

// Обрабатываем остаток строк, если их количество не кратно размеру порции
Если МассивСтрок.Количество() > 0 Тогда
    ПорцияТЗ = Данные.Скопировать(МассивСтрок);
    СоздатьДокументКорректировки("СтоимостьОбъектовОС", ПорцияТЗ);    
КонецЕсли;

Решение 2: Обход с помощью счетчика строк

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

Рассмотрим пример обхода с созданием документов прямо в цикле:


Док = Документы.КорректировкаРегистров.СоздатьДокумент();
Счетчик = 0;
РазмерПорции = 200;

Для Каждого Стр Из Данные Цикл
    // Заполняем реквизиты или движения документа из Стр
    // ...
    
    Счетчик = Счетчик + 1;
    
    Если Счетчик = РазмерПорции Тогда 
        Док.Записать(РежимЗаписиДокумента.Проведение);
        Счетчик = 0;
        // Создаем следующий документ для новой порции
        Док = Документы.КорректировкаРегистров.СоздатьДокумент();
    КонецЕсли;
КонецЦикла;

// Записываем последний документ, если в нем остались строки
Если Счетчик > 0 Тогда
    Док.Записать(РежимЗаписиДокумента.Проведение);
КонецЕсли;

Решение 3: Разбиение запросом через временную таблицу и АВТОНОМЕРЗАПИСИ()

Выясним, как реализовать порционную выборку, если данные требуется обязательно обработать запросом на стороне СУБД (например, при фильтрации больших объемов). Для этого поместим исходную таблицу значений во временную таблицу и пронумеруем строки с помощью функции АВТОНОМЕРЗАПИСИ().

Рассмотрим пример кода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТЗ.Номенклатура КАК Номенклатура,
    |   ТЗ.Количество КАК Количество,
    |   АВТОНОМЕРЗАПИСИ() КАК ИндексСтроки
    |ПОМЕСТИТЬ ВТ_Данные
    |ИЗ
    |   &ИсходнаяТЗ КАК ТЗ
    |;
    |
    |ВЫБРАТЬ
    |   ВТ.Номенклатура,
    |   ВТ.Количество
    |ИЗ
    |   ВТ_Данные КАК ВТ
    |ГДЕ
    |   ВТ.ИндексСтроки МЕЖДУ &НачалоИндекс И &КонецИндекс";

Запрос.Параметры.Вставить("ИсходнаяТЗ", Данные);

РазмерПорции = 200;
ВсегоСтрок = Данные.Количество();
ТекущийИндекс = 1;

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

Рекомендации по оптимизации

← На главную