Если вы можете позволить себе немного бит времени предварительной обработки, тогда классическим решением этой проблемы будет использование nested sets.
CREATE TABLE node (
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
parent_id INTEGER DEFAULT NULL,
seq_min INTEGER NOT NULL,
seq_max INTEGER NOT NULL
);
Предполагая, что вы используете какой-то структуры данных в памяти, как
class Node:
def __init__(self, id, name, children=()):
self.id = id
self.name = name
self.children = list(children)
представляют узлы дерева в памяти, мы можем определить
def assign_sequence_numbers(root_node):
def recurse(counter, node):
node.seq_min = counter
node.seq_max = reduce(recurse, node.children, 1 + counter)
return node.seq_max
return recurse(1, root_node)
, который делает предварительный обход по дереву, который присваивает каждому из двух дополнительных номеров:
seq_min
Значение становится альтернативным уникальным целым ключом для каждого узла. Кроме того, все прямые дочерние элементы (и непрямые потомки) узла будут иметь значение seq_min
, которое больше, чем значение, присвоенное самому узлу.
Значение seq_max
- это просто верхняя граница для значений seq_min
всех узлов-потомков.
Пример: Рассмотрим следующее дерево
Root
|
+--- Category1
| |
| +--- Category1.1
| |
| `--- Category1.2
|
`--- Category2
Используя функцию, определенную выше, мы получаем
Root [min=1, max=6]
|
+--- Category1 [min=2, max=5]
| |
| +--- Category1.1 [min=3, max=4]
| |
| `--- Category1.2 [min=4, max=5]
|
`--- Category2 [min=5, max=6]
Заметим, что для каждого узла, значение min
всегда < = значение min
все потомки и значение max
всегда >
максимальное значение всех потомков. Таким образом, чтобы найти потомок Root
и что сами (то есть, получить все дерево) узел, мы делаем:
SELECT * FROM node
WHERE seq_min >= 1 /* i.e., seq_min(Root) */
AND seq_min < 6 /* i.e., seq_max(Root) */
Чтобы получить потомок category1 (и что сам узел)
SELECT * FROM node
WHERE seq_min >= 2 /* i.e., seq_min(Category1) */
AND seq_min < 5 /* i.e., seq_max(Category2) */
Недостатком этого метода является то, что в принципе вам придется повторно назначить номера seq_xxx
для всех узлов, если вы вносите изменения в дерево, то есть вставляете новые узлы или удаляете старые. Это часто не проблема, по крайней мере, если изменения в древовидной структуре нечасты и дерево достаточно мало.
Поскольку ITGenius уже связан с that answer: если ваша база данных поддерживает его, использование общих табличных выражений - это еще одна возможность, которая позволяет избежать этапа предварительной обработки и также более устойчива перед изменениями в древовидной структуре.
(я не проверял какого-либо кода, приведенной в этом ответе Вы, возможно, потребуется применить несколько исправлений здесь и там;., В частности, если язык вашего проекта выбора является не питон ...)
Пожалуйста, обратитесь http://stackoverflow.com/questions/14274942/sql -server-cte-and-recursion-example – ITGenius
Какая СУБД вы используете? Postgres? Oracle? –
@a_horse_with_no_name Я использую оракул. – Suniel