Приветствуем вас, уважаемые разработчики! Система компоновки данных (СКД) в платформе 1С — это мощнейший декларативный инструмент для построения отчетов любой сложности. Однако при выходе за рамки простых однотабличных отчетов мы часто сталкиваемся со специфическим поведением платформы. Две классические проблемы, вызывающие ступор у многих программистов: невозможность обратиться к одному вычисляемому полю из другого и хаотичная сортировка периодов (например, внезапное появление «Января» посреди третьего квартала) при связывании нескольких наборов данных.
В этой статье мы подробно, по шагам разберем причины возникновения этих проблем и проанализируем наиболее эффективные практические способы их решения.
Рассмотрим ситуацию, когда в нашем отчете задействованы два набора данных. Например, НаборДанных1 представляет собой таблицу с фактическими продажами, а НаборДанных2 — таблицу с плановыми показателями или информацией из периодического регистра сведений. Нам необходимо сопоставить эти данные и рассчитать отклонения.
Объединение таких таблиц настраивается на вкладке «Связи наборов данных». Важно помнить ключевую особенность СКД: связывание наборов данных выполняется на уровне процессора компоновки (в оперативной памяти сервера приложений 1С), а не на уровне СУБД. По сути, СКД выполняет аналог левого соединения (LEFT JOIN).
Это накладывает жесткие правила на логику сравнения полей в вычисляемых выражениях:
ВЫБОР КОГДА... для детальных (строчных) записей только тогда, когда связь между наборами гарантирует уникальность сопоставления (связь «один к одному» или «многие к одному»). В выражении вычисляемого поля это выглядит стандартно:
ВЫБОР
КОГДА Набор1.Продажи > Набор2.План
ТОГДА Набор1.Продажи - Набор2.План
ИНАЧЕ 0
КОНЕЦ
Сумма(Набор1.Продажи) - Сумма(Набор2.План)
Выясним причину популярной ошибки. Допустим, мы создали вычисляемое поле с путем к данным ВычисляемоеПоле1, в выражении которого написали формулу расчета промежуточного показателя. Далее мы создаем второе поле и хотим использовать в его логике результат первого поля:
ВЫБОР
КОГДА Продажи.Сумма > ВычисляемоеПоле1
ТОГДА "План перевыполнен"
ИНАЧЕ "План не выполнен"
КОНЕЦ
При попытке сформировать отчет система выдает ошибку: «Поле не найдено "ВычисляемоеПоле1"». Почему так происходит? Дело в том, что в выражениях вычисляемых полей СКД на уровне схемы запрещено обращаться к другим вычисляемым полям по их пути к данным. Платформа рассчитывает вычисляемые поля на одном этапе компоновки, и строгая очередность их вычисления не гарантируется.
Проанализируем пять проверенных способов обойти это ограничение.
Самый правильный с точки зрения производительности и чистоты архитектуры подход — отказаться от вычисляемых полей СКД и перенести всю логику на уровень СУБД. Для этого мы можем использовать возможности временных таблиц в запросе.
Рассмотрим пример. Допустим, нам нужно сначала вычислить промежуточную сумму со скидкой, а затем на ее основе присвоить категорию сделке. Вместо вычисляемых полей СКД напишем пакетный запрос:
ВЫБРАТЬ
Продажи.Контрагент КАК Контрагент,
Продажи.Номенклатура КАК Номенклатура,
ВЫБОР
КОГДА Продажи.Сумма > 100000 ТОГДА Продажи.Сумма * 0.9
ИНАЧЕ Продажи.Сумма
КОНЕЦ КАК СуммаСоСкидкой
ПОМЕСТИТЬ ВТ_ПредварительныйРасчет
ИЗ
РегистрНакопления.Продажи КАК Продажи
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ.Контрагент,
ВТ.Номенклатура,
ВТ.СуммаСоСкидкой,
ВЫБОР
КОГДА ВТ.СуммаСоСкидкой > 50000 ТОГДА "Крупная сделка"
ИНАЧЕ "Стандартная сделка"
КОНЕЦ КАК КатегорияСделки
ИЗ
ВТ_ПредварительныйРасчет КАК ВТ
В этом случае СКД получит уже готовые, предрассчитанные на сервере базы данных поля СуммаСоСкидкой и КатегорияСделки. Это существенно повысит скорость работы отчета на больших объемах данных.
Если логика вычислений простая, а переписывать схему запросов нецелесообразно, можно прибегнуть к простому копированию формул. Вместо обращения к псевдониму первого поля мы полностью копируем его выражение внутрь второго:
// Выражение для ВычисляемоеПоле1:
ВЫБОР КОГДА Сумма > 0 ТОГДА Сумма / 4 ИНАЧЕ Сумма + 4 КОНЕЦ
// Выражение для ВычисляемоеПоле2 (где нам нужно было проверить равенство ВычисляемоеПоле1 числу 4):
ВЫБОР
КОГДА (ВЫБОР КОГДА Сумма > 0 ТОГДА Сумма / 4 ИНАЧЕ Сумма + 4 КОНЕЦ) = 4
ТОГДА "Минимум"
ИНАЧЕ "Обычное"
КОНЕЦ
Важное предупреждение: этот способ прост в реализации, но порождает технический долг. Если в будущем изменится формула расчета первого поля, вам придется вручную отслеживать и менять ее во всех зависимых полях.
Ограничение на использование путей к данным вычисляемых полей не распространяется на ресурсы. Если вам не нужно выводить признак в детальных записях, а расчет требуется только для группировок (итогов), вы можете свободно писать формулы на вкладке ресурсов:
Сумма(ВычисляемоеПоле1) * Среднее(ВычисляемоеПоле2)
СКД сначала вычислит значения детальных записей для каждого вычисляемого поля, а затем корректно произведет расчет итоговой формулы ресурса.
В СКД есть разделение между вычисляемыми полями схемы и пользовательскими полями настроек. Если вы создадите Пользовательское поле-выражение на вкладке «Настройки» компоновщика данных, в его выражении вы сможете обращаться к любым вычисляемым полям схемы. Это легитимный механизм платформы, разработанный для конечных пользователей и разработчиков.
Если формула получается громоздкой, вы можете вынести расчет на уровень кода 1С, создав экспортную функцию в общем модуле. Общий модуль должен иметь свойства «Сервер», «Вызов сервера» и «Клиент» (в зависимости от архитектуры выполнения отчета).
В вычисляемом поле СКД выражение примет лаконичный вид:
ИмяОбщегоМодуля.РассчитатьПоказатель(Сумма, Количество)
В коде модуля функция будет выглядеть стандартно:
Функция РассчитатьПоказатель(Сумма, Количество) Экспорт
Если Количество = 0 Тогда
Возврат 0;
КонецЕсли;
ПромежуточныйРезультат = Сумма / Количество;
// Любая сложная зависимая логика
Если ПромежуточныйРезультат > 100 Тогда
Возврат ПромежуточныйРезультат * 1.2;
Иначе
Возврат ПромежуточныйРезультат;
КонецЕсли;
КонецФункции
Обратите внимание: частые вызовы функций общих модулей для каждой строки детальных записей могут замедлить формирование отчета. Используйте этот метод обдуманно.
Перейдем ко второму критическому вопросу: почему при связи двух наборов данных в структуре колонок отчета (например, по Кварталам и Месяцам) в третьем квартале внезапно появляется «Январь», а реальные месяцы квартала сортируются некорректно?
Проанализируем три основные причины и методы их исправления.
Поскольку СКД связывает наборы данных по принципу LEFT JOIN, для записей третьего квартала (например, за июль) во втором наборе данных может просто не оказаться информации. В результате поле даты из второго набора вернет значение NULL.
Процессор компоновки данных при попытке вывести или сгруппировать значение NULL преобразует его в пустую дату — 01.01.0001 00:00:00. А пустая дата при форматировании в формат представления месяца выдает строку «Январь».
Решение: В запросах наборов данных всегда обрабатывайте потенциальные NULL с помощью функции ЕСТЬNULL. Задавайте базовые даты из основного (всегда заполненного) набора данных:
ЕСТЬNULL(Продажи.Период, ДАТАВРЕМЯ(1, 1, 1))
Если СКД выполняет автоматическое дополнение периодов, ей нужно явно указать логическую подчиненность дат. Без этого система не поймет, к какому кварталу относится конкретный месяц.
Разберем по шагам правильную настройку ролей полей на вкладке «Наборы данных»:
Месяц).Квартал.Квартал родителем укажите поле Год.После этого СКД будет четко понимать структуру временной шкалы и распределять месяцы строго по своим кварталам. Для наглядного изучения настройки иерархических колонок пригодится пример формирования шапки отчета по неделям и месяцам в СКД.
Часто разработчики получают название месяца в виде текстовой строки (например, «Январь», «Февраль») непосредственно в запросе или через вычисляемое поле. Если сгруппировать колонки по такому строковому полю, СКД применит к ним стандартную алфавитную сортировку. В результате «Август» отобразится раньше «Июля», а «Январь» встанет первым независимо от квартала.
Решение:
Дата (например, приведенному к началу месяца с помощью функции НАЧАЛОПЕРИОДА(Период, МЕСЯЦ)).ДФ='MMMM yyyy' (для вывода названия месяца и года) или ДФ='MMMM' (только название месяца).При такой настройке СКД выполнит сортировку колонок хронологически (по значению даты), а пользователю покажет красивые и понятные строковые названия.
Мы детально рассмотрели внутренние механизмы СКД при работе со сложными структурами данных. Понимание того, как платформа обрабатывает вычисляемые поля, связывает наборы данных в памяти и упорядочивает временные периоды, убережет вас от множества ошибок. Проектируйте запросы оптимально, переносите сложные вычисления на уровень СУБД, правильно расставляйте роли полей дат — и ваши отчеты будут работать быстро и корректно!