К статье
Практический материал

Разработка и Конфигурация · 14 мин

1С: как свернуть дерево значений

Как свернуть дерево значений в 1С: метод Свернуть, сворачивание по колонкам, суммирование показателей, типичные ошибки и готовые примеры кода на встроенном языке.

Дерево значений в 1С — это иерархическая коллекция, в которой строки могут иметь подчинённые строки произвольной глубины. На практике разработчики часто сталкиваются с задачей «свернуть» такое дерево: либо агрегировать данные по группировочным колонкам с суммированием показателей, либо свернуть иерархию до плоской таблицы, либо программно свернуть развёрнутые ветки в элементе формы. Это три разные задачи, и у каждой своё решение.

Ключевой инструмент — метод Свернуть(), который есть и у дерева значений, и у таблицы значений. Он группирует строки по указанным колонкам и суммирует числовые показатели. Однако у дерева значений поведение метода имеет особенности: сворачивание выполняется только на верхнем уровне иерархии, а подчинённые строки при этом теряются. Понимание этого нюанса избавляет от потери данных и долгой отладки.

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

Как работает метод Свернуть() у дерева значений

Метод Свернуть(КолонкиГруппировки, КолонкиСуммирования) группирует строки верхнего уровня дерева по значениям указанных колонок. Строки с одинаковыми значениями группировочных колонок объединяются в одну, а значения суммируемых колонок складываются. Оба параметра — строки с именами колонок через запятую; второй параметр можно опустить, тогда строки просто сгруппируются без суммирования.

Главная особенность: метод обрабатывает только корневые строки. Все подчинённые строки (дочерние ветки любой глубины) при сворачивании удаляются без суммирования в родительскую строку. Если дерево хранило детализацию — например, товары внутри групп номенклатуры — после вызова Свернуть() эта детализация будет потеряна, а суммы дочерних строк не попадут в итог автоматически.

// Дерево с колонками: Номенклатура, Склад, Количество, Сумма

// Сворачиваем по номенклатуре, суммируя количество и сумму

Дерево.Свернуть("Номенклатура", "Количество,Сумма");

После выполнения в дереве останется по одной строке на каждое уникальное значение номенклатуры. Колонки, не входящие ни в группировку, ни в суммирование (в примере — «Склад»), будут удалены из структуры дерева. Это важно учитывать, если колонка нужна дальше по логике программы.

Сворачивание с сохранением иерархии

Если нужно сгруппировать данные, но сохранить древовидную структуру, стандартный вызов Свернуть() не подходит. Рабочий приём — обойти дерево рекурсивно и сворачивать строки каждого уровня отдельно, либо предварительно просуммировать дочерние строки в родительские.

Типовой алгоритм выглядит так: сначала рекурсивно поднимаются суммы с нижних уровней наверх (каждая родительская строка получает сумму показателей всех потомков), затем при необходимости нижние уровни удаляются, а верхний сворачивается штатным методом. Такой подход используется, например, при подготовке данных отчёта «группа — итог», где детализация не нужна.

Процедура ПоднятьСуммы(СтрокиДерева)

Для Каждого Строка Из СтрокиДерева Цикл

ПоднятьСуммы(Строка.Строки); // сначала обрабатываем потомков

Для Каждого Подстрока Из Строка.Строки Цикл

Строка.Количество = Строка.Количество + Подстрока.Количество;

Строка.Сумма = Строка.Сумма + Подстрока.Сумма;

КонецЦикла;

КонецЦикла;

КонецПроцедуры

// Вызов для всего дерева:

ПоднятьСуммы(Дерево.Строки);

Дерево.Свернуть("Номенклатура", "Количество,Сумма");

Обратите внимание: если родительские строки уже содержат собственные значения показателей (а не только служат группами), простое прибавление дочерних сумм приведёт к задвоению. В таком случае перед подъёмом сумм обнулите показатели родительских строк или храните детальные данные только на нижнем уровне.

Преобразование дерева в таблицу значений и свертка

Часто удобнее не бороться с иерархией, а «развернуть» дерево в плоскую таблицу значений, свернуть её штатным методом и при необходимости собрать дерево обратно. Это особенно удобно, когда группировка нужна по колонкам, значения которых находятся на разных уровнях дерева.

Обход дерева выполняется рекурсией: для каждой строки в таблицу добавляется новая строка, в неё копируются значения колонок (при необходимости — с указанием родителя и уровня), затем обрабатываются подчинённые строки.

Функция ДеревоВТаблицу(СтрокиДерева, Таблица, Родитель = Неопределено)

Для Каждого Строка Из СтрокиДерева Цикл

НоваяСтрока = Таблица.Добавить();

ЗаполнитьЗначенияСвойств(НоваяСтрока, Строка);

НоваяСтрока.Родитель = Родитель;

