При разработке интерфейсов на обычных формах программисты часто сталкиваются с задачей отображения иерархических структур внутри документов. Стандартная табличная часть (ТЧ) представляет собой «плоский» список, в то время как пользователю удобно работать с Деревом значений (ДЗ). Основная сложность заключается в том, как сохранить эту иерархию в базе данных так, чтобы при добавлении, перемещении или удалении строк порядок не нарушался, а в отчетах данные выводились в правильной последовательности.
В этой статье мы разберем несколько эффективных методов организации хранения иерархии, проанализируем программные способы формирования индексов сортировки и выясним, как минимизировать трудозатраты системы при перенумерации строк.
Этот метод был выбран автором темы как основной. Его суть заключается в создании служебного реквизита (например, Порядок), который хранит полный путь к узлу в виде строки с фиксированной длиной сегментов. Например: 001.002.000, где первые три цифры — индекс первого уровня, следующие три — второго и так далее.
Рассмотрим преимущества данного подхода:
Посмотрим на пример реализации функции, которая формирует такой индекс. Мы будем использовать объект ДеревоЗначений и текущую строку для вычисления ее нового порядка. Разберем по шагам логику, предложенную автором:
Функция СформироватьПорядок(Дерево, ТекущаяСтрока) Экспорт
// Определяем формат сегмента (например, 3 знака с ведущими нулями)
ФорматЧисла = "ЧЦ=3; ЧВН=";
Если ТекущаяСтрока.Уровень() = 0 Тогда
// Для верхнего уровня берем индекс строки в корне дерева
ИндексСтроки = Дерево.Строки.Индекс(ТекущаяСтрока) + 1;
Порядок = Формат(ИндексСтроки, ФорматЧисла) + ".000.000";
ИначеЕсли ТекущаяСтрока.Уровень() = 1 Тогда
// Для второго уровня берем индекс родителя и индекс текущей строки у родителя
СтрокаРодитель = ТекущаяСтрока.Родитель;
// Разрезаем порядок родителя, чтобы получить его первый сегмент
ПорядокРодителя = СтрРазделить(СтрокаРодитель.Порядок, ".");
ИндексСтроки = СтрокаРодитель.Строки.Индекс(ТекущаяСтрока) + 1;
Порядок = ПорядокРодителя[0] + "." + Формат(ИндексСтроки, ФорматЧисла) + ".000";
ИначеЕсли ТекущаяСтрока.Уровень() = 2 Тогда
// Для третьего уровня собираем сегменты от родителя и добавляем свой
СтрокаРодитель = ТекущаяСтрока.Родитель;
ПорядокРодителя = СтрРазделить(СтрокаРодитель.Порядок, ".");
ИндексСтроки = СтрокаРодитель.Строки.Индекс(ТекущаяСтрока) + 1;
Порядок = ПорядокРодителя[0] + "." + ПорядокРодителя[1] + "." + Формат(ИндексСтроки, ФорматЧисла);
КонецЕсли;
Возврат Порядок;
КонецФункции
Важно отметить: при использовании этого метода необходимо обновлять индексы всех подчиненных строк при перемещении родителя или изменении его положения в списке. Однако при добавлении новой строки в конец ветки достаточно рассчитать индекс только для нее.
Если основной целью является последующий вывод данных в отчеты, мы можем использовать механизм Системы компоновки данных. В этом случае нам не обязательно хранить сложные строковые индексы. Достаточно добавить в табличную часть два реквизита: КлючСвязи (уникальный идентификатор строки, например, число или GUID) и КлючРодителя.
Проанализируем процесс настройки такого отчета:
Источник = КлючСвязи, Приемник = КлючРодителя.КлючРодителя пустой).СКД автоматически построит иерархическое дерево любой вложенности, независимо от физического порядка строк в документе. Это избавляет программиста от необходимости писать сложные функции пересчета индексов при каждом изменении документа.
Интересной альтернативой является использование числового поля Порядок с типом Число(15, 10). Вместо того чтобы пересчитывать индексы всех соседей при вставке строки между позициями 1 и 2, мы можем просто назначить новой строке индекс 1.5. Если нужно вставить строку между 1.1 и 1.2 — назначаем 1.15.
Рассмотрим плюсы и минусы:
Для высоконагруженных систем, где чтение иерархии происходит гораздо чаще, чем запись, применяется алгоритм Nested Sets. Каждой строке присваиваются два индекса: ЛевоеЗначение и ПравоеЗначение.
Выясним причину сложности этого метода: при добавлении всего одного узла в начало дерева приходится обновлять значения Lft и Rgt почти у всех строк документа. Однако этот метод позволяет получить все дерево или любую его ветку одним простейшим запросом без использования рекурсии ИТОГИ ПО или иерархических связей СКД.
Проанализируем техническое ограничение платформы 1С. В любой табличной части есть стандартное поле НомерСтроки. Платформа всегда стремится поддерживать его актуальность. Если ваша логическая сортировка по полю Порядок сильно отличается от физического порядка строк, это может привести к путанице при редактировании.
Рекомендуем придерживаться следующего алгоритма при сохранении документа:
ПередЗаписью) программно отсортируйте табличную часть по вашему служебному полю Порядок.ТабличнаяЧасть.Сортировать("Порядок").Таким образом, выбор метода зависит от ваших приоритетов: если важна простота реализации и наглядность — используйте строковый шаблон. Если важна гибкость отчетов и минимизация кода — выбирайте ключи связи.