Вложенное множество (модель)

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску

Модель вложенного множества (англ. Nested set model) — это способ представления вложенных множеств[англ.] (известных как деревья или иерархии) в реляционных базах данных.

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

Альтернативным решением является выражение иерархии как отношения родитель-потомок. Селко назвал это списком смежности. Если иерархия может иметь произвольную глубину, то список смежности не допускает выражения операций, как сравнение содержимого иерархий двух элементов или определение того, находится ли элемент где-то в подиерархии другого элемента. Когда иерархия имеет фиксированную или ограниченную глубину, операции возможны, но дорогостоящие, из-за необходимости выполнения на каждом уровне одного реляционного соединения. Это часто называют проблемой спецификации материалов[1].

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

  • поддержка выделенного иерархического типа данных, такого как установка иерархического запроса SQL;
  • расширение реляционного языка с помощью манипуляции иерархической структуры, такой как, вложенная реляционная алгебра;
  • расширение реляционного языка с помощью транзитивного замыкания такого как SQL-оператор CONNECT, это позволяет использовать отношение родитель-потомок, но выполнение остается дорогостоящим;
  • запросы могут быть выражены на языке, который поддерживает итерации и обернут вокруг реляционных операций, таких как PL/SQL, T-SQL или язык программирования общего назначения.

Когда эти решения недоступны или неосуществимы, необходимо использовать другой подход.

Модель вложенного множества состоит в том, чтобы пронумеровать узлы в соответствии с обходом дерева, который дважды посещает каждый узел. При обоих посещениях назначаются номера в порядке посещения. Обход оставляет два числа для каждого узла, которые хранятся в виде двух атрибутов. Запрос становится недорогим: принадлежность к иерархии можно проверить, сравнив эти числа. Обновление узла требует перенумерации дерева и поэтому является дорогостоящим. Улучшения, в которых вместо целых чисел используются рациональные числа, помогут избежать перенумерации и поэтому узлы обновляются быстрее, хоть и намного сложнее[2].

В каталоге магазина одежда может быть классифицирована в соответствии с иерархией, приведённой слева:

Диаграмма Эйлера категорий одежды
Нумерация, назначаемая обходом дерева
№ п/п Узел Левый Правый
1 Одежда 1 22
2 Мужская 2 9
3 Женская 10 21
4 Костюмы 3 8
5 Брюки 4 5
6 Куртки 6 7
7 Платья 11 16
8 Юбки 17 18
9 Блузки 19 20
10 Вечерние платья 12 13
11 Сарафаны 14 15
Результат нумерации узлов

Категория «Одежда», занимающая самое высокое положение в иерархии, охватывает все подчиненные категории. Поэтому узлу задаются левый и правый ключ со значениями 1 и 22, причем последнее значение (22) является удвоенной величиной от общего числа представленных узлов (категорий). Следующий иерархический уровень содержит в себе «Мужской» и «Женский» уровни, которые должны быть учтены предыдущим уровнем. Узлу каждого уровня присваиваются значения левого и правого ключей в соответствии с количеством содержащихся в нём подуровней, как показано в данных таблицы.

Производительность

[править | править код]

Можно ожидать, что запросы, использующие вложенное множество, будут быстрее, чем запросы, использующие хранимую процедуру для обхода списка смежности, и поэтому являются более быстрым вариантом для баз данных, в которых отсутствуют встроенные рекурсивные конструкции запросов, таких как MySQL[3]. Однако, можно ожидать, что рекурсивные SQL-запросы будут выполняться сопоставимо для запросов «найти ближайших потомков» и гораздо быстрее для других запросов поиска в глубину, а также являются более быстрым вариантом для баз данных, которые их предоставляют, таких как PostgreSQL[4], Oracle[5] и Microsoft SQL Server[6].

Недостатки

[править | править код]

Случай использования динамически-бесконечной иерархии дерева баз данных встречается редко. Модель вложенного множества уместна там, где элемент дерева и один или два атрибута являются единственными данными. Модель будет плохим выбором, когда для элементов дерева существуют более сложные реляционные данные. Чтобы учесть произвольную начальную глубину для категории «Транспортные средства» и дочернего элемента «Автомобили» с дочерним элементом «Мерседес», должна быть установлена связь таблицы с помощью внешнего ключа, если только таблица дерева изначально не нормализована. Атрибуты вновь созданного элемента дерева не могут использовать все атрибуты совместно с родительским, дочерним или даже сестринским элементом. Если внешний ключ таблицы установлен для таблицы атрибутов «Растения», то целостность данных дочернего атрибута «Деревья» и его дочернего «Дуба» не предоставляется. Следовательно, в каждом случае для всех атрибутов элемента, вставленного в дерево, должен быть создан внешний ключ таблицы, кроме самых тривиальных вариантов использования.

