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