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