Как разбить большой массив на порции по 100 элементов в 1С:Предприятие?

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

При разработке прикладных решений на платформе 1С разработчики регулярно сталкиваются с необходимостью разделить большой массив данных (например, список ссылок на документы или номенклатуру) на порции фиксированного размера. Это требуется при передаче параметров в запросы с условием ГДЕ Ссылка В (&Массив), пакетной отправке данных через HTTP-сервисы, распределении нагрузки по фоновым заданиям или для оптимизации потребления оперативной памяти процессом rphost.

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

Типичные ошибки при разделении массивов

Проанализируем частые заблуждения, с которыми можно столкнуться при решении данной задачи:

  1. Неправильное понимание конструктора Новый Массив(<Размер>): если разработчик инициализирует массив выражением Массив1 = Новый Массив(100);, создается массив, в котором уже содержатся 100 элементов со значением Неопределено. Последующий вызов Массив1.Добавить(...) добавит новый элемент 101-м по счету, а не заполнит существующие ячейки. Для создания динамического массива конструктор вызывается без параметров: Новый Массив.
  2. Динамическая генерация переменных через Выполнить: попытки создать отдельные переменные вида Массив1, Массив2, Массив3 через строковое выполнение команды не соответствуют стандартам разработки 1С, вызывают критические ошибки в безопасном режиме (SafeMode) и делают невозможной дальнейшую удобную работу с полученными коллекциями. Результатом разбиения должна быть единая структура — массив подмассивов (пачек).
  3. Потеря «хвоста» выборки: при обходе по счетчикам или остатку от деления легко допустить ошибку, из-за которой последняя неполная порция данных (например, оставшиеся 50 элементов из 450) не будет добавлена в итоговый массив.

Вариант 1. Линейный обход с контролем размера текущей пачки

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

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


МассивПачек = Новый Массив;
ТекущаяПачка = Новый Массив;
РазмерПорции = 100;

Для Каждого Элемент Из ИсходныйМассив Цикл
    ТекущаяПачка.Добавить(Элемент);
    
    Если ТекущаяПачка.Количество() = РазмерПорции Тогда
        МассивПачек.Добавить(ТекущаяПачка);
        ТекущаяПачка = Новый Массив;
    КонецЕсли;
КонецЦикла;

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

Разберем по шагам, почему этот алгоритм эффективен:

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

Вариант 2. Индексный обход порциями (Пока ... Цикл)

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

Рассмотрим реализацию данного подхода:


МассивПачек = Новый Массив;
ВсегоЭлементов = ИсходныйМассив.Количество();
РазмерПорции = 100;
Индекс = 0;

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

Проанализируем особенности этого варианта:

  1. Внешний цикл Пока шагает с шагом, равным размеру порции (100).
  2. Конструкция Мин(Индекс + РазмерПорции, ВсегоЭлементов) автоматически защищает алгоритм от выхода за границы массива при обработке завершающего неполного блока данных.
  3. Внутренний цикл быстро переносит нужный срез элементов в текущую порцию.

Работа с полученными порциями данных

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


Для Каждого Порция Из МассивПачек Цикл
    // Порция представляет собой массив из не более чем 100 элементов
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Номенклатура.Ссылка КАК Ссылка
        |ИЗ
        |   Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |   Номенклатура.Ссылка В (&МассивСсылок)";
    
    Запрос.УстановитьПараметр("МассивСсылок", Порция);
    Выборка = Запрос.Выполнить().Выбрать();
    
    Пока Выборка.Следующий() Цикл
        // Обработка данных порции
    КонецЦикла;
КонецЦикла;

Важное архитектурное замечание: если разбиение массива производится исключительно для передачи в условие запроса В (&Массив), в типовых конфигурациях и высоконагруженных системах рекомендуется альтернативный паттерн: загрузить массив в ТаблицаЗначений, передать её в запрос через менеджер временных таблиц и использовать оператор ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Это полностью снимает ограничения СУБД на число параметров в выражении IN и исключает необходимость делить данные вручную.

← На главную