Как заменить временную таблицу в запросе СКД на таблицу значений без использования набора данных «Объект»?

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

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

Стандартный и очевидный путь — использование «Набора данных - Объект». Однако опытные разработчики знают, что этот метод имеет критические недостатки:

  1. Ограничения на соединения: Связать набор данных «Объект» с набором данных «Запрос» можно только на вкладке «Связи наборов данных». При этом допускается исключительно левое соединение, что делает невозможным использование внутренних или правых соединений на уровне СУБД.
  2. Падение производительности: Соединение наборов данных СКД выполняет в оперативной памяти сервера приложений 1С. Если таблицы содержат десятки или сотни тысяч строк, отчет начинает сильно тормозить и может привести к перерасходу оперативной памяти.
  3. Сложность доработки: Вам приходится перестраивать архитектуру схемы компоновки данных, описывать вручную все поля и связи, что усложняет дальнейшую поддержку отчета.

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

Штатное решение для современных платформ (8.3.17+): Использование Менеджера временных таблиц

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

Давайте разберем по шагам, как реализовать этот механизм в вашем отчете.

Шаг 1. Описание внешней временной таблицы в запросе СКД

В основном запросе СКД мы пишем текст так, будто временная таблица из нашей ТЗ уже существует в базе данных. Однако здесь есть крайне важный нюанс — типизация полей.

Поскольку конфигуратор и компоновщик макета СКД анализируют структуру запроса до его фактического выполнения, они не могут знать заранее, какие типы данных будут переданы во внешней таблице значений. Чтобы избежать ошибок компоновки макета, мы должны явно типизировать поля внешней ВТ с помощью конструкции ВЫРАЗИТЬ() или функции ЕСТЬNULL() в первом пакете запроса СКД.

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


// ПАКЕТ №1: Обязательно типизируем поля внешней временной таблицы ВТ_Внешняя
ВЫБРАТЬ
    ВЫРАЗИТЬ(ВТ_Внешняя.Номенклатура КАК Справочник.Номенклатура) КАК Номенклатура,
    ВЫРАЗИТЬ(ВТ_Внешняя.Количество КАК ЧИСЛО(15, 3)) КАК Количество
ПОМЕСТИТЬ ВТ_Типизированная
ИЗ
    ВТ_Внешняя КАК ВТ_Внешняя;

////////////////////////////////////////////////////////////////////////////////
// ПАКЕТ №2: Работаем с типизированной таблицей с использованием любых соединений
ВЫБРАТЬ
    ВТ_Типизированная.Номенклатура КАК Номенклатура,
    ВТ_Типизированная.Количество КАК Количество,
    ЕСТЬNULL(ЦеныНоменклатурыСрезПоследних.Цена, 0) КАК Цена,
    ВТ_Типизированная.Количество * ЕСТЬNULL(ЦеныНоменклатурыСрезПоследних.Цена, 0) КАК Сумма
ИЗ
    ВТ_Типизированная КАК ВТ_Типизированная
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК ЦеныНоменклатурыСрезПоследних
        ПО ВТ_Типизированная.Номенклатура = ЦеныНоменклатурыСрезПоследних.Номенклатура

Обратите внимание, что во втором пакете мы применили полноценное ВНУТРЕННЕЕ СОЕДИНЕНИЕ, которое отработает максимально быстро на уровне СУБД MS SQL или PostgreSQL.

Шаг 2. Программный код подготовки данных и вывода отчета

Для реализации этого метода нам необходимо переопределить стандартный вывод отчета. Мы сделаем это в модуле объекта нашего отчета в предопределенной процедуре ПриКомпоновкеРезультата.

