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