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