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

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

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

Разберем практическую ситуацию: в конфигурации есть бизнес-процесс и подчиненная ему задача. На форме задачи расположено текстовое поле ИсторияВыполненияЗадач. Этот текст формируется функцией ФормированиеТекстаВФорму, находящейся в модуле формы задачи. Нам необходимо построить отчет, который выведет все задачи и заполнит колонку с историей их выполнения. Выясним причину, почему стандартный подход «в лоб» не работает, и рассмотрим по шагам правильные архитектурные решения этой задачи.

Почему нельзя просто обратиться к модулю формы

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

Единственный верный путь для получения таких динамических данных — вынос алгоритма расчета из модуля формы во внешний программный компонент, доступный для СКД. Таким компонентом является Общий модуль с установленным флагом Сервер.

Решение 1. Наивный перенос функции в общий модуль и вызов через вычисляемые поля СКД

Рассмотрим базовый вариант реализации, к которому пришли разработчики в ходе обсуждения на форуме. Этот способ работает, но, как мы разберем позже, имеет скрытые проблемы с быстродействием.

Для начала проанализируем ситуацию с исходной функцией. Нам нужно перенести код формирования текста в общий модуль. Пусть это будет общий модуль ФормированиеИсторииЗадач. Убедимся, что в свойствах модуля в конфигураторе стоят флажки Сервер и Вызов сервера, а сама функция объявлена с ключевым словом Экспорт.

Создадим или скорректируем функцию в общем модуле следующим образом:


Функция ФормированиеТекстаВФорму(БизнесПроцесс = Неопределено) Экспорт
    Если БизнесПроцесс = Неопределено Тогда
        Возврат "";
    КонецЕсли;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    ЗадачаМоегоБизнесПроцесса.Ссылка КАК Ссылка,
    |    ЗадачаМоегоБизнесПроцесса.Наименование КАК Наименование,
    |    ЗадачаМоегоБизнесПроцесса.Комментарий КАК Комментарий
    |ИЗ
    |    Задача.ЗадачаМоегоБизнесПроцесса КАК ЗадачаМоегоБизнесПроцесса
    |ГДЕ
    |    ЗадачаМоегоБизнесПроцесса.БизнесПроцесс = &БизнесПроцесс";
    
    Запрос.УстановитьПараметр("БизнесПроцесс", БизнесПроцесс);
    Выборка = Запрос.Выполнить().Выбрать();
    
    РезультатТекст = "";
    Пока Выборка.Следующий() Цикл
        // Здесь выполняется логика склеивания строк и форматирования текста
        РезультатТекст = РезультатТекст + Выборка.Наименование + ": " + Выборка.Комментарий + Символы.ПС;
    КонецЦикла;
    
    Возврат РезультатТекст;
КонецФункции

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

  1. Откроем конструктор схемы компоновки данных и перейдем на вкладку Наборы данных.
  2. В тексте запроса нашего основного набора данных обязательно выберем поле БизнесПроцесс (ссылку на бизнес-процесс, к которому привязана задача). Наш запрос должен выглядеть следующим образом:
    
    ВЫБРАТЬ
        ЗадачаМоегоБизнесПроцесса.Ссылка КАК Ссылка,
        ЗадачаМоегоБизнесПроцесса.Наименование КАК Наименование,
        ЗадачаМоегоБизнесПроцесса.БизнесПроцесс КАК БизнесПроцесс
    ИЗ
        Задача.ЗадачаМоегоБизнесПроцесса КАК ЗадачаМоегоБизнесПроцесса
    
  3. Перейдем на вкладку Вычисляемые поля.
  4. Добавим новое поле. Зададим ему имя, например, ИсторияЗадачТекст.
  5. В колонке Выражение напишем вызов нашей функции из общего модуля, передав в качестве параметра поле бизнес-процесса из запроса: ФормированиеИсторииЗадач.ФормированиеТекстаВФорму(БизнесПроцесс)
  6. Перейдем на вкладку Настройки и добавим наше новое вычисляемое поле ИсторияЗадачТекст в список выбранных полей отчета.

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

Внимание: Критическая проблема производительности (Антипаттерн «Запрос в цикле»)

Несмотря на то, что описанное выше решение технически решает задачу, оно таит в себе серьезную опасность для стабильности и производительности системы. Проанализируем ситуацию подробнее.

