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

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

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

Проблема повторного выполнения запроса

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


// Вывод в табличный документ
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);

// Повторный вывод в таблицу значений
ТЗ = Новый ТаблицаЗначений;
ПроцессорВыводаТЗ = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ПроцессорВыводаТЗ.УстановитьОбъект(ТЗ);    
ПроцессорВыводаТЗ.Вывести(ПроцессорКомпоновки);

Выясним причину, по которой этот код работает неоптимально. Объект ПроцессорКомпоновкиДанных функционирует по принципу последовательного курсора. Когда мы вызываем метод Вывести() в первый раз, процессор итерирует все записи, полученные в результате выполнения запроса. По завершении цикла указатель данных оказывается в самом конце набора. Для того чтобы второй процессор вывода смог «прочитать» те же самые данные, СКД вынуждена инициировать запрос к СУБД заново, чтобы наполнить внутренний кэш или заново открыть курсор. На больших объемах данных или при сложных запросах это удваивает время формирования отчета.

Решение 1: Использование набора данных «Объект»

Один из наиболее очевидных способов избежать повторного обращения к БД — это предварительная выгрузка данных в промежуточную таблицу значений. В этом случае мы меняем тип набора данных в схеме СКД с «Запрос» на «Объект».

Рассмотрим алгоритм реализации этого метода:

  1. Выполняем запрос к базе данных программно с помощью объекта Запрос.
  2. Выгружаем результат в ТаблицаЗначений.
  3. Передаем эту таблицу в СКД через структуру ВнешниеНаборыДанных.
  4. Выполняем компоновку стандартным способом.

Преимущества: Запрос выполняется строго один раз. Мы имеем полный контроль над данными в таблице еще до начала работы механизмов компоновки.

Недостатки и нюансы: Проанализируем риски. При использовании набора данных Запрос СКД сама оптимизирует текст SQL, добавляя только те поля, которые выбрал пользователь. В случае с набором данных Объект нам придется всегда выбирать все возможные поля в таблицу значений, что увеличивает потребление оперативной памяти. Кроме того, автоматический расчет иерархии в СКД на базе объектных данных может работать медленнее.

Решение 2: Ручной обход результата компоновки (самый эффективный)

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

Разберем пример кода для этого метода:


ПроцессорВыводаВТД = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВыводаВТД.УстановитьДокумент(ДокументРезультат);

ПроцессорВыводаВТЗ = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ТЗ = Новый ТаблицаЗначений;
ПроцессорВыводаВТЗ.УстановитьОбъект(ТЗ);

// Начинаем вывод
ПроцессорВыводаВТД.НачатьВывод();
ПроцессорВыводаВТЗ.НачатьВывод();

Пока Истина Цикл
    ЭлементРезультата = ПроцессорКомпоновки.Следующий();
    Если ЭлементРезультата = Неопределено Тогда
        Прервать;
    КонецЕсли;

    // Выводим один и тот же элемент в оба приемника одновременно
    ПроцессорВыводаВТД.ВывестиЭлемент(ЭлементРезультата);
    ПроцессорВыводаВТЗ.ВывестиЭлемент(ЭлементРезультата);
КонецЦикла;

ПроцессорВыводаВТД.ЗакончитьВывод();
ПроцессорВыводаВТЗ.ЗакончитьВывод();

Проанализируем ситуацию: в этом случае ПроцессорКомпоновки считывает запись из БД один раз, и мы тут же передаем её обоим процессорам вывода. Это максимально экономит ресурсы сервера и время пользователя.

Решение 3: Программный анализ через «ДанныеРасшифровки»

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

Рассмотрим, как это работает: при формировании отчета в ПроцессорКомпоновки.Инициализировать() передается ссылка на объект расшифровки. После формирования отчета мы можем перебирать области ТабличныйДокумент, получать идентификатор расшифровки из ячейки и обращаться к коллекции ДанныеРасшифровки.Элементы. Это позволяет получить значения измерений без обращения к базе данных. Однако этот метод требует написания сложного кода обхода ячеек и подходит только для специфических задач анализа.

Решение 4: Использование двух последовательных схем (Цепочка СКД)

В некоторых случаях удобно разделить логику на две разные схемы компоновки данных. Рассмотрим этот архитектурный подход:

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

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

Важные рекомендации по работе с памятью

Выясним причину возможных проблем при работе с большими объемами данных. Если ваш отчет возвращает сотни тысяч строк, выгрузка их в ТаблицаЗначений может привести к переполнению оперативной памяти на сервере 1С. В таких ситуациях стоит оценить, что дешевле: повторный оптимизированный запрос к SQL-серверу (который умеет эффективно работать с дисковым кэшем) или хранение огромного массива данных в памяти процесса rphost.

Также стоит помнить про Менеджер Временных Таблиц. Если ваш основной запрос использует временные таблицы, то при повторном выводе (если вы все же используете метод из первого примера) важно, чтобы менеджер временных таблиц оставался «живым» и не очищался между вызовами. В противном случае второй вызов Вывести() завершится ошибкой «Таблица не найдена».

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

← На главную