Как в 1С создать новый документ через каждые 20 000 строк при переборе таблицы значений?

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

При обработке больших объемов данных в 1С часто возникает задача разделения информации на части (пакеты). Например, если у нас есть таблица значений (ТЗ) на 100 000 строк, создание одного гигантского документа может привести к ошибкам памяти, блокировкам базы данных или просто сделает документ нечитаемым для пользователя. Оптимальным решением является создание нескольких документов, каждый из которых содержит ограниченное количество строк (например, по 20 000).

В этой статье мы подробно разберем, как правильно реализовать такой механизм, проанализируем типичные ошибки начинающих разработчиков и рассмотрим профессиональные методы оптимизации этой задачи.

Вариант 1. Классический обход с проверкой индекса

Рассмотрим самый популярный способ решения задачи — использование оператора % (остаток от деления) для определения момента, когда нужно создать новый документ. Однако здесь важно правильно обработать первую и последнюю итерации цикла.

Разберем по шагам логику, которая учитывает, что индексы в 1С начинаются с нуля:


НовДок = Документы.ПриходнаяНакладная.СоздатьДокумент();
НовДок.Дата = ТекущаяДата();

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

    // Заполняем табличную часть текущего документа
    СтрокаТЧ = НовДок.Материалы.Добавить();
    ЗаполнитьЗначенияСвойств(СтрокаТЧ, СтрокаТЗ);

КонецЦикла;

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

Проанализируем ситуацию: если мы просто напишем Индекс % 20000 = 0, то условие выполнится на самой первой строке (индекс 0). В результате будет создан пустой документ, сразу записан, и тут же создан второй. Чтобы этого избежать, мы добавили проверку ТЗ.Индекс(СтрокаТЗ) > 0.

Вариант 2. Оптимизация с использованием счетчика

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

Выясним причину эффективности этого метода: инкремент числа — это элементарная операция процессора, которая всегда быстрее поиска в коллекции.


КоличествоСтрокВПакете = 20000;
Счетчик = 0;
НовДок = Неопределено;

Для Каждого СтрокаТЗ Из ТЗ Цикл
    
    Если Счетчик % КоличествоСтрокВПакете = 0 Тогда
        // Если это не первая итерация, записываем предыдущий документ
        Если НовДок <> Неопределено Тогда
            НовДок.Записать();
        КонецЕсли;
        
        НовДок = Документы.ПриходнаяНакладная.СоздатьДокумент();
        НовДок.Дата = ТекущаяДата();
        // Здесь можно заполнить общие реквизиты шапки
    КонецЕсли;

    СтрокаТЧ = НовДок.Материалы.Добавить();
    ЗаполнитьЗначенияСвойств(СтрокаТЧ, СтрокаТЗ);
    
    Счетчик = Счетчик + 1;
КонецЦикла;

// Записываем финальную порцию
Если НовДок <> Неопределено И НовДок.Материалы.Количество() > 0 Тогда
    НовДок.Записать();
КонецЕсли;

Вариант 3. Использование методов «Выгрузить» и «Загрузить» (Самый быстрый)

Если нам нужно достичь максимальной производительности, стоит отказаться от построчного добавления строк через Добавить() и ЗаполнитьЗначенияСвойств(). Платформа 1С работает гораздо быстрее, когда оперирует массивами данных в памяти.

Разберем алгоритм пакетной загрузки:

  1. Определяем общее количество строк в исходной ТаблицаЗначений.
  2. В цикле шагаем по индексам с шагом 20 000.
  3. Используем метод Выгрузить() для получения куска таблицы.
  4. Используем метод Загрузить() для мгновенной вставки строк в документ.

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

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

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

Дополнительные рекомендации по производительности

При работе с такими объемами (десятки тысяч строк) важно помнить о следующих вещах:

1. Управление транзакциями. По умолчанию каждый вызов Записать() открывает и закрывает транзакцию в базе данных. Если документов много, это создает нагрузку на диск. Мы можем объединить запись нескольких документов в одну транзакцию, используя НачатьТранзакцию() и ЗафиксироватьТранзакцию(). Однако будьте осторожны: транзакция не должна длиться слишком долго, чтобы не блокировать работу других пользователей.

2. Режим загрузки данных. Если при создании документов вам не нужно выполнять сложные проверки, расчеты или подписки на события (например, вы просто переносите остатки), используйте свойство ОбменДанными.Загрузка = Истина. Рассмотрим пример:


НовДок = Документы.ПриходнаяНакладная.СоздатьДокумент();
НовДок.ОбменДанными.Загрузка = Истина;
// ... заполнение ...
НовДок.Записать();

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

3. Фоновое выполнение. Если обработка таблицы значений занимает более 10–20 секунд, интерфейс пользователя может "зависнуть". В таких случаях рекомендуется выносить процедуру обработки в Фоновое задание. Это позволит пользователю продолжать работу, пока сервер формирует документы в фоновом режиме. Для реализации такого механизма подойдёт готовый пример запуска длительных операций в фоне с отслеживанием прогресса.

4. Ограничение памяти. Помните, что ТаблицаЗначений находится в оперативной памяти сервера (или клиента). Если в ней несколько миллионов строк, вы можете столкнуться с ошибкой нехватки памяти. В таких случаях лучше не выгружать данные в ТЗ целиком, а работать с РезультатЗапроса через Выборка, обрабатывая данные потоково.

Подводя итог, мы проанализировали ситуацию и увидели, что задача имеет несколько решений. Для простых случаев подойдет счетчик внутри цикла, а для высоконагруженных систем лучше использовать методы Выгрузить() / Загрузить() в сочетании с отключением стандартных проверок через ОбменДанными.Загрузка.

← На главную