Как отобразить сгруппированные данные в Дереве значений без дублирования в дочерних строках?

Программист 1С v8.3 (Управляемые формы)
← На главную

При работе с управляемыми формами в 1С часто возникает задача вывести данные из запроса с итогами в элемент формы типа ДеревоЗначений. Стандартная выгрузка результата запроса с группировками приводит к тому, что значение группировочного поля повторяется в каждой дочерней строке. Это выглядит неаккуратно и затрудняет чтение данных. Рассмотрим, как добиться классического древовидного представления, где значение группировки отображается только один раз в родительской строке.

Исходная ситуация: у нас есть запрос с итогами, который мы выгружаем на форму одной строкой кода.


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|    Док1.Справочник1 КАК Справочник1,
|    Док1.Справочник2 КАК Справочник2,
|    Док1.Количество КАК Количество,
|    Док1.Цена КАК Цена,
|    Док1.Сумма КАК Сумма
|ИЗ
|    Документ.Док1.ТабЧасть КАК Док1
|ГДЕ
|    Док1.Ссылка = &Ссылка
|ИТОГИ
|    СУММА(Количество),
|    СУММА(Сумма)
|ПО
|    Справочник1";

Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);
ДанныеДляДерева = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
ЗначениеВРеквизитФормы(ДанныеДляДерева, "ДеревоНаФорме");

В результате поле Справочник1 заполнено и для группировочных, и для детальных строк. Наша цель — очистить это поле в дочерних строках для наглядности.

Решение 1: Программное заполнение дерева через обход результата запроса

Самый гибкий и контролируемый способ — это отказаться от прямой выгрузки через ЗначениеВРеквизитФормы и заполнить дерево на форме вручную, перебирая результат запроса в цикле. Это позволяет полностью управлять структурой и данными на каждом уровне иерархии.

Вариант А: Постобработка выгруженного дерева

Сначала выгружаем данные как есть, а затем вторым циклом "очищаем" лишние значения в дочерних строках. Этот метод прост для понимания, но может быть неоптимальным на больших объемах данных из-за двойной обработки.

Пошаговый алгоритм:

  1. Выполняем запрос и выгружаем результат с группировками в переменную типа ДеревоЗначений.
  2. Перебираем строки верхнего уровня (группировки).
  3. Для каждой группировки перебираем ее дочерние строки (детальные записи).
  4. Внутри второго цикла устанавливаем значение группировочного поля (в нашем случае Справочник1) в Неопределено.
  5. Передаем измененное дерево на форму.

Пример кода:


// Выполняем запрос и выгружаем с группировками (без иерархии)
ДанныеДляДерева = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);

// Перебираем полученное дерево и очищаем значения в детальных строках
Для каждого Группировка Из ДанныеДляДерева.Строки Цикл
    Для каждого ДетальнаяЗапись Из Группировка.Строки Цикл
        ДетальнаяЗапись.Справочник1 = Неопределено;
    КонецЦикла;
КонецЦикла;

// Отправляем готовое дерево в реквизит формы
ЗначениеВРеквизитФормы(ДанныеДляДерева, "ДеревоНаФорме");

Вариант Б: Формирование дерева "с нуля"

Этот подход более производительный, так как мы не создаем промежуточных структур данных, а сразу формируем итоговое дерево в нужном виде, обходя выборку из результата запроса.

Пошаговый алгоритм:

  1. Получаем реквизит формы в переменную типа ДеревоЗначений и очищаем его.
  2. Выполняем запрос и начинаем обход результата в режиме ПоГруппировкам.
  3. Для каждой группировки из выборки создаем новую строку в корне нашего дерева и заполняем ее свойства с помощью ЗаполнитьЗначенияСвойств.
  4. Получаем вложенную выборку детальных записей для текущей группировки.
  5. Обходим детальные записи. Для каждой записи создаем дочернюю строку у текущей группировки.
  6. Заполняем свойства дочерней строки, исключая группировочное поле (Справочник1). Это ключевой момент данного метода.
  7. После завершения всех циклов передаем заполненное дерево обратно на форму.

Пример кода:


// Получаем дерево с формы и очищаем его
ДанныеДляДерева = РеквизитФормыВЗначение("ДеревоНаФорме");
ДанныеДляДерева.Строки.Очистить();

// Выполняем запрос и получаем выборку по группировкам
ВыборкаПоГруппировкам = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаПоГруппировкам.Следующий() Цикл
    // Добавляем строку верхнего уровня (группу)
    ГруппаДерева = ДанныеДляДерева.Строки.Добавить();
    ЗаполнитьЗначенияСвойств(ГруппаДерева, ВыборкаПоГруппировкам);
    
    // Получаем выборку вложенных детальных записей
    ВыборкаДетальныеЗаписи = ВыборкаПоГруппировкам.Выбрать();
    
    Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
        // Добавляем дочернюю строку
        ДетальнаяЗапись = ГруппаДерева.Строки.Добавить();
        // Заполняем ее свойства, пропуская поле "Справочник1"
        ЗаполнитьЗначенияСвойств(ДетальнаяЗапись, ВыборкаДетальныеЗаписи, , "Справочник1");
    КонецЦикла;
КонецЦикла;

// Возвращаем заполненное дерево на форму
ЗначениеВРеквизитФормы(ДанныеДляДерева, "ДеревоНаФорме");

