Как эффективно представить и хранить дерево (XML) в базе данных 1С для работы с путями?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При интеграции систем часто возникает задача импорта и последующей обработки иерархических структур данных, таких как XML-документы. Если дерево имеет большой объем и к его элементам требуется выполнять регулярные, быстрые запросы (своего рода аналоги синтаксиса XPath), стандартные реляционные механизмы СУБД могут стать узким местом. В этой статье мы подробно рассмотрим, как правильно спроектировать хранение древовидных структур в экосистеме «1С:Предприятие 8», разберем плюсы и минусы различных архитектурных шаблонов и научимся выбирать оптимальный метод под конкретные бизнес-требования.

Вариант 1. Хранение XML «как есть» в Хранилище Значений с обработкой в памяти

Один из самых простых и надежных способов решения задачи, особенно если дерево XML не требует постоянного прямого изменения отдельных узлов на уровне базы данных, — это хранить весь файл целиком в виде бинарных данных. Для этого в платформе 1С предусмотрен специальный объект ХранилищеЗначения.

Суть метода: Мы не раскладываем структуру XML по таблицам СУБД, а записываем файл в реквизит справочника или регистра сведений с типом ХранилищеЗначения (при необходимости используя сжатие). Когда нам требуется произвести поиск или выборку данных, мы извлекаем объект из базы и обрабатываем его в оперативной памяти с помощью встроенных механизмов объектной модели документов (DOM).

Рассмотрим по шагам, как реализовать загрузку XML в базу данных:


// Запись XML-файла в базу данных
НовыйОбъект = Справочники.ДеревьяДанных.СоздатьЭлемент();
НовыйОбъект.Наименование = "Импорт структуры товаров";

// Читаем файл в двоичные данные
ДвоичныеДанныеФайла = Новый ДвоичныеДанные("C:\temp\structure.xml");

// Помещаем в ХранилищеЗначения с максимальным сжатием
НовыйОбъект.ДанныеXML = Новый ХранилищеЗначения(ДвоичныеДанныеФайла, Новый СжатиеДанных(9));
НовыйОбъект.Записать();

Теперь разберем, как прочитать эти данные и выполнить к ним полноценный аналог запроса XPath средствами встроенного языка 1С:


// Извлечение данных и поиск с помощью XPath
ДвоичныеДанные = СсылкаНаОбъект.ДанныеXML.Получить();
Если ДвоичныеДанные <> Неопределено Тогда
    ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xml");
    ДвоичныеДанные.Записать(ИмяВременногоФайла);
    
    // Инициализируем чтение XML и DOM-модель
    ЧтениеXML = Новый ЧтениеXML;
    ЧтениеXML.ОткрытьФайл(ИмяВременногоФайла);
    
    ПостроительDOM = Новый ПостроительDOM;
    ДокументDOM = ПостроительDOM.Прочитать(ЧтениеXML);
    ЧтениеXML.Закрыть();
    
    // Создаем выражение XPath для поиска всех узлов "Товар" с ценой выше 1000
    ВыражениеXPath = "//Товар[Цена > 1000]";
    РезультатXPath = ДокументDOM.ВычислитьВыражениеXPath(ВыражениеXPath, ДокументDOM);
    
    УзелDOM = РезультатXPath.ПолучитьСледующий();
    Пока УзелDOM <> Неопределено Цикл
        // Обрабатываем найденный узел
        ИмяТовара = УзелDOM.ПолучитьТекстовоеСодержимое();
        Сообщить("Найден товар: " + ИмяТовара);
        
        УзелDOM = РезультатXPath.ПолучитьСледующий();
    КонецЦикла;
    
    УдалитьФайлы(ИмяВременногоФайла);
КонецЕсли;

Преимущества подхода:

Недостатки:

Вариант 2. Паттерн «Материализованный путь» (Materialized Path)