ДеревоВТаблицу(Строка.Строки, Таблица, Строка.Номенклатура);

КонецЦикла;

Возврат Таблица;

КонецФункции

После преобразования с таблицей работают привычными методами: Свернуть(), Сортировать(), НайтиСтроки(). Чтобы собрать дерево обратно, строки таблицы группируют по родителю и рекурсивно добавляют в дерево. Этот маршрут длиннее, но даёт полный контроль над тем, какие данные попадают в итог.

Маршрут решения

Как выбрать подход

Иерархия не нужна, важны только итоги по группамВызовите Дерево.Свернуть() напрямую — это самый короткий путь
Нужно сохранить иерархию и получить итоги на каждом уровнеРекурсивно поднимите суммы снизу вверх, затем сверните нужные уровни
Группировка идёт по данным с разных уровней дереваПреобразуйте дерево в таблицу значений, сверните её и при необходимости соберите дерево обратно
Нужно просто свернуть ветки визуально на формеИспользуйте метод Свернуть() элемента формы, данные при этом не меняются

Программное сворачивание строк дерева на форме

Отдельная задача — свернуть развёрнутые ветки дерева, отображаемого в табличном поле формы, не меняя сами данные. Здесь метод Свернуть() вызывается уже не у объекта ДеревоЗначений, а у элемента формы (таблицы формы), и принимает он идентификатор строки, а не колонки.

Чтобы свернуть всё дерево целиком, перебирают строки верхнего уровня и для каждой вызывают сворачивание. Для разворачивания используется парный метод Развернуть(), который умеет работать и с подчинёнными уровнями через параметр.

// Обычные формы

Для Каждого Строка Из Дерево.Строки Цикл

ЭлементыФормы.Дерево.Свернуть(Строка);

КонецЦикла;

// Управляемые формы

Для Каждого Строка Из Дерево.ПолучитьЭлементы() Цикл

Элементы.Дерево.Свернуть(Строка.ПолучитьИдентификатор());

КонецЦикла;

Разница между платформами принципиальна: в обычных формах метод принимает саму строку дерева, в управляемых — идентификатор строки реквизита формы, получаемый через ПолучитьИдентификатор(). Перепутав эти варианты, вы получите ошибку «Недопустимое значение параметра». Также учитывайте, что на управляемой форме сворачивание выполняется на клиенте — код должен располагаться в клиентской процедуре.

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

Типичные ошибки при сворачивании

Самая частая ошибка — ожидание, что Свернуть() агрегирует всё дерево целиком с учётом подчинённых уровней. На деле метод обрабатывает только верхний уровень, а вложенные строки удаляет. Итоги, рассчитанные по такому дереву, оказываются заниженными или пустыми, и причина не всегда очевидна.

Вторая группа ошибок связана с колонками: попытка суммировать нечисловую колонку, опечатка в имени колонки (метод молча не выполнится корректно или выдаст ошибку «Колонка не найдена»), а также неожиданное удаление колонок, не перечисленных в параметрах. Если после сворачивания код обращается к удалённой колонке, возникает ошибка обращения к полю.

Третья типичная ситуация — сворачивание дерева, связанного с элементом формы, когда изменения данных не отражаются на экране или, наоборот, сворачивание веток на форме ошибочно принимают за агрегирование данных. Данные и их визуальное представление — разные вещи: Свернуть() у дерева меняет данные, Свернуть() у элемента формы — только отображение.

Типичные ошибки
01
Вызов Свернуть() у дерева с многоуровневой иерархией — подчинённые строки удаляются, их суммы теряются
02
Суммирование колонки со строковым типом или значениями Неопределено — ошибка или неверный итог
03
Обращение к колонке, не указанной в параметрах сворачивания — она удалена из структуры дерева
04
Передача строки дерева вместо идентификатора в Свернуть() на управляемой форме — ошибка параметра
05
Сворачивание исходного дерева вместо копии — потеря детальных данных без возможности восстановления

Проверка результата после сворачивания

После любого сворачивания данных полезно выполнить контрольную проверку. Самый надёжный способ — сравнить итоговую сумму показателя до и после операции. Для дерева итог считается обходом всех уровней (или берётся из заранее рассчитанной суммы), для таблицы — методом Итог("Сумма").

Дополнительно проверьте количество строк: после сворачивания их число должно равняться количеству уникальных комбинаций группировочных колонок. Если строк больше — вероятно, в группировочных колонках есть неявно различающиеся значения, например лишние пробелы, разный регистр символов или ссылки на разные объекты с одинаковым представлением.

СуммаДо = Дерево.Строки.Итог("Сумма", Истина); // Истина — с учётом подчинённых

Дерево.Свернуть("Номенклатура", "Сумма");

СуммаПосле = Дерево.Строки.Итог("Сумма");

Если СуммаДо <> СуммаПосле Тогда

