Как проверить уникальность значений при обходе строк и сгруппировать данные для печати в 1С?

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

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

Способ 1. Использование объекта Соответствие

Объект Соответствие представляет собой ассоциативный массив (хеш-таблицу). Это наиболее эффективная встроенная коллекция для проверки уникальности произвольных ссылочных типов (включая ссылки на справочники и документы), поскольку поиск по ключу выполняется со сложностью O(1).

Рассмотрим, как устроен этот механизм:


Организации = Новый Соответствие;

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

Важный момент: объект Структура для этой задачи не подходит, так как ключами структуры могут выступать исключительно строки, соответствующие правилам образования идентификаторов 1С. Для хранения ссылок в качестве ключей всегда используем Соответствие.

Способ 2. Использование объекта Массив и разбор типичных ошибок

Проверку уникальности можно реализовать и через коллекцию Массив с помощью метода Найти(). Поиск в массиве выполняется последовательным перебором элементов (сложность O(N)), что вполне допустимо на небольших объемах данных (например, в пределах строк одного документа).

Посмотрим на корректный пример реализации:


// Объявляем массив строго ДО начала цикла
МассивОрганизаций = Новый Массив;

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

Проанализируем распространенные ошибки начинающих разработчиков при работе с массивом в цикле:

  1. Пересоздание коллекции внутри цикла: если строчка МассивОрганизаций = Новый Массив; находится внутри тела цикла, то на каждой итерации создается новый пустой массив. В результате условие поиска всегда будет возвращать Неопределено, а коллекция никогда не сохранит предыдущие значения.
  2. Добавление элемента до проверки: если вызвать МассивОрганизаций.Добавить(Значение) перед условием Если МассивОрганизаций.Найти(Значение) = Неопределено, условие никогда не выполнится, так как элемент уже помещен в массив.

Способ 3. Сворачивание таблицы значений

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

Разберем пример реализации:


// Выгружаем и сворачиваем таблицу по организации
ТаблицаОрганизаций = СсылкаНаОбъект.Маршрут.Выгрузить(, "Организация");
ТаблицаОрганизаций.Свернуть("Организация");

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

Если таблица содержит большое количество строк, для ускорения работы метода НайтиСтроки() рекомендуется добавить индекс по полю поиска: Таблица.Индексы.Добавить("Организация");.

Способ 4. Группировка выборки в запросе через ИТОГИ ПО (архитектурно правильное решение)

Когда конечной целью является печать или пакетная обработка документов по группам (например, сгруппировать заявки по организациям), правильнее всего перенести группировку на уровень СУБД с помощью одного запроса и секции ИТОГИ.

Это исключает критический антипаттерн «Запрос в цикле», который многократно увеличивает количество обращений к СУБД и замедляет работу системы.

Рассмотрим пример формирования запроса и двухуровневого обхода выборки:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Маршрут.Заявка.Организация КАК ЗаявкаОрганизация,
    |	Маршрут.Заявка.Ссылка КАК ЗаявкаСсылка,
    |	Маршрут.Ссылка КАК ОтправкаСсылка
    |ИЗ
    |	Документ.ОтправкаМашины.Маршрут КАК Маршрут
    |ГДЕ
    |	Маршрут.Ссылка = &ОтправкаСсылка
    |ИТОГИ ПО
    |	ЗаявкаОрганизация";

Запрос.УстановитьПараметр("ОтправкаСсылка", СсылкаНаОбъект);

РезультатЗапроса = Запрос.Выполнить();

// Выбираем верхний уровень группировки — уникальные организации
ВыборкаОрганизаций = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "ЗаявкаОрганизация");

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

Способ 5. Линейная выборка с сортировкой (паттерн смены ключа)

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

Посмотрим на пример реализации:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Маршрут.Заявка.Организация КАК ЗаявкаОрганизация,
    |	Маршрут.Заявка.Ссылка КАК ЗаявкаСсылка
    |ИЗ
    |	Документ.ОтправкаМашины.Маршрут КАК Маршрут
    |ГДЕ
    |	Маршрут.Ссылка = &ОтправкаСсылка
    |УПОРЯДОЧИТЬ ПО
    |	ЗаявкаОрганизация";

Запрос.УстановитьПараметр("ОтправкаСсылка", СсылкаНаОбъект);
Выборка = Запрос.Выполнить().Выбрать();

ПредыдущаяОрганизация = Неопределено;

Пока Выборка.Следующий() Цикл
    Если Выборка.ЗаявкаОрганизация <> ПредыдущаяОрганизация Тогда
        ПредыдущаяОрганизация = Выборка.ЗаявкаОрганизация;
        // Код инициализации блока новой организации
    КонецЕсли;
    
    // Обработка конкретной строки
КонецЦикла;

Рекомендации по оптимизации кода

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