При разработке отчетов или механизмов вывода результатов исследований часто возникает ситуация, когда данные в массиве содержат повторяющиеся заголовки для каждой строки. Например, если мы выводим результаты анализов, заголовок «Общий анализ крови» может дублироваться перед каждым показателем (лейкоциты, эритроциты и т.д.). Это делает документ трудночитаемым. В этой статье мы разберем, как эффективно сгруппировать данные и вывести заголовок только один раз для группы записей.
Это самый простой и быстрый способ, который подходит для небольших массивов данных, уже отсортированных по нужному полю. Рассмотрим алгоритм: мы создаем переменную вне цикла, в которой будем хранить значение заголовка предыдущей итерации. Если текущий заголовок совпадает с предыдущим, мы его не выводим или заменяем на пустую строку.
Проанализируем пример кода, реализующий эту логику:
ПеременнаяПредыдущийЗаголовок = "";
Для Каждого СтрокаДанных Из МассивРезультатов Цикл
Если СтрокаДанных.РегистраторЗаголовок = ПеременнаяПредыдущийЗаголовок Тогда
// Заголовок совпадает с предыдущим, выводим только результат
ТекстДляВывода = СтрокаДанных.Показатель + ": " + СтрокаДанных.Значение;
Иначе
// Заголовок изменился, выводим новый заголовок и результат
ТекстДляВывода = "<b>" + СтрокаДанных.РегистраторЗаголовок + "</b><br>" +
СтрокаДанных.Показатель + ": " + СтрокаДанных.Значение;
// Запоминаем новый заголовок для сравнения на следующей итерации
ПеременнаяПредыдущийЗаголовок = СтрокаДанных.РегистраторЗаголовок;
КонецЕсли;
// Добавляем сформированный текст в документ
ФорматированныйДокумент.Добавить(ТекстДляВывода);
КонецЦикла;
Важно: этот метод будет работать корректно только в том случае, если Массив_ строго отсортирован по полю РегистраторЗаголовок. Если данные перемешаны, дубли останутся.
Если мы не уверены в сортировке исходного массива, правильнее будет использовать объект ТаблицаЗначений. Рассмотрим подробнее, как это сделать. Сначала мы загружаем данные в таблицу, сортируем их и только потом выводим.
Разберем по шагам:
ТаблицаЗначений и добавляем колонки.Сортировать().
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("РегистраторЗаголовок");
ТЗ.Колонки.Добавить("Результат");
// Заполняем ТЗ из исходного массива
Для Каждого Элемент Из ИсходныйМассив Цикл
НоваяСтрока = ТЗ.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Элемент);
КонецЦикла;
// Сортируем данные для правильной группировки
ТЗ.Сортировать("РегистраторЗаголовок");
// Вывод данных
ТекЗаголовок = "";
Для Каждого Стр Из ТЗ Цикл
Если Стр.РегистраторЗаголовок <> ТекЗаголовок Тогда
// Выводим заголовок один раз
ВывестиВДокумент(Стр.РегистраторЗаголовок);
ТекЗаголовок = Стр.РегистраторЗаголовок;
КонецЕсли;
ВывестиВДокумент(Стр.Результат);
КонецЦикла;
Еще один профессиональный подход — использование объекта Соответствие. Это позволяет сгруппировать данные по ключу, где ключом выступает заголовок, а значением — массив всех результатов, относящихся к нему.
Посмотрим на пример реализации:
ГруппировкаДанных = Новый Соответствие;
// Распределяем данные по группам
Для Каждого Элемент Из Массив_ Цикл
Ключ = Элемент.РегистраторЗаголовок;
МассивРезультатов = ГруппировкаДанных.Получить(Ключ);
Если МассивРезультатов = Неопределено Тогда
МассивРезультатов = Новый Массив;
ГруппировкаДанных.Вставить(Ключ, МассивРезультатов);
КонецЕсли;
МассивРезультатов.Добавить(Элемент.ПоказательРезультат);
КонецЦикла;
// Выводим сгруппированные данные
Для Каждого Запись Из ГруппировкаДанных Цикл
// Запись.Ключ — это наш уникальный заголовок
ВывестиЗаголовок(Запись.Ключ);
// Запись.Значение — это массив результатов для этого заголовка
Для Каждого Результат Из Запись.Значение Цикл
ВывестиРезультат(Результат);
КонецЦикла;
КонецЦикла;
Выясним причину, почему при работе с большим количеством данных вывод может замедляться. Если мы будем напрямую манипулировать объектом ФорматированныйДокумент внутри цикла, 1С будет пересчитывать структуру документа при каждом добавлении строки.
Для ускорения работы рекомендуется сформировать итоговую строку в формате HTML и установить её в документ одним действием. Проанализируем ситуацию на примере:
ТекстHTML = "<html><body>";
ТекЗаголовок = "";
Для Каждого Стр Из СортированнаяТЗ Цикл
Если Стр.РегистраторЗаголовок <> ТекЗаголовок Тогда
ТекстHTML = ТекстHTML + "<h3>" + Стр.РегистраторЗаголовок + "</h3>";
ТекЗаголовок = Стр.РегистраторЗаголовок;
КонецЕсли;
ТекстHTML = ТекстHTML + "<p>" + Стр.Результат + "</p>";
КонецЦикла;
ТекстHTML = ТекстHTML + "</body></html>";
// Устанавливаем весь текст разом
ФорматированныйДокументПоле.УстановитьHTML(ТекстHTML, Новый Структура);
Если данные для массива изначально выбираются из базы данных, то оптимальным решением будет использование конструкции ИТОГИ ПО в запросе. Это позволит получить иерархическую структуру (выборку с итогами), где заголовок будет верхним уровнем, а показатели — подчиненным.
Рассмотрим структуру такого запроса:
ВЫБРАТЬ
РезультатыАнализов.РегистраторЗаголовок КАК Заголовок,
РезультатыАнализов.Показатель КАК Показатель,
РезультатыАнализов.Значение КАК Значение
ИЗ
РегистрСведений.РезультатыАнализов КАК РезультатыАнализов
ИТОГИ ПО
Заголовок
При обходе результата такого запроса с помощью метода Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам), вы получите чистую логику: внешний цикл выводит заголовок, а внутренний — все связанные с ним записи. Это исключает необходимость вручную проверять дубли и сравнивать значения с предыдущими итерациями.
Резюме: для простых задач достаточно сравнения с переменной в цикле (при условии сортировки), но для сложных интерфейсов и больших объемов данных лучше использовать Соответствие или группировку на уровне Запроса.