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