Как записать дерево значений в регистр сведений или справочник и сохранить иерархию

Программист 1С v8.3 (Управляемые формы) Управленческий учет Промышленность, строительство и АПК
← На главную

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

В этой статье мы подробно разберем несколько проверенных методов реализации этой задачи: от простых табличных частей до профессиональных подходов, используемых в крупных системах уровня ERP.

Метод 1. Использование табличной части справочника

Если дерево описывает структуру конкретного объекта (например, состав одной конкретной «Матрешки»), самый логичный и простой путь — использовать подчиненную таблицу. Рассмотрим этот вариант подробнее.

Для реализации нам потребуется создать в справочнике Номенклатура табличную часть Состав со следующими реквизитами:

  1. Номенклатура (тип СправочникСсылка.Номенклатура) — сам компонент.
  2. Количество (тип Число).
  3. КлючСвязи (тип Число или УникальныйИдентификатор) — идентификатор текущей строки.
  4. КлючРодителя (тип Число или УникальныйИдентификатор) — ссылка на КлючСвязи родительской строки.

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

Метод 2. Хранение в регистре сведений (связь Родитель — Потомок)

Когда структура может быть очень глубокой или когда одни и те же элементы могут входить в разные узлы, лучше использовать Регистр сведений. Проанализируем, как спроектировать такой регистр.

Создадим непериодический регистр сведений СоставИзделий со следующей структурой:

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


Процедура ЗаписатьВеткиДерева(ТекущаяСтрока, ВладелецСсылка, РодительСсылка)
    Для Каждого СтрокаИзДерева Из ТекущаяСтрока.Строки Цикл
        МенеджерЗаписи = РегистрыСведений.СоставИзделий.СоздатьМенеджерЗаписи();
        МенеджерЗаписи.Владелец = ВладелецСсылка;
        МенеджерЗаписи.Узел = СтрокаИзДерева.Номенклатура;
        МенеджерЗаписи.РодительУзла = РодительСсылка;
        МенеджерЗаписи.Количество = СтрокаИзДерева.Количество;
        МенеджерЗаписи.Записать();
        
        // Рекурсивный вызов для вложенных веток
        ЗаписатьВеткиДерева(СтрокаИзДерева, ВладелецСсылка, СтрокаИзДерева.Номенклатура);
    КонецЦикла;
КонецПроцедуры

Метод 3. Метод «Транзитивное замыкание» (как в 1С:ERP)

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

Суть метода: в регистр записываются не только прямые связи (папа-сын), но и связи через уровни (дедушка-внук). Если у нас есть цепь А -> Б -> В, мы записываем строки:

  1. А — родитель Б (дистанция 1)
  2. Б — родитель В (дистанция 1)
  3. А — родитель В (дистанция 2)

Это позволяет одним простым запросом ГДЕ Родитель = &НужноеИзделие мгновенно получить весь состав на любую глубину. Мы рекомендуем использовать этот метод, если планируются частые отчеты по составам изделий.

Метод 4. Сериализация в JSON (для простого хранения)

Если вам не нужно анализировать состав изделия в запросах или отчетах, а дерево требуется только для отображения в форме (например, настройки пользователя), можно воспользоваться механизмом JSON. Выясним, как это сделать максимально быстро.

Начиная с версии платформы 8.3.6, мы можем превратить дерево в строку одной командой:


// Сериализация дерева в строку JSON
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, Объект.ДеревоСостава);
СтрокаДляХранения = ЗаписьJSON.Закрыть();

// Эту строку можно сохранить в реквизит справочника с типом "Строка (неограниченная)" 
// или в Регистр Сведений в ресурс с типом "ХранилищеЗначения"

Важные технические нюансы при реализации

При работе с древовидными структурами в плоских таблицах 1С важно учитывать два критических фактора, о которых часто забывают начинающие разработчики:

1. Порядок следования веток. В регистре сведений записи могут перемешаться при перестроении индексов. Чтобы «Штуцер» не оказался выше «Корпуса», если в спецификации задан обратный порядок, обязательно добавляйте числовой реквизит НомерСтроки или Порядок. Это позволит восстановить дерево в исходном визуальном виде.

2. Контроль зацикливания. Рассмотрим ситуацию, когда по ошибке пользователь указал, что «Деталь 1» состоит из «Детали 2», а «Деталь 2» — из «Детали 1». При попытке рекурсивного обхода такого дерева программа уйдет в бесконечный цикл и «вылетит» с ошибкой переполнения стека. Мы настоятельно рекомендуем перед записью в регистр проверять, не является ли новый родитель потомком для самого себя.

Для проверки зацикливания можно использовать следующий алгоритм:


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

Резюме

Выбор способа хранения зависит от ваших задач:

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

← На главную