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