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