Если перед вами стоит задача выполнять поиск элементов дерева именно через стандартные запросы 1С (для построения отчетов, объединения таблиц и т. д.), нам необходимо физически разложить XML по строкам базы данных. Одним из наиболее эффективных реляционных паттернов является Материализованный путь.

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

Представим структуру каталога:

Для элемента «Игровые» материализованный путь будет выглядеть как: /1/5/12/.

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

  1. Наименование — Строка
  2. Значение — Строка
  3. Путь — Строка (неограниченной или фиксированной длины, обязательно установить свойство Индексировать).

Проанализируем, как в этом случае будет выглядеть запрос на получение всех потомков элемента «Ноутбуки» (его путь /1/5/):


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	УзлыДерева.Ссылка КАК Ссылка,
    |	УзлыДерева.Наименование КАК Наименование,
    |	УзлыДерева.Путь КАК Путь
    |ИЗ
    |	Справочник.УзлыДерева КАК УзлыДерева
    |ГДЕ
    |	УзлыДерева.Путь ПОДОБНО &РодительскийПуть + ""%""";

Запрос.УстановитьПараметр("РодительскийПуть", "/1/5/");
РезультатЗапроса = Запрос.Выполнить().Выгрузить();

Использование индекса по реквизиту Путь делает конструкцию ПОДОБНО "...%" чрезвычайно быстрой в СУБД MS SQL Server и PostgreSQL, так как СУБД выполняет эффективный поиск по диапазону (Index Seek).

Вариант 3. Регистр связей «Таблица замыканий» (Closure Table)

Для сценариев, где структура дерева XML постоянно меняется (узлы переносятся, удаляются или вставляются новые), паттерн материализованного пути может быть неудобен из-за необходимости перезаписывать пути всех дочерних элементов. В этом случае на помощь приходит паттерн Closure Table, реализованный на плоском регистре сведений 1С.

Суть метода: Мы разделяем сами данные (справочник ЭлементыДерева) и связи между ними. Создается независимый регистр сведений СвязиУзлов со следующей структурой:

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

  1. Предок: Корень — Потомок: Корень (Дистанция: 0)
  2. Предок: Корень — Потомок: Группа (Дистанция: 1)
  3. Предок: Корень — Потомок: Элемент (Дистанция: 2)
  4. Предок: Группа — Потомок: Элемент (Дистанция: 1)

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	СвязиУзлов.Потомок КАК Элемент,
    |	СвязиУзлов.Дистанция КАК Дистанция,
    |	Элементы.Значение КАК Значение
    |ИЗ
    |	РегистрСведений.СвязиУзлов КАК СвязиУзлов
    |	ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ЭлементыДерева КАК Элементы
    |		ПО СвязиУзлов.Потомок = Элементы.Ссылка
    |ГДЕ
    |	СвязиУзлов.Предок = &Предок";

Запрос.УстановитьПараметр("Предок", СсылкаНаКорень);
Результат = Запрос.Выполнить().Выгрузить();

Этот запрос отработает мгновенно даже на миллионах записей, поскольку поиск ведется по первому измерению составного индекса регистра сведений (Предок).

Какое решение выбрать? Резюме для разработчика

Подводя итоги, сопоставим рассмотренные методы для различных практических задач:

  1. Нужна полная поддержка XPath и минимум дискового пространства? Используйте Вариант 1 (ХранилищеЗначения). Выгружайте данные в память и навигируйте по дереву через родную DOM-модель платформы 1С.
  2. Требуется быстрый поиск поддеревьев через стандартные SQL-запросы в 1С? Реализуйте паттерн «Материализованный путь» (Вариант 2). Строковое поле пути с индексацией идеально подходит для легкой фильтрации по условию ПОДОБНО.
  3. Дерево часто изменяется, элементы динамически перемещаются между ветками? Оптимальным выбором будет «Таблица замыканий» (Вариант 3). Хранение связей в плоском регистре сведений исключает блокировки при обновлении структуры и сохраняет высокую производительность запросов чтения.
← На главную