В процессе разработки на платформе 1С:Предприятие 8 часто возникает задача пакетной обработки данных. Представим ситуацию: у вас есть плоская таблица значений (например, список подразделений и соответствующих им регионов), и вам необходимо вызвать одну и ту же функцию для каждого региона, передав в неё массив всех подразделений, относящихся к этому региону. Рассмотрим, как реализовать этот алгоритм эффективно и правильно, проанализировав различные подходы — от простых циклов до использования встроенных механизмов поиска и индексации.
Это наиболее наглядный и понятный способ, который был предложен в ходе обсуждения. Суть его заключается в том, чтобы сначала получить список уникальных значений группировочного поля, а затем по этому списку отобрать строки из исходной таблицы. Разберем этот алгоритм по шагам.
Для начала проанализируем ситуацию: у нас есть исходная таблица Объект.ТабличнаяЧасть. Нам нужно передать в функцию СформироватьНаСервере массив строк, сгруппированных по полю Регион.
&НаСервере
Процедура ГрупповаяОбработкаПоРегионам()
// 1. Выгружаем данные из табличной части в таблицу значений для удобства обработки
ТаблЗнач = Объект.ТабличнаяЧасть.Выгрузить();
// 2. Создаем временную таблицу для получения уникальных регионов
ТаблицаРегионов = ТаблЗнач.Скопировать();
ТаблицаРегионов.Свернуть("Регион"); // Теперь здесь только уникальные названия регионов
// 3. Обходим уникальные регионы в цикле
Для каждого СтрокаРегион ИЗ ТаблицаРегионов Цикл
// 4. Формируем структуру отбора для поиска строк
Отбор = Новый Структура("Регион", СтрокаРегион.Регион);
// 5. Находим все строки в исходной ТЗ, соответствующие текущему региону
// Метод НайтиСтроки возвращает Массив объектов СтрокаТаблицыЗначений
МассивСтрокРегиона = ТаблЗнач.НайтиСтроки(Отбор);
// 6. Передаем полученный массив в целевую функцию
ОбработатьНаборДанных(МассивСтрокРегиона, Объект.ДатаОтчета);
КонецЦикла;
КонецПроцедуры
Обратите внимание на важный нюанс: метод НайтиСтроки возвращает коллекцию объектов типа СтрокаТаблицыЗначений. Если ваша функция обработки ожидает массив простых ссылок или строк, вам придется дополнительно извлечь значения из этого массива. Однако для большинства задач формирования печатных форм или документов этого достаточно, так как через строку таблицы можно обратиться к любому её реквизиту.
Если в вашей таблице не десять строк, а несколько тысяч, метод НайтиСтроки может начать работать медленно, так как при каждой итерации цикла системе приходится просматривать всю таблицу заново. Чтобы ускорить процесс, мы можем использовать индексацию. Рассмотрим, как изменить наш код.
Добавление индекса по колонке Регион позволяет платформе использовать алгоритмы быстрого поиска (хеш-таблицы или бинарные деревья), что значительно сокращает время выполнения операции НайтиСтроки.
// Добавляем индекс перед началом цикла
ТаблЗнач.Индексы.Добавить("Регион");
Для каждого СтрокаРегион ИЗ ТаблицаРегионов Цикл
Отбор = Новый Структура("Регион", СтрокаРегион.Регион);
// Теперь этот метод отработает в разы быстрее на больших данных
МассивСтрокРегиона = ТаблЗнач.НайтиСтроки(Отбор);
ОбработатьНаборДанных(МассивСтрокРегиона, Объект.ДатаОтчета);
КонецЦикла;
Рассмотрим более продвинутый способ, который не требует двойного обхода таблицы (сначала Свернуть, потом НайтиСтроки). Мы можем использовать объект Соответствие, где ключом будет название региона, а значением — массив подразделений. Этот метод считается наиболее эффективным с точки зрения алгоритмической сложности.
Проанализируем ситуацию: мы один раз перебираем таблицу и сразу распределяем данные по "корзинам" (регионам).
&НаСервере
Процедура РаспределитьПоСоответствию()
// Создаем соответствие для хранения групп
ГруппировкаПоРегионам = Новый Соответствие;
Для Каждого СтрокаТЧ Из Объект.ТабличнаяЧасть Цикл
ТекРегион = СтрокаТЧ.Регион;
// Проверяем, есть ли уже массив для этого региона в соответствии
МассивПодразделений = ГруппировкаПоРегионам.Получить(ТекРегион);
Если МассивПодразделений = Неопределено Тогда
// Если региона еще нет, создаем новый массив
МассивПодразделений = Новый Массив;
ГруппировкаПоРегионам.Вставить(ТекРегион, МассивПодразделений);
КонецЕсли;
// Добавляем текущее подразделение (или всю строку) в массив
МассивПодразделений.Добавить(СтрокаТЧ.Подразделение);
КонецЦикла;
// Теперь обходим само соответствие и вызываем нашу функцию
Для Каждого КлючИЗначение Из ГруппировкаПоРегионам Цикл
// КлючИЗначение.Ключ - это Регион
// КлючИЗначение.Значение - это Массив подразделений
СформироватьНаСервере(КлючИЗначение.Значение, Объект.Дата);
КонецЦикла;
КонецПроцедуры
Этот метод хорош тем, что он работает за линейное время и не зависит от сортировки исходной таблицы. Кроме того, мы сразу формируем массив именно тех значений, которые нам нужны (например, только ссылок на подразделения), не передавая лишние объекты строк таблицы значений.
В среде 1С наиболее правильным способом группировки данных считается использование языка запросов. Даже если данные находятся в таблице значений в памяти, мы можем передать их в запрос как параметр. Выясним причину, почему это выгодно: запрос позволяет использовать конструкцию ИТОГИ ПО, которая автоматически строит иерархическую структуру.
Разберем реализацию через временную таблицу:
&НаСервере
Процедура СгруппироватьЗапросом()
ТЗ = Объект.ТабличнаяЧасть.Выгрузить();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВТ_Данные.Подразделение КАК Подразделение,
| ВТ_Данные.Регион КАК Регион
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
| &ТЗ КАК ВТ_Данные
|;
|
|ВЫБРАТЬ
| ВТ_Данные.Подразделение КАК Подразделение,
| ВТ_Данные.Регион КАК Регион
|ИЗ
| ВТ_Данные КАК ВТ_Данные
|ИТОГИ ПО
| Регион";
Запрос.УстановитьПараметр("ТЗ", ТЗ);
РезультатЗапроса = Запрос.Выполнить();
// Выборка по верхнему уровню (Регионы)
ВыборкаРегион = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаРегион.Следующий() Цикл
мПодразделения = Новый Массив;
// Выборка по детальным записям внутри региона
ВыборкаДетали = ВыборкаРегион.Выбрать();
Пока ВыборкаДетали.Следующий() Цикл
мПодразделения.Добавить(ВыборкаДетали.Подразделение);
КонецЦикла;
// Передаем массив в функцию
СформироватьНаСервере(мПодразделения, Объект.Дата);
КонецЦикла;
КонецПроцедуры
Важные рекомендации по реализации:
ВыполнитьОбработкуДанных() и СформироватьПечатнуюФорму().ТаблицаЗначений не существует на клиенте. Все манипуляции с ней должны происходить в процедурах с директивой &НаСервере.НайтиСтроки убедитесь, что типы данных в ключе структуры отбора и в колонке таблицы совпадают (например, оба являются справочниками или оба строками), иначе поиск вернет пустой результат.Таким образом, мы рассмотрели несколько способов решения задачи: от классического использования НайтиСтроки с индексацией до высокопроизводительных методов с использованием Соответствия и Запросов. Выбор конкретного метода зависит от объема ваших данных и требований к чистоте кода.