При разработке отчетов на схеме компоновки данных (СКД) программисты часто сталкиваются с необходимостью вывода сложных расчетных показателей. Самым простым решением кажется вызов экспортной функции общего модуля непосредственно в выражении вычисляемого поля. Однако этот подход таит в себе две серьезные проблемы: ошибки преобразования типов данных и катастрофическое падение производительности отчета (вплоть до того, что простой отчет по 300 сотрудникам может выполняться целый час). В этой статье мы подробно разберем, почему возникают эти проблемы и как их правильно решить несколькими способами.
Число()Рассмотрим ситуацию, когда функция общего модуля возвращает форматированное строковое представление числа (например, результат работы метода Представление() или функции форматирования). При попытке сложить два таких вычисляемых поля или явно преобразовать их через функцию Число(), платформа 1С выдает ошибку: «Преобразование значения к типу Число не может быть выполнено».
В чем кроется причина? Дело в том, что при формировании красивого представления чисел (особенно сумм) система 1С по умолчанию разделяет группы разрядов (тысячи, миллионы) специальным символом — неразрывным пробелом. Этот символ имеет код 160 (в отличие от обычного пробела с кодом 32). Стандартная функция платформы Число() абсолютно «не умеет» распознавать неразрывные пробелы, буквы валют и прочие оформительские символы. Встретив их в строке, она аварийно завершает работу.
Разберем два пути решения этой проблемы: быстрый (исправление строки «на лету») и архитектурно правильный.
Вариант 1. Быстрое исправление в выражении вычисляемого поля
Если у нас нет возможности изменить возвращаемое значение исходной функции общего модуля, нам придется принудительно очистить строку от неразрывных пробелов перед преобразованием. Для этого в выражении вычисляемого поля СКД можно использовать следующую конструкцию:
Число(СтрЗаменить(МоеПолеСтрока, Символы.НПП, ""))
Или, если в вашей конфигурации внедрена Библиотека Стандартных Подсистем (БСП), можно обратиться к специализированной функции общего модуля:
СтроковыеФункцииКлиентСервер.СтрокаВЧисло(СтрЗаменить(МоеПолеСтрока, Символы.НПП, ""))
Вариант 2. Архитектурно правильный подход
Давайте согласимся с важным правилом разработки: функции общего модуля, возвращающие данные для расчетов, должны возвращать чистый тип Число. Передавать строковое представление в СКД для математических операций — это заведомо проигрышный путь.
Красивое визуальное оформление (разделение тысяч, округление, знаки валют) должно настраиваться исключительно средствами самой СКД. Для этого перейдем на вкладку «Наборы данных», выберем наше вычисляемое поле, найдем колонку «Оформление», откроем параметр «Формат» и настроим его интерактивно (например, зададим форматную строку ЧДЦ=2; ЧГ=3,0 для разделения триад пробелом и вывода двух знаков после запятой). Таким образом, данные останутся числами, а пользователь увидит их в красивом, читабельном виде.
Часто разработчики создают одну большую функцию, например ОбщегоНазначения.СуммыСотрудника(ФизЛицо, &Период), которая собирает сразу несколько показателей: оклад, премию, надбавки и ставку. Из-за ограничений синтаксиса СКД мы не можем напрямую написать в вычисляемом поле выражение вида ОбщегоНазначения.СуммыСотрудника(ФизЛицо, &Период).Оклад — СКД выдаст синтаксическую ошибку.
Многие начинают множить функции под каждый показатель отдельно, из-за чего отчет начинает выполняться невероятно долго, так как база данных опрашивается по кругу для каждого сотрудника и каждого поля. Проанализируем, как обойти это ограничение платформы.
Шаг 1. Создание вспомогательной функции-посредника
В любом неглобальном общем модуле (например, ОбщегоНазначенияКлиентСервер) создадим простую экспортную функцию, которая умеет безопасно извлекать свойства из структуры:
Функция ПолучитьЗначениеПоКлючу(СтруктураДанных, Ключ) Экспорт
Если ТипЗнч(СтруктураДанных) = Тип("Структура") И СтруктураДанных.Свойство(Ключ) Тогда
Возврат СтруктураДанных[Ключ];
КонецЕсли;
Возврат 0;
КонецФункции
Шаг 2. Настройка вычисляемых полей в СКД
Теперь в схеме компоновки данных мы можем создать отдельные вычисляемые поля для каждого показателя и прописать в них вызовы нашей вспомогательной функции, передавая внутрь тяжелую структуру:
Оклад: ОбщегоНазначенияКлиентСервер.ПолучитьЗначениеПоКлючу(ОбщегоНазначения.СуммыСотрудника(ФизЛицо, &Период), "Оклад")Премия: ОбщегоНазначенияКлиентСервер.ПолучитьЗначениеПоКлючу(ОбщегоНазначения.СуммыСотрудника(ФизЛицо, &Период), "Премия")Шаг 3. Важнейшая настройка кэширования (Повторное использование)
Обратите внимание: если оставить все как есть, СКД все равно будет вызывать тяжелую функцию СуммыСотрудника для каждого вычисляемого поля отдельно (в нашем примере — 2 раза для каждой строки). Для 300 сотрудников это будет уже 600 тяжелых обращений к базе данных.
Чтобы этого избежать, откроем свойства общего модуля, в котором расположена функция СуммыСотрудника, и установим свойство «Повторное использование возвращаемых значений» в положение «На время вызова» (или «На время сеанса»).
Теперь при расчете поля Оклад платформа выполнит запрос к базе данных и сохранит полученную структуру в кэш оперативной памяти для текущего сотрудника. Когда СКД перейдет к расчету поля Премия для этого же сотрудника, платформа мгновенно вернет готовую структуру из кэша памяти, вообще не обращаясь к СУБД. Скорость работы отчета вырастет в десятки раз!
Несмотря на кэширование, вызовы функций общего модуля из вычисляемых полей СКД для каждой строки — это все еще построчный обход (проблема N+1 запросов). Если в отчете будет не 300 сотрудников, а 10 000, отчет все равно начнет зависать. Самым производительным и профессиональным решением является подготовка таблицы значений на стороне встроенного языка и передача ее в СКД как внешнего набора данных.
Давайте разберем по шагам, как реализовать эту схему:
Шаг 1. Настройка схемы компоновки данных
В конструкторе СКД добавим два набора данных:
ОсновнойЗапрос): он будет выбирать базовую информацию, например, список сотрудников (поле ФизЛицо) и их подразделения.ДополнительныеДанные): укажем имя входящего параметра (Имя объекта содержащего данные) как ТаблицаПоказателей. Вручную добавим в этот набор колонки: ФизЛицо (тип аналогичный первому набору), Оклад (число), Премия (число).Шаг 2. Связи наборов данных
Перейдем на вкладку «Связи наборов данных» и свяжем наши наборы:
ОсновнойЗапросДополнительныеДанныеФизЛицоШаг 3. Программный вывод в модуле объекта отчета
Теперь нам нужно перехватить компоновку отчета и передать туда готовую таблицу значений. Для этого откроем модуль объекта нашего отчета и пропишем обработчик события ПриКомпоновкеРезультата:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// 1. Получаем настройки отчета и инициализируем компоновщик макета
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
// 2. Получаем список сотрудников, которые попадут в отчет.
// Сначала выполним быстрый запрос, чтобы получить только массив уникальных ФизЛиц.
МассивФизЛиц = ПолучитьМассивСотрудниковИзНастроек(Настройки);
// 3. Одним пакетным запросом получаем оклады и премии СРАЗУ для всего массива сотрудников
ТаблицаПоказателей = ПолучитьТаблицуСуммДляСотрудников(МассивФизЛиц);
// 4. Помещаем подготовленную таблицу в структуру внешних наборов данных
ВнешниеНаборыДанных = Новый Структура;
ВнешниеНаборыДанных.Вставить("ТаблицаПоказателей", ТаблицаПоказателей);
// 5. Инициализируем процессор компоновки с передачей внешнего набора данных
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных, ДанныеРасшифровки, Истина);
// 6. Выводим результат в табличный документ
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецПроцедуры
При использовании этого подхода система выполнит всего два быстрых и оптимизированных запроса к базе данных: первый — для получения списка сотрудников, второй — для пакетного получения их окладов и премий. Соединение этих данных произойдет в оперативной памяти силами процессора СКД. Время формирования отчета сократится с минут или часов до долей секунды!