Сообщить("Итоги не совпадают: " + СуммаДо + " / " + СуммаПосле);

КонецЕсли;

Обратите внимание на второй параметр метода Итог() у коллекции строк дерева: Истина означает суммирование с учётом всех подчинённых уровней. Именно этот приём позволяет быстро получить общий итог по дереву без написания рекурсивного обхода.

Контрольные точки

Как проверить результат

Итог по суммируемой колонке до и после свертки
Норма

Значения совпадают

Тревожный признак

Итог уменьшился — потеряны суммы подчинённых строк

Количество строк после сворачивания
Норма

Равно числу уникальных значений группировочных колонок

Тревожный признак

Строк больше — значения различаются скрытыми символами или ссылками

Состав колонок дерева
Норма

Остались только колонки группировки и суммирования

Тревожный признак

Код дальше обращается к удалённой колонке

Отображение на форме
Норма

Ветки свернуты, данные доступны при разворачивании

Тревожный признак

Строки пропали совсем — был изменён объект данных, а не представление

Альтернативы: свертка через запрос

Если дерево строится из результата запроса, часто рациональнее выполнить группировку на уровне самого запроса с помощью конструкции СГРУППИРОВАТЬ ПО и агрегатных функций СУММА(). Запрос с группировкой выполняется на сервере СУБД и обычно быстрее, чем загрузка детальных данных в дерево с последующим сворачиванием в памяти.

Запрос также умеет строить иерархические выборки через ИТОГИ ПО ... ИЕРАРХИЯ и обход результата с типом ПоГруппировкамСИерархией. В этом случае вы получаете готовое дерево с уже просуммированными итогами на каждом уровне — ручной подъём сумм не требуется. Это предпочтительный вариант для отчётов и больших объёмов данных.

Сравнение способов свертки

Метод Свернуть() дерева

Быстро и просто, но только верхний уровень и с потерей подчинённых строк. Подходит для плоских итогов

Рекурсивный подъём сумм + Свернуть()

Сохраняет контроль над иерархией, но требует аккуратности с задвоением сумм

Дерево в таблицу значений + Свернуть()

Гибкая группировка по любым колонкам с разных уровней, но больше кода и памяти

Группировка в запросе

Самый производительный вариант для больших данных, но требует перестройки источника данных

Выбор зависит от объёма данных и того, откуда они берутся. Для небольшого дерева, уже существующего в памяти, достаточно штатного метода. Для отчётов по регистрам накопления почти всегда выгоднее сгруппировать данные запросом ещё до построения дерева.

Итог

Что важно запомнить

Метод Свернуть() у дерева значений группирует только строки верхнего уровня и удаляет подчинённые строки и лишние колонки. Для сохранения иерархии поднимайте суммы рекурсией или работайте через таблицу значений. Сворачивание веток на форме — отдельная операция через элемент формы, не меняющая данные. Всегда сверяйте итоги до и после свертки.

Частые вопросы

Почему после Свернуть() пропали все вложенные строки?

Метод Свернуть() у дерева значений работает только со строками верхнего уровня и не переносит подчинённые строки в результат — они удаляются вместе со своими данными. Если вложенность нужна, сначала просуммируйте дочерние строки в родительские рекурсивным обходом или преобразуйте дерево в таблицу значений.

Можно ли свернуть дерево по нескольким колонкам одновременно?

Да. В первом параметре метода перечислите имена колонок через запятую: Дерево.Свернуть("Склад,Номенклатура", "Количество"). Строки будут сгруппированы по каждой уникальной комбинации значений этих колонок.

Как свернуть все ветки дерева на управляемой форме одной командой?

Отдельной команды «свернуть всё» нет. Нужно в клиентской процедуре перебрать строки верхнего уровня через Дерево.ПолучитьЭлементы() и для каждой вызвать Элементы.Дерево.Свернуть(Строка.ПолучитьИдентификатор()). Вложенные уровни свернутся вместе с родительской веткой.

Чем Свернуть() отличается от Итог() у дерева значений?

Итог() только вычисляет сумму по колонке (с параметром Истина — с учётом всех уровней) и не изменяет дерево. Свернуть() изменяет структуру: объединяет строки, складывает показатели и удаляет лишние колонки и подчинённые строки.

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

Группировка идёт по точному совпадению значений. Строки с лишними пробелами, разным регистром букв или ссылками на разные объекты с одинаковым наименованием считаются разными. Очистите значения от пробелов функцией СокрЛП() до сворачивания или группируйте по ссылке, а не по представлению.

Что быстрее: свернуть дерево в памяти или сгруппировать данные запросом?

Для больших объёмов быстрее группировка в запросе — она выполняется на сервере СУБД и не требует передачи детальных строк в память приложения. Сворачивание дерева в памяти оправдано, когда данные уже загружены или получены не из запроса (например, сформированы программно).