Давайте подробно проанализируем программный код:


Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
    
    // Отменяем стандартный вывод отчета системой
    СтандартнаяОбработка = Ложь;
    
    // 1. Формируем исходную таблицу значений (ТЗ)
    // В реальном отчете здесь будет ваш алгоритм заполнения ТЗ данными
    ТаблицаДанных = Новый ТаблицаЗначений;
    ТаблицаДанных.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
    ТаблицаДанных.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
    
    // Заполним ТЗ тестовой строкой
    НоваяСтрока = ТаблицаДанных.Добавить();
    НоваяСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоКоду("00000001");
    НоваяСтрока.Количество = 150;
    
    // 2. Создаем Менеджер временных таблиц и помещаем туда нашу ТЗ
    МВТ = Новый МенеджерВременныхТаблиц;
    
    ЗапросПодготовки = Новый Запрос;
    ЗапросПодготовки.МенеджерВременныхТаблиц = МВТ;
    ЗапросПодготовки.Текст = 
        "ВЫБРАТЬ
        |   ВнешняяТЗ.Номенклатура КАК Номенклатура,
        |   ВнешняяТЗ.Количество КАК Количество
        |ПОМЕСТИТЬ ВТ_Внешняя
        |ИЗ
        |   &ВнешняяТЗ КАК ВнешняяТЗ";
        
    ЗапросПодготовки.УстановитьПараметр("ВнешняяТЗ", ТаблицаДанных);
    ЗапросПодготовки.Выполнить();
    
    // 3. Инициализируем макет и настройки СКД
    СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
    Настройки = КомпоновщикНастроек.ПолучитьНастройки();
    
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
    
    // 4. Инициализируем процессор компоновки данных и передаем наш МВТ шестым параметром
    ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
    
    ПроцессорКомпоновки.Инициализировать(
        МакетКомпоновки, 
        , // Внешние наборы данных (оставляем пустым)
        ДанныеРасшифровки, 
        Истина, // Разрешить использование внешних функций
        , // Ограничивать получение полей по ссылкам
        МВТ // НАШ ПОДГОТОВЛЕННЫЙ МЕНЕДЖЕР ВРЕМЕННЫХ ТАБЛИЦ
    );
    
    // 5. Выводим результат в табличный документ
    ДокументРезультат.Очистить();
    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
    ПроцессорВывода.Вывести(ПроцессорКомпоновки);
    
    // Освобождаем ресурсы СУБД, закрывая менеджер временных таблиц
    МВТ.Закрыть();
    
КонецПроцедуры

Благодаря этому подходу, СКД выполнит основной запрос на сервере базы данных, успешно подключившись к временной таблице ВТ_Внешняя, которую мы передали через объект МенеджерВременныхТаблиц.

Решение для старых версий платформ (режим совместимости ниже 8.3.17)

Если ваша конфигурация работает в режиме совместимости с более старыми версиями платформы (например, 8.3.14 - 8.3.16), то метод ПроцессорКомпоновкиДанных.Инициализировать() не примет шестой параметр и вызовет ошибку. В такой ситуации мы можем воспользоваться проверенным обходным путем — динамической подменой текста запроса СКД.

Суть метода заключается в том, что мы генерируем фрагмент SQL-запроса, состоящий из конструкций ВЫБРАТЬ ... ОБЪЕДИНИТЬ ВСЕ на основе строк нашей таблицы значений, и программно встраиваем его в схему компоновки данных перед компоновкой макета.

Давайте разберем, как выглядит этот процесс по шагам:

  1. В схеме компоновки данных (СКД) в тексте запроса мы подготавливаем маркер-заглушку, например:
    
    //{ВСТАВКА_ВТ_ЗНАЧЕНИЙ}
    ПОМЕСТИТЬ ВТ_Внешняя
    ИЗ
        &Заглушка;
    
  2. В модуле отчета мы пишем алгоритм, который обходит строки таблицы значений и собирает текстовый блок, заменяя маркер:
    
    ТекстВыборкиТЗ = "";
    Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
        Если Не ПустаяСтрока(ТекстВыборкиТЗ) Тогда
            ТекстВыборкиТЗ = ТекстВыборкиТЗ + Символы.ПС + "ОБЪЕДИНИТЬ ВСЕ" + Символы.ПС;
        КонецЕсли;
        
        // Формируем строку выборки для каждой записи нашей ТЗ
        ТекстВыборкиТЗ = ТекстВыборкиТЗ + 
            "ВЫБРАТЬ Значение(Справочник.Номенклатура.ПустаяСсылка) КАК Номенклатура, 0 КАК Количество"; 
            // В реальном коде подставляйте реальные значения или ссылки!
    КонецЦикла;
    
    // Получаем текст запроса из схемы СКД и производим замену
    СхемаКомпоновки = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
    НаборДанныхЗапрос = СхемаКомпоновки.НаборыДанных.Найти("НаборДанныхЗапрос");
    НаборДанныхЗапрос.Запрос = СтрЗаменить(НаборДанныхЗапрос.Запрос, "//{ВСТАВКА_ВТ_ЗНАЧЕНИЙ}", ТекстВыборкиТЗ);
    

Важное предостережение: Метод динамической подмены текста запроса отлично подходит для небольших таблиц значений (до 100-200 строк). Если в вашей ТЗ содержатся тысячи строк, СУБД потратит слишком много времени на парсинг гигантского SQL-запроса с тысячами блоков ОБЪЕДИНИТЬ ВСЕ, что может привести к падению производительности сервера СУБД.

Рекомендации по оптимизации производительности

Чтобы ваши отчеты работали максимально быстро и не создавали избыточную нагрузку на сервер 1С и СУБД, рекомендуем соблюдать три простых правила:

Резюме

Мы детально изучили современные и классические способы передачи таблицы значений в СКД в обход ограничений «Набора данных - Объект». Использование МенеджерВременныхТаблиц в качестве параметра процессора компоновки данных — это самый передовой, быстрый и удобный инструмент, который делает интеграцию внешних данных в сложные аналитические отчеты 1С простой и эффективной задачей.

← На главную