Как передать массив строк в функцию с группировкой по полю в 1С

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

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

Метод 1: Использование вспомогательной свернутой таблицы (через НайтиСтроки)

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

Для начала проанализируем ситуацию: у нас есть исходная таблица Объект.ТабличнаяЧасть. Нам нужно передать в функцию СформироватьНаСервере массив строк, сгруппированных по полю Регион.


&НаСервере
Процедура ГрупповаяОбработкаПоРегионам()
    
    // 1. Выгружаем данные из табличной части в таблицу значений для удобства обработки
    ТаблЗнач = Объект.ТабличнаяЧасть.Выгрузить(); 
    
    // 2. Создаем временную таблицу для получения уникальных регионов
    ТаблицаРегионов = ТаблЗнач.Скопировать();
    ТаблицаРегионов.Свернуть("Регион"); // Теперь здесь только уникальные названия регионов
    
    // 3. Обходим уникальные регионы в цикле
    Для каждого СтрокаРегион ИЗ ТаблицаРегионов Цикл
        
        // 4. Формируем структуру отбора для поиска строк
        Отбор = Новый Структура("Регион", СтрокаРегион.Регион);
        
        // 5. Находим все строки в исходной ТЗ, соответствующие текущему региону
        // Метод НайтиСтроки возвращает Массив объектов СтрокаТаблицыЗначений
        МассивСтрокРегиона = ТаблЗнач.НайтиСтроки(Отбор);
        
        // 6. Передаем полученный массив в целевую функцию
        ОбработатьНаборДанных(МассивСтрокРегиона, Объект.ДатаОтчета);
        
    КонецЦикла;
    
КонецПроцедуры

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

Метод 2: Оптимизация производительности с помощью индексации

Если в вашей таблице не десять строк, а несколько тысяч, метод НайтиСтроки может начать работать медленно, так как при каждой итерации цикла системе приходится просматривать всю таблицу заново. Чтобы ускорить процесс, мы можем использовать индексацию. Рассмотрим, как изменить наш код.

Добавление индекса по колонке Регион позволяет платформе использовать алгоритмы быстрого поиска (хеш-таблицы или бинарные деревья), что значительно сокращает время выполнения операции НайтиСтроки.


// Добавляем индекс перед началом цикла
ТаблЗнач.Индексы.Добавить("Регион");

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

Метод 3: Группировка "в один проход" (без НайтиСтроки)

Рассмотрим более продвинутый способ, который не требует двойного обхода таблицы (сначала Свернуть, потом НайтиСтроки). Мы можем использовать объект Соответствие, где ключом будет название региона, а значением — массив подразделений. Этот метод считается наиболее эффективным с точки зрения алгоритмической сложности.

Проанализируем ситуацию: мы один раз перебираем таблицу и сразу распределяем данные по "корзинам" (регионам).


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

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

Метод 4: Использование механизмов запроса (профессиональный подход)

В среде 1С наиболее правильным способом группировки данных считается использование языка запросов. Даже если данные находятся в таблице значений в памяти, мы можем передать их в запрос как параметр. Выясним причину, почему это выгодно: запрос позволяет использовать конструкцию ИТОГИ ПО, которая автоматически строит иерархическую структуру.

Разберем реализацию через временную таблицу:


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

Важные рекомендации по реализации:

  1. Избегайте рекурсии: В коде на форуме была допущена ошибка — процедура называлась так же, как и вызываемая внутри неё функция. Всегда давайте уникальные и понятные имена процедурам, например ВыполнитьОбработкуДанных() и СформироватьПечатнуюФорму().
  2. Контроль контекста: Если вы работаете в Управляемом приложении, помните, что ТаблицаЗначений не существует на клиенте. Все манипуляции с ней должны происходить в процедурах с директивой &НаСервере.
  3. Очистка памяти: Если таблица данных очень большая, после выгрузки её в массив или соответствие, старайтесь обнулять исходные переменные, если они больше не нужны, чтобы оптимизировать потребление оперативной памяти сервером.
  4. Типизация: При использовании НайтиСтроки убедитесь, что типы данных в ключе структуры отбора и в колонке таблицы совпадают (например, оба являются справочниками или оба строками), иначе поиск вернет пустой результат.

Таким образом, мы рассмотрели несколько способов решения задачи: от классического использования НайтиСтроки с индексацией до высокопроизводительных методов с использованием Соответствия и Запросов. Выбор конкретного метода зависит от объема ваших данных и требований к чистоте кода.

← На главную