Как вывести реквизиты вычисляемого поля в СКД и оптимизировать работу отчета

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

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

Проблема: дублирование вычислений и недоступность реквизитов

Представим ситуацию: нам нужно вывести информацию о действующем сертификате номенклатуры. Логика поиска сертификата сложная (нужно соединять таблицы, проверять даты и области действия), поэтому мы выносим ее в отдельную функцию общего модуля. Рассмотрим пример такой функции, предоставленный автором темы:


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

Если мы просто добавим вычисляемое поле с выражением СертификатНоменклатуры(Номенклатура, Характеристика), мы столкнемся с тем, что в настройках отчета мы не сможем развернуть это поле "через точку", чтобы выбрать, например, ДатаОкончанияСрокаДействия. А если мы начнем создавать отдельные поля для каждого реквизита, вызывая функцию снова и снова, это приведет к катастрофическому падению производительности, так как запрос в функции будет выполняться для каждой строки отчета столько раз, сколько реквизитов мы выводим.

Решение 1: Использование связей наборов данных (Рекомендуемый метод)

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

  1. Создадим второй набор данных — запрос. Назовем его НаборДанныхСертификаты. В нем пропишем запрос, который выбирает ссылку на сертификат и все его необходимые реквизиты.
  2. Настроим связи наборов данных. На соответствующей закладке свяжем наш основной набор (где есть номенклатура) с новым набором данных. В качестве условий связи укажем Номенклатура и Характеристика.
  3. Ограничим выборку. Чтобы для каждой номенклатуры выбирался только один актуальный сертификат, в параметрах связи можно использовать функционал условий или просто грамотно спроектировать запрос во втором наборе.

Преимущество этого метода: СКД сама оптимизирует выполнение запросов. Все реквизиты сертификата станут доступны в дереве полей автоматически, и нам не придется писать ни строчки кода на встроенном языке.

Решение 2: Принудительная типизация через основной запрос

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

Выполним следующий трюк:

  1. В основном запросе добавим "пустое" поле с нужным нам типом. Например: ЗНАЧЕНИЕ(Справочник.СертификатыНоменклатуры.ПустаяСсылка) КАК СертификатОбъект.
  2. Добавим это поле в список "Поля компоновки данных".
  3. Перейдем на закладку "Вычисляемые поля" и создадим новое поле с ТЕМ ЖЕ ИМЕНЕМ — СертификатОбъект. В выражении укажем вызов нашей функции: МойОбщийМодуль.СертификатНоменклатуры(Номенклатура, Характеристика).

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

Решение 3: Цепочка вычисляемых полей и оптимизация вызовов

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

Рассмотрим порядок действий:

  1. Создаем вычисляемое поле ОсновнойСертификат. В выражении пишем: СертификатНоменклатуры(Номенклатура, Характеристика).
  2. Создаем второе вычисляемое поле, например, ДатаСертификата. В выражении НЕ ВЫЗЫВАЕМ функцию, а пишем просто: ОсновнойСертификат.ДатаОкончанияСрокаДействия.

Важный нюанс: Чтобы это работало без повторных запросов в базу внутри каждой строки, необходимо использовать механизм кэширования в общем модуле.

Решение 4: Кэширование в общем модуле (Повторное использование значений)

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

  1. Выносим функцию поиска сертификата в отдельный общий модуль.
  2. В свойствах модуля устанавливаем параметр "Повторное использование возвращаемых значений" в значение "На время вызова" или "На время сеанса" (в зависимости от частоты обновления данных).

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

Пример кода для вызова реквизита через вспомогательную функцию (как предлагал автор):


Функция СертификатНоменклатурыРеквизит(Номенклатура, Характеристика, ИмяРеквизита) Экспорт
    // Благодаря кэшированию модуля, вызов основной функции будет быстрым
    СсылкаНаСертификат = СертификатНоменклатуры(Номенклатура, Характеристика);
    Если ЗначениеЗаполнено(СсылкаНаСертификат) Тогда
        Возврат СсылкаНаСертификат[ИмяРеквизита];
    Иначе
        Возврат Неопределено;
    КонецЕсли;
КонецФункции

Выводы и рекомендации

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

Если же архитектура требует использования внешних функций, обязательно следуйте этим правилам:

Применяя эти методы, мы сможем создавать гибкие, функциональные и, что самое главное, быстрые отчеты, которые будут удобны как для разработчика, так и для конечного пользователя.

← На главную