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