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