Если предполагается, что дерево не будет часто меняться, то при первоначальном проектировании системы можно создать правильно нормализованную иерархию таблиц, что приведет к более простым и переносимым SQL-операторам, особенно тем, которые не требуют для изменений в дереве произвольного количества времени выполнения, программно созданных или удаленных таблиц. Для более сложных систем иерархия может быть разработана с помощью реляционных моделей, а не неявной числовой древовидной структуры. Глубина — это просто ещё один атрибут, а не основа для всей архитектуры БД. Как сказано в SQL Antipatterns[7]:

Модель вложенного множества это умное решение, может быть, даже слишком умное. Она также не поддерживает ссылочную целостность. Лучше всего используется, когда вам нужно запрашивать дерево чаще, чем его изменять[8].

Модель не допускает наличие нескольких родительских категорий. Например, «Дуб» может быть дочерним по отношению к «Типу дерева», но также и к «Типу древесины». Для этого необходимо установить дополнительную маркировку или таксономию, что снова приведет к проектировании более сложной структуры, чем простая фиксированная модель.

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

Модель вложенных интервалов[англ.] не испытывает такой проблемы, но является более сложной для реализации и не так широко известна. Она по-прежнему страдает от проблемы внешних ключей таблицы. Модель вложенных интервалов хранит положение узлов в виде рациональных чисел, выраженных как коэффициенты (n/d)[9].

Использование модели вложенного множества имеет некоторые ограничения производительности при выполнении определённых операций обхода дерева. Например, при поиске дочернего узла, заданного родительским узлом, требуется сократить поддерево до определённого уровня, как в следующем примере кода SQL:

SELECT Child.Node, Child.Left, Child.Right
FROM Tree as Parent, Tree as Child
WHERE
    Child.Left BETWEEN Parent.Left AND Parent.Right
    AND NOT EXISTS (    -- Нет промежуточного узла
        SELECT *
        FROM Tree as Mid
        WHERE Mid.Left BETWEEN Parent.Left AND Parent.Right
            AND Child.Left BETWEEN Mid.Left AND Mid.Right
            AND Mid.Node NOT IN (Parent.Node, Child.Node)
    )
    AND Parent.Left = 1  -- Учитывается левый индекс родительского узла

Или, что эквивалентно:

SELECT DISTINCT Child.Node, Child.Left, Child.Right
FROM Tree as Child, Tree as Parent 
WHERE Parent.Left < Child.Left AND Parent.Right > Child.Right  -- Объединение дочерних узлов с предками
GROUP BY Child.Node, Child.Left, Child.Right
HAVING max(Parent.Left) = 1  -- Подмножество тех, у кого данный родительский узел является ближайшим предком

Запрос будет более сложным при поиске потомков в глубину более чем на один уровень. Чтобы преодолеть это ограничение и упростить обход дерева, в модель добавляется дополнительный столбец для хранения глубины узла в дереве.

Узел Левый Правый Глубина
Одежда 1 22 0
Мужская 2 9 1
Женская 10 21 1
Костюмы 3 8 2
Брюки 4 5 3
Куртки 6 7 3
Платья 11 16 2
Юбки 17 18 2
Блузки 19 20 2
Вечерние платья 12 13 3
Сарафаны 14 15 3
Получившееся представление

В этой модели поиск дочерних элементов, заданных родительским узлом, может быть выполнен с помощью следующего SQL-запроса:

SELECT Child.Node, Child.Left, Child.Right
FROM Tree as Child, Tree as Parent
WHERE
	Child.Depth = Parent.Depth + 1
	AND Child.Left > Parent.Left
	AND Child.Right < Parent.Right
	AND Parent.Left = 1  -- Учитывается левый индекс родительского узла

Примечания

[править | править код]
  1. Joe Celko. Trees and Hierarchies in SQL for Smarties, 2012.
  2. Hazel, Daniel. "Using rational numbers to key nested sets". arXiv:0806.3115. {{cite arXiv}}: Неизвестный параметр |accessdate= игнорируется (справка)
  3. Quassnoi. Adjacency list vs. nested sets: MySQL (29 сентября 2009). Дата обращения: 19 мая 2020. Архивировано 1 октября 2020 года.
  4. Quassnoi. Adjacency list vs. nested sets: PostgreSQL (24 сентября 2009). Дата обращения: 19 мая 2020. Архивировано 14 августа 2020 года.
  5. Quassnoi. Adjacency list vs. nested sets: Oracle (28 сентября 2009). Дата обращения: 19 мая 2020. Архивировано 30 октября 2020 года.
  6. Quassnoi. Adjacency list vs. nested sets: SQL Server (25 сентября 2009). Дата обращения: 19 мая 2020. Архивировано 26 сентября 2020 года.
  7. Bill Karwin. SQL Antipatterns, 2010.
  8. Bill Karwin. SQL Antipatterns, 2010, p. 44.
  9. Vadim Tropashko. Nested Intervals Tree Encoding in SQL, 2005.

Литература

[править | править код]