Решение 2: Формирование данных в запросе

Наиболее элегантный и производительный способ — подготовить данные в нужном виде еще на этапе выполнения запроса. Таким образом, мы избегаем циклов в коде 1С, перекладывая всю работу по формированию иерархии на СУБД. Для этого используются конструкции ВЫБОР КОГДА и ЕСТЬNULL в секции ИТОГИ.

Идея в том, что для итоговых (группировочных) строк поля, которые не агрегируются и не входят в группировку, имеют значение NULL. Мы можем использовать это условие, чтобы подставлять разные значения для разных уровней иерархии.

Пример сложного запроса:

В этом примере мы объединяем два поля (Заказ и Реализация) в одно поле ДокументТовар. Для строки группировки по заказу в это поле попадет сам заказ, а для детальной строки — документ реализации.


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|    ВтЗаказы.Заказ КАК Заказ,
|    РеализацияТоваровУслугТовары.Ссылка КАК Реализация,
|    РеализацияТоваровУслугТовары.Цена КАК Цена,
|    РеализацияТоваровУслугТовары.Ссылка.Менеджер КАК Менеджер,
|    РеализацияТоваровУслугТовары.Количество КАК Количество,
|    РеализацияТоваровУслугТовары.Номенклатура КАК Номенклатура,
|    РеализацияТоваровУслугТовары.Сумма КАК Сумма
|ИЗ
|    // ... (здесь может быть временная таблица ВтЗаказы или прямое обращение)
|ИТОГИ
|    СУММА(Количество),
|    СУММА(Сумма),
|    // Ключевая конструкция: если Реализация = NULL (это группировка по Заказу),
|    // то в поле ДокументТовар подставляем Заказ, иначе - Реализацию.
|    ЕСТЬNULL(Реализация, Заказ) КАК ДокументТовар,
|    ВЫБОР
|        КОГДА Реализация ЕСТЬ NULL
|            ТОГДА ЗНАЧЕНИЕ(Справочник.Пользователи.ПустаяСсылка)
|        ИНАЧЕ МАКСИМУМ(Менеджер)
|    КОНЕЦ КАК Менеджер
|ПО
|    Заказ,
|    Реализация";

// После выполнения такого запроса результат можно сразу выгружать на форму
Результат = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);
ЗначениеВРеквизитФормы(Результат, "Дерево");

Этот метод требует более глубокого понимания языка запросов, но является самым эффективным. Для интерактивной отладки запросов с секцией итогов и проверки постобработки данных можно использовать консоль запросов для управляемых форм.

Дополнительная задача: Как растянуть название группы на всю ширину строки?

Часто требуется, чтобы длинное наименование в группировочной строке не обрезалось границей колонки, а занимало все доступное пространство. Стандартное дерево значений не поддерживает объединение ячеек, но этого эффекта можно достичь с помощью условного оформления.

Алгоритм решения:

  1. Добавьте в дерево значений (и в реквизит формы) служебную колонку, например, ЭтоГруппа с типом Булево.
  2. При программном заполнении дерева (как в Решении 1) устанавливайте для группировочных строк ЭтоГруппа = Истина, а для дочерних — Ложь.
  3. Настройте условное оформление для элемента формы "ДеревоНаФорме":
    • Условие: ДеревоНаФорме.ЭтоГруппа = Истина
    • Оформление: Для всех колонок, кроме первой (Справочник1), установите свойство Отображать в значение Ложь. Можно также дополнительно выделить группировку, например, жирным шрифтом.

В результате для строк, где ЭтоГруппа = Истина, все ячейки, кроме первой, будут скрыты, и текст из первой ячейки визуально займет всю ширину строки. Для быстрого переноса настроенных правил в код подойдёт обработка для декомпиляции условного оформления формы.

Частые ошибки и их решения

В процессе работы с деревом значений можно столкнуться с типичными ошибками. Разберем две из них.

1. Ошибка "Несоответствие типов" при добавлении строки

Проблема: При выполнении кода Группировка = ДанныеДляДерева.Строки.Добавить() возникает ошибка "Несоответствие типов".

Причина: Чаще всего это происходит, когда имя переменной (в данном случае Группировка) совпадает с зарезервированным словом системы или именем реквизита самой формы. Система пытается интерпретировать это имя не как переменную, а как объект другого типа, что и вызывает конфликт.

Решение: Используйте уникальные имена для переменных. Например, переименуйте Группировка в НоваяСтрокаГруппы или ГруппаДерева. Это простое действие немедленно решает проблему.

2. Ошибка "Недопустимое значение параметра" при вызове `ЗначениеВРеквизитФормы`

Проблема: Выполнение ЗначениеВРеквизитФормы(ДанныеДляДерева, "ДеревоНаФорме") завершается ошибкой "Ошибка при вызове метода контекста (ЗначениеВРеквизитФормы) по причине: Недопустимое значение параметра (параметр номер '2')".

Причина: Эта ошибка однозначно указывает на несоответствие структуры дерева-источника (ДанныеДляДерева) и дерева-приемника (реквизита формы ДеревоНаФорме).

Решение: Тщательно проверьте:

Самый частый случай — опечатка в имени колонки или использование разных псевдонимов в запросе и имен в реквизите формы.

← На главную