Когда СКД рассчитывает вычисляемое поле, она делает это построчно. Если ваш отчет выбирает 100 задач, платформа выполнит 1 исходный запрос для получения списка задач, а затем 100 раз последовательно вызовет функцию общего модуля. А внутри этой функции каждый раз инициализируется и выполняется новый запрос к СУБД! Это классическая проблема N+1 запросов.

Если в вашей базе данных накопится 10 000 задач, формирование простого отчета приведет к выполнению 10 000 отдельных мелких запросов к СУБД. Это вызовет колоссальную нагрузку на дисковую систему сервера, процессор и сеть, а отчет просто «зависнет». В профессиональной разработке использование запросов внутри функций, вызываемых из вычисляемых полей СКД, категорически запрещено.

Рассмотрим более продвинутые и безопасные с точки зрения производительности альтернативные варианты реализации.

Решение 2. Разделение получения данных и форматирования (Рекомендуемый подход)

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

Разберем по шагам, как перестроить архитектуру решения:

  1. В основном запросе СКД мы выбираем не просто ссылку на бизнес-процесс, а соединяем таблицы или получаем нужные реквизиты через точку. Например, нам нужны наименования и комментарии всех задач этого бизнес-процесса. Мы можем сделать левое соединение или собрать данные вложенной таблицей (если СКД позволяет работать с коллекциями).
  2. Если структура простая, мы можем передавать в функцию уже готовые строки. Если же нам нужно собрать текстовое представление из нескольких реквизитов одной строки, мы передаем их параметрами: ИмяМодуля.СформироватьКрасивыйТекст(Наименование, Комментарий). Внутри этой функции нет никаких запросов — она мгновенно обрабатывает уже полученные СУБД текстовые значения на уровне оперативной памяти сервера приложений.

Решение 3. Использование связей наборов данных в СКД

Платформа 1С предоставляет мощный встроенный механизм для связи таблиц без написания кода на встроенном языке. Мы можем полностью отказаться от функций общих модулей и решить задачу силами СКД с помощью связей наборов данных.

Посмотрим на пример реализации данного подхода:

  1. В схеме компоновки данных создадим Набор данных - Запрос 1 (выбирает список основных задач, для которых строится отчет).
  2. Создадим второй набор данных — Набор данных - Запрос 2, который будет выбирать подробные сведения (историю, комментарии, авторов) по всем бизнес-процессам без ограничений:
    
    ВЫБРАТЬ
        Задача.БизнесПроцесс КАК БизнесПроцесс,
        Задача.Наименование КАК Наименование,
        Задача.Комментарий КАК Комментарий
    ИЗ
        Задача.ЗадачаМоегоБизнесПроцесса КАК Задача
    
  3. Перейдем на вкладку Связи наборов данных.
  4. Добавим новую связь. Источником связи укажем НаборДанных1, приемником — НаборДанных2. В качестве условия связи свяжем их по полю БизнесПроцесс.
  5. В качестве выводимого поля мы можем использовать встроенную функцию языка выражений СКД — СоединитьСтроки().

Функция СоединитьСтроки() позволяет объединить несколько строк подчиненного набора данных в одну строку через указанный разделитель. В выражении ресурсов или вычисляемых полей мы пишем:

СоединитьСтроки(НаборДанных2.Наименование, ", ", "; ")

Этот подход невероятно быстр. Платформа 1С самостоятельно оптимизирует запросы к СУБД, минимизируя их количество и эффективно кэшируя результаты.

Решение 4. Использование «Набора данных - Объект» (внешний источник)

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

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

  1. Первым пакетным запросом мы выбираем все задачи, которые должны попасть в отчет.
  2. Выгружаем результат в Таблицу Значений (ТЗ). Из этой ТЗ получаем массив уникальных ссылок на бизнес-процессы.
  3. Вторым запросом (передав массив БП в качестве параметра ГДЕ БизнесПроцесс В (&МассивБП)) мы за один раз получаем всю историю выполнения по всем нужным процессам скопом.
  4. В цикле обходим нашу ТЗ задач, сопоставляем данные из второго запроса и программно заполняем колонку ИсторияВыполненияЗадач текстовым представлением. Нам потребуется сделать всего 2 запроса к базе данных вместо тысяч!
  5. Полученную готовую Таблицу Значений мы передаем обратно в СКД через Набор данных - Объект.

Рассмотрим пример кода в модуле объекта отчета:


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

Резюме и правила хорошего тона при работе с СКД

Подводя итоги, важно зафиксировать ключевые правила архитектуры отчетов 1С. Если перед вами стоит задача вывести динамические данные из форм в отчет, всегда следуйте этим рекомендациям:

← На главную