Каков наиболее эффективный / элегантный способ разбора плоской таблицы в дерево?
Предположим, у вас есть плоская таблица, которая хранит упорядоченную иерархию дерево:
Id Name ParentId Order
1 'Node 1' 0 10
2 'Node 1.1' 1 10
3 'Node 2' 0 20
4 'Node 1.1.1' 2 10
5 'Node 2.1' 3 10
6 'Node 1.2' 1 20
вот диаграмма, где мы имеем [id] Name
. Корневой узел 0 является вымышленным.
[0] ROOT / [1] Node 1 [3] Node 2 / [2] Node 1.1 [6] Node 1.2 [5] Node 2.1 / [4] Node 1.1.1
какой минималистичный подход вы бы использовали для вывода этого в HTML (или текст, если на то пошло) как правильно упорядоченное, правильно отступающее дерево?
предположим, что у вас есть только базовые структуры данных (массивы и хэш-карты), нет причудливых объектов с родительскими / дочерними ссылками, нет ORM, нет фреймворк, только две руки. Таблица представлена в виде результирующего набора, доступ к которому осуществляется случайным образом.
псевдо-код или простой английский-это нормально, это чисто концептуальный вопрос.
бонусный вопрос: есть ли принципиально лучший способ сохранить древовидную структуру, подобную этой, в СУБД?
ПРАВКИ И ДОПОЛНЕНИЯ
ответить одному комментатору (Пометить Городе Бесси ' s) вопрос: корневой узел не является необходимо, потому что он никогда не будет отображаться в любом случае. ParentId = 0-это соглашение, выражающее "это верхний уровень". Столбец Order определяет, как будут сортироваться узлы с одним и тем же родителем.
"результирующий набор", о котором я говорил, можно представить как массив хэш-карт (чтобы остаться в этой терминологии). Мой пример должен был быть уже там. Некоторые ответы идут на лишнюю милю и сначала строят ее, но это нормально.
дерево может быть сколь угодно глубокой. Каждый узел может иметь детей. Однако я не имел в виду дерево "миллионы записей".
не принимайте мой выбор именования узлов ('Node 1.1.1') за то, на что можно положиться. Узлы с таким же успехом можно назвать "Фрэнк" или "боб", не подразумевается структура именования, это было просто для того, чтобы сделать его читаемым.
я опубликовал свое собственное решение, чтобы вы, ребята, могли разорвать его на куски.
14 ответов
теперь, когда MySQL 8.0 приближается к выпуску,все популярные базы данных SQL будут поддерживать рекурсивные запросы в стандартном синтаксисе.
WITH RECURSIVE MyTree AS (
SELECT * FROM MyTable WHERE ParentId IS NULL
UNION ALL
SELECT m.* FROM MyTABLE AS m JOIN MyTree AS t ON m.ParentId = t.Id
)
SELECT * FROM MyTree;
я тестировал рекурсивные запросы в MySQL 8.0 в моей презентации Рекурсивный Запрос Throwdown в 2017 году.
ниже мой первоначальный ответ от 2008:
существует несколько способов хранения данных с древовидной структурой в реляционной базе данных. То, что вы показываете в своем примере, использует два методы:
- Список Смежности (столбец "родитель") и
- Перечисление Путь (пунктирные номера в столбце ваше имя).
другое решение называется Вложенные Наборы, и его можно хранить в той же таблице тоже. Читайте "деревья и иерархии в SQL для Smarties" Джо Селко для получения дополнительной информации об этих проектах.
я обычно предпочитаю дизайн называется Закрытие Таблице (он же "отношение смежности") для хранения древовидных данных. Для этого требуется другая таблица, но тогда запрос деревьев довольно прост.
я покрываю таблицу закрытия в моей презентации модели для иерархических данных с SQL и PHP и в моей книге SQL Antipatterns: избегая подводных камней программирования баз данных.
CREATE TABLE ClosureTable (
ancestor_id INT NOT NULL REFERENCES FlatTable(id),
descendant_id INT NOT NULL REFERENCES FlatTable(id),
PRIMARY KEY (ancestor_id, descendant_id)
);
хранить все пути в таблице закрытия, где есть прямая родословная от одного узла к другому. Включите строку для каждого узла, чтобы ссылаться на себя. Например, используя набор данных, который вы показали в своем вопросе:
INSERT INTO ClosureTable (ancestor_id, descendant_id) VALUES
(1,1), (1,2), (1,4), (1,6),
(2,2), (2,4),
(3,3), (3,5),
(4,4),
(5,5),
(6,6);
вы можете получить дерево, начиная с узла 1, как это:
SELECT f.*
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1;
вывод (в клиенте MySQL) выглядит следующим образом:
+----+
| id |
+----+
| 1 |
| 2 |
| 4 |
| 6 |
+----+
другими словами, узлы 3 и 5 исключаются, потому что они являются частью отдельной иерархии, а не нисходящей от узла 1.
Re: комментарий от e-satis о непосредственных детях (или непосредственных родителях). Вы можете добавить "path_length
столбец" к ClosureTable
чтобы упростить запрос специально для непосредственного ребенка или родителя (или любого другого расстояния).
INSERT INTO ClosureTable (ancestor_id, descendant_id, path_length) VALUES
(1,1,0), (1,2,1), (1,4,2), (1,6,1),
(2,2,0), (2,4,1),
(3,3,0), (3,5,1),
(4,4,0),
(5,5,0),
(6,6,0);
затем вы можете добавить термин в поиск для запроса непосредственных потомков данного узла. Это потомки, чьи path_length
is 1.
SELECT f.*
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
AND path_length = 1;
+----+
| id |
+----+
| 2 |
| 6 |
+----+
re комментарий от @ashraf: "как насчет сортировки всего дерева [по имя]?"
вот пример запроса для возврата всех узлов, которые являются потомками узла 1, присоедините их к плоской таблице, содержащей другие атрибуты узла, такие как name
, и Сортировать по имени.
SELECT f.name
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
ORDER BY f.name;
Re комментарий от @Nate:
SELECT f.name, GROUP_CONCAT(b.ancestor_id order by b.path_length desc) AS breadcrumbs
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
JOIN ClosureTable b ON (b.descendant_id = a.descendant_id)
WHERE a.ancestor_id = 1
GROUP BY a.descendant_id
ORDER BY f.name
+------------+-------------+
| name | breadcrumbs |
+------------+-------------+
| Node 1 | 1 |
| Node 1.1 | 1,2 |
| Node 1.1.1 | 1,2,4 |
| Node 1.2 | 1,6 |
+------------+-------------+
пользователь предложил изменить сегодня. Итак, модераторы одобрили редактирование, но я его отменяю.
редактирование предложило, чтобы порядок в последнем запросе выше был ORDER BY b.path_length, f.name
, предположительно, чтобы убедиться, что порядок соответствует иерархии. Но это не работает, потому что он будет заказывать "узел 1.1.1" после "узла 1.2".
если вы хотите, чтобы порядок соответствовал иерархии разумным образом, это возможно, но не просто путем упорядочения по длине пути. Например, см. Мой ответ MySQL таблица закрытия иерархической базы данных-как вытащить информацию в правильном порядке.
если вы используете вложенные наборы (иногда называемые измененным обходом дерева предварительного порядка), вы можете извлечь всю древовидную структуру или любое поддерево внутри нее в древовидном порядке с помощью одного запроса, за счет более дорогих вставок, так как вам нужно управлять столбцами, которые описывают путь в порядке через древовидную структуру.
на django-mptt, я использовал такую структуру:
id parent_id tree_id level lft rght -- --------- ------- ----- --- ---- 1 null 1 0 1 14 2 1 1 1 2 7 3 2 1 2 3 4 4 2 1 2 5 6 5 1 1 1 8 13 6 5 1 2 9 10 7 5 1 2 11 12
, который описывает дерево, которое выглядит так (с id
представляя каждому пункту):
1 +-- 2 | +-- 3 | +-- 4 | +-- 5 +-- 6 +-- 7
или, как вложенная диаграмма набора, которая делает более очевидным, как lft
и rght
значения работы:
__________________________________________________________________________ | Root 1 | | ________________________________ ________________________________ | | | Child 1.1 | | Child 1.2 | | | | ___________ ___________ | | ___________ ___________ | | | | | C 1.1.1 | | C 1.1.2 | | | | C 1.2.1 | | C 1.2.2 | | | 1 2 3___________4 5___________6 7 8 9___________10 11__________12 13 14 | |________________________________| |________________________________| | |__________________________________________________________________________|
как вы можете видеть, чтобы получить все поддерево для данного узла, в порядке дерева, вам просто нужно выбрать все строки, которые имеют lft
и rght
значения между lft
и rght
значения. Также просто получить дерево предков для данного узла.
на
начиная с Oracle 9i, вы можете использовать CONNECT BY.
SELECT LPAD(' ', (LEVEL - 1) * 4) || "Name" AS "Name"
FROM (SELECT * FROM TMP_NODE ORDER BY "Order")
CONNECT BY PRIOR "Id" = "ParentId"
START WITH "Id" IN (SELECT "Id" FROM TMP_NODE WHERE "ParentId" = 0)
начиная с SQL Server 2005 можно использовать рекурсивное общее табличное выражение (CTE).
WITH [NodeList] (
[Id]
, [ParentId]
, [Level]
, [Order]
) AS (
SELECT [Node].[Id]
, [Node].[ParentId]
, 0 AS [Level]
, CONVERT([varchar](MAX), [Node].[Order]) AS [Order]
FROM [Node]
WHERE [Node].[ParentId] = 0
UNION ALL
SELECT [Node].[Id]
, [Node].[ParentId]
, [NodeList].[Level] + 1 AS [Level]
, [NodeList].[Order] + '|'
+ CONVERT([varchar](MAX), [Node].[Order]) AS [Order]
FROM [Node]
INNER JOIN [NodeList] ON [NodeList].[Id] = [Node].[ParentId]
) SELECT REPLICATE(' ', [NodeList].[Level] * 4) + [Node].[Name] AS [Name]
FROM [Node]
INNER JOIN [NodeList] ON [NodeList].[Id] = [Node].[Id]
ORDER BY [NodeList].[Order]
оба выведут следующие результаты.
Name 'Node 1' ' Node 1.1' ' Node 1.1.1' ' Node 1.2' 'Node 2' ' Node 2.1'
Это довольно старый вопрос, но поскольку у него много взглядов, я думаю, что стоит представить альтернативное и, на мой взгляд, очень элегантное решение.
для чтения древовидной структуры вы можете использовать рекурсивные общие табличные выражения (CTEs). Это дает возможность получить сразу всю древовидную структуру, иметь информацию об уровне узла, его родительском узле и порядке внутри дочерних узлов родительского узла.
позвольте мне показать вам, как это будет работа в PostgreSQL 9.1.
-
создать структуру
CREATE TABLE tree ( id int NOT NULL, name varchar(32) NOT NULL, parent_id int NULL, node_order int NOT NULL, CONSTRAINT tree_pk PRIMARY KEY (id), CONSTRAINT tree_tree_fk FOREIGN KEY (parent_id) REFERENCES tree (id) NOT DEFERRABLE ); insert into tree values (0, 'ROOT', NULL, 0), (1, 'Node 1', 0, 10), (2, 'Node 1.1', 1, 10), (3, 'Node 2', 0, 20), (4, 'Node 1.1.1', 2, 10), (5, 'Node 2.1', 3, 10), (6, 'Node 1.2', 1, 20);
-
написать запрос
WITH RECURSIVE tree_search (id, name, level, parent_id, node_order) AS ( SELECT id, name, 0, parent_id, 1 FROM tree WHERE parent_id is NULL UNION ALL SELECT t.id, t.name, ts.level + 1, ts.id, t.node_order FROM tree t, tree_search ts WHERE t.parent_id = ts.id ) SELECT * FROM tree_search WHERE level > 0 ORDER BY level, parent_id, node_order;
вот результаты:
id | name | level | parent_id | node_order ----+------------+-------+-----------+------------ 1 | Node 1 | 1 | 0 | 10 3 | Node 2 | 1 | 0 | 20 2 | Node 1.1 | 2 | 1 | 10 6 | Node 1.2 | 2 | 1 | 20 5 | Node 2.1 | 2 | 3 | 10 4 | Node 1.1.1 | 3 | 2 | 10 (6 rows)
узлы дерева упорядочены по уровню глубины. В конечном итоге мы представим их в последующих строках.
для каждого уровня они упорядочиваются parent_id и node_order внутри родительского. Это говорит нам, как представить их в узле output - link для родитель в таком порядке.
имея такую структуру, было бы не сложно сделать действительно хорошую презентацию в HTML.
рекурсивные CTEs доступны в PostgreSQL, IBM DB2, MS SQL Server и Oracle.
Если вы хотите узнать больше о рекурсивных SQL-запросах, вы можете проверить документацию вашей любимой СУБД или прочитать мои две статьи, посвященные этой теме:
ответ Билла довольно чертовски хорош, этот ответ добавляет к нему некоторые вещи, которые заставляют меня желать так поддерживаемых резьбовых ответов.
В любом случае я хотел поддержать древовидную структуру и свойство Order. Я включил одно свойство в каждый узел с именем leftSibling
что делает то же самое Order
предназначен для выполнения в исходном вопросе (поддерживать порядок слева направо).
mysql> desc nodes ; +-------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | leftSibling | int(11) | NO | | 0 | | +-------------+--------------+------+-----+---------+----------------+ 3 rows in set (0.00 sec) mysql> desc adjacencies; +------------+---------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+---------+------+-----+---------+----------------+ | relationId | int(11) | NO | PRI | NULL | auto_increment | | parent | int(11) | NO | | NULL | | | child | int(11) | NO | | NULL | | | pathLen | int(11) | NO | | NULL | | +------------+---------+------+-----+---------+----------------+ 4 rows in set (0.00 sec)
подробнее и SQL-код в моем блоге.
Спасибо Билл ваш ответ был полезен для начала!
Ну учитывая выбор, я бы использовал объекты. Я бы создал объект для каждой записи, где каждый объект имеет children
collection и хранить их все в массиве assoc (/hashtable), где Id является ключом. И блиц через коллекцию один раз, добавив детей в соответствующие детские поля. простой.
но поскольку вам неинтересно ограничивать использование некоторых хороших ООП, я бы, вероятно, повторил на основе:
function PrintLine(int pID, int level)
foreach record where ParentID == pID
print level*tabs + record-data
PrintLine(record.ID, level + 1)
PrintLine(0, 0)
Edit: это похоже на пару других записей, но я думаю, что это немного чище. Одна вещь, которую я добавлю:это чрезвычайно SQL-интенсивный. Это гадость. Если у вас есть выбор, идти по пути ООП.
Это было написано быстро, и это ни красиво, ни эффективно (плюс это автобоксы много, преобразование между int
и Integer
раздражает!), но это работает.
Это, вероятно, нарушает правила, так как я создаю мои собственные объекты, но эй, я делаю это как отвлечение от реальной работы :)
Это также предполагает, что resultSet/table полностью считывается в какую-то структуру, прежде чем вы начнете строить узлы, что не было бы лучшим решением, если у вас есть сотни тысяч строк.
public class Node {
private Node parent = null;
private List<Node> children;
private String name;
private int id = -1;
public Node(Node parent, int id, String name) {
this.parent = parent;
this.children = new ArrayList<Node>();
this.name = name;
this.id = id;
}
public int getId() {
return this.id;
}
public String getName() {
return this.name;
}
public void addChild(Node child) {
children.add(child);
}
public List<Node> getChildren() {
return children;
}
public boolean isRoot() {
return (this.parent == null);
}
@Override
public String toString() {
return "id=" + id + ", name=" + name + ", parent=" + parent;
}
}
public class NodeBuilder {
public static Node build(List<Map<String, String>> input) {
// maps id of a node to it's Node object
Map<Integer, Node> nodeMap = new HashMap<Integer, Node>();
// maps id of a node to the id of it's parent
Map<Integer, Integer> childParentMap = new HashMap<Integer, Integer>();
// create special 'root' Node with id=0
Node root = new Node(null, 0, "root");
nodeMap.put(root.getId(), root);
// iterate thru the input
for (Map<String, String> map : input) {
// expect each Map to have keys for "id", "name", "parent" ... a
// real implementation would read from a SQL object or resultset
int id = Integer.parseInt(map.get("id"));
String name = map.get("name");
int parent = Integer.parseInt(map.get("parent"));
Node node = new Node(null, id, name);
nodeMap.put(id, node);
childParentMap.put(id, parent);
}
// now that each Node is created, setup the child-parent relationships
for (Map.Entry<Integer, Integer> entry : childParentMap.entrySet()) {
int nodeId = entry.getKey();
int parentId = entry.getValue();
Node child = nodeMap.get(nodeId);
Node parent = nodeMap.get(parentId);
parent.addChild(child);
}
return root;
}
}
public class NodePrinter {
static void printRootNode(Node root) {
printNodes(root, 0);
}
static void printNodes(Node node, int indentLevel) {
printNode(node, indentLevel);
// recurse
for (Node child : node.getChildren()) {
printNodes(child, indentLevel + 1);
}
}
static void printNode(Node node, int indentLevel) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < indentLevel; i++) {
sb.append("\t");
}
sb.append(node);
System.out.println(sb.toString());
}
public static void main(String[] args) {
// setup dummy data
List<Map<String, String>> resultSet = new ArrayList<Map<String, String>>();
resultSet.add(newMap("1", "Node 1", "0"));
resultSet.add(newMap("2", "Node 1.1", "1"));
resultSet.add(newMap("3", "Node 2", "0"));
resultSet.add(newMap("4", "Node 1.1.1", "2"));
resultSet.add(newMap("5", "Node 2.1", "3"));
resultSet.add(newMap("6", "Node 1.2", "1"));
Node root = NodeBuilder.build(resultSet);
printRootNode(root);
}
//convenience method for creating our dummy data
private static Map<String, String> newMap(String id, String name, String parentId) {
Map<String, String> row = new HashMap<String, String>();
row.put("id", id);
row.put("name", name);
row.put("parent", parentId);
return row;
}
}
предполагаю, что вы знаете, что корневые элементы равны нулю, вот псевдокод для вывода в текст:
function PrintLevel (int curr, int level)
//print the indents
for (i=1; i<=level; i++)
print a tab
print curr \n;
for each child in the table with a parent of curr
PrintLevel (child, level+1)
for each elementID where the parentid is zero
PrintLevel(elementID, 0)
вы можете эмулировать любую другую структуру данных с помощью hashmap, так что это не ужасное ограничение. При сканировании сверху вниз создается хэш-карта для каждой строки базы данных с записью для каждого столбца. Добавьте каждый из этих хэш-карт в" мастер " хэш-карту, набранную на id. Если какой-либо узел имеет "родителя", который вы еще не видели, создайте для него запись-заполнитель в главной хэш-карте и заполните ее, когда увидите фактический узел.
чтобы распечатать его, сделайте простой глубина-первый проход через данные, отслеживая уровень отступа по пути. Вы можете сделать это проще, сохраняя запись "дети" для каждой строки и заполняя ее при сканировании данных.
Что касается того, есть ли "лучший" способ хранения дерева в базе данных, это зависит от того, как вы собираетесь использовать данные. Я видел системы с известной максимальной глубиной, которые использовали разные таблицы для каждого уровня иерархии. Это имеет большой смысл, если уровни в дереве не являются в конце концов, вполне эквивалентно (категории верхнего уровня отличаются от листьев).
есть действительно хорошие решения, которые используют внутреннее представление btree индексов sql. Это основано на некоторых больших исследованиях, проведенных в 1998 году.
вот пример таблицы (в MySQL).
CREATE TABLE `node` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`tw` int(10) unsigned NOT NULL,
`pa` int(10) unsigned DEFAULT NULL,
`sz` int(10) unsigned DEFAULT NULL,
`nc` int(11) GENERATED ALWAYS AS (tw+sz) STORED,
PRIMARY KEY (`id`),
KEY `node_tw_index` (`tw`),
KEY `node_pa_index` (`pa`),
KEY `node_nc_index` (`nc`),
CONSTRAINT `node_pa_fk` FOREIGN KEY (`pa`) REFERENCES `node` (`tw`) ON DELETE CASCADE
)
единственными полями, необходимыми для представления дерева, являются:
- tw: слева направо индекс предварительного заказа DFS, где root = 1.
- pa: ссылка (используя tw) на родительский узел, root имеет значение null.
- sz: Размер ветви узла, включая саму себя.
- nc: используется в качестве синтаксического сахара. это tw+nc и представляет собой tw "следующего дочернего узла".
вот пример 24 Node population, упорядоченный tw:
+-----+---------+----+------+------+------+
| id | name | tw | pa | sz | nc |
+-----+---------+----+------+------+------+
| 1 | Root | 1 | NULL | 24 | 25 |
| 2 | A | 2 | 1 | 14 | 16 |
| 3 | AA | 3 | 2 | 1 | 4 |
| 4 | AB | 4 | 2 | 7 | 11 |
| 5 | ABA | 5 | 4 | 1 | 6 |
| 6 | ABB | 6 | 4 | 3 | 9 |
| 7 | ABBA | 7 | 6 | 1 | 8 |
| 8 | ABBB | 8 | 6 | 1 | 9 |
| 9 | ABC | 9 | 4 | 2 | 11 |
| 10 | ABCD | 10 | 9 | 1 | 11 |
| 11 | AC | 11 | 2 | 4 | 15 |
| 12 | ACA | 12 | 11 | 2 | 14 |
| 13 | ACAA | 13 | 12 | 1 | 14 |
| 14 | ACB | 14 | 11 | 1 | 15 |
| 15 | AD | 15 | 2 | 1 | 16 |
| 16 | B | 16 | 1 | 1 | 17 |
| 17 | C | 17 | 1 | 6 | 23 |
| 359 | C0 | 18 | 17 | 5 | 23 |
| 360 | C1 | 19 | 18 | 4 | 23 |
| 361 | C2(res) | 20 | 19 | 3 | 23 |
| 362 | C3 | 21 | 20 | 2 | 23 |
| 363 | C4 | 22 | 21 | 1 | 23 |
| 18 | D | 23 | 1 | 1 | 24 |
| 19 | E | 24 | 1 | 1 | 25 |
+-----+---------+----+------+------+------+
каждый результат дерева может быть выполнен нерекурсивно. Например, получить список предков узла при tw= '22'
предков
select anc.* from node me,node anc
where me.tw=22 and anc.nc >= me.tw and anc.tw <= me.tw
order by anc.tw;
+-----+---------+----+------+------+------+
| id | name | tw | pa | sz | nc |
+-----+---------+----+------+------+------+
| 1 | Root | 1 | NULL | 24 | 25 |
| 17 | C | 17 | 1 | 6 | 23 |
| 359 | C0 | 18 | 17 | 5 | 23 |
| 360 | C1 | 19 | 18 | 4 | 23 |
| 361 | C2(res) | 20 | 19 | 3 | 23 |
| 362 | C3 | 21 | 20 | 2 | 23 |
| 363 | C4 | 22 | 21 | 1 | 23 |
+-----+---------+----+------+------+------+
братья и сестры и дети тривиальны-просто используйте pa порядок полей по tw.
потомки
например, множество (ветвь) узлов, которые коренятся в tw = 17.
select des.* from node me,node des
where me.tw=17 and des.tw < me.nc and des.tw >= me.tw
order by des.tw;
+-----+---------+----+------+------+------+
| id | name | tw | pa | sz | nc |
+-----+---------+----+------+------+------+
| 17 | C | 17 | 1 | 6 | 23 |
| 359 | C0 | 18 | 17 | 5 | 23 |
| 360 | C1 | 19 | 18 | 4 | 23 |
| 361 | C2(res) | 20 | 19 | 3 | 23 |
| 362 | C3 | 21 | 20 | 2 | 23 |
| 363 | C4 | 22 | 21 | 1 | 23 |
+-----+---------+----+------+------+------+
Дополнительная Информация
эта методология чрезвычайно полезна, когда есть гораздо большее количество чтений, чем вставки или обновления.
поскольку для вставки, перемещения или обновления узла в дереве требуется, чтобы дерево было отрегулировано, необходимо заблокировать таблица перед началом действия.
стоимость вставки/удаления высока, потому что значения Индекса tw и SZ (размер ветви) должны быть обновлены на всех узлах после точки вставки и для всех предков соответственно.
перемещение ветви включает перемещение значения TW ветви за пределы диапазона, поэтому также необходимо отключить ограничения внешнего ключа при перемещении ветви. Есть, по существу, четыре запроса, необходимых для перемещения a бранч:
- переместить ветку из ряда.
- закройте пробел, который он оставил. (оставшееся дерево теперь нормализовано).
- откройте зазор, куда он пойдет.
- переместить ветку в новое положение.
Настроить Дерево Запросов
открытие / закрытие пробелов в дереве является важной подфункцией, используемой методами create/update/delete, поэтому я включаю ее здесь.
нам нужна два параметра-флаг, представляющий, уменьшаем мы или нет, и индекс TW узла. Так, например, tw=18 (который имеет размер ветви 5). Предположим, что мы сокращаем (удаляем tw) - это означает, что мы используем ' - ' вместо '+' в обновлениях следующего примера.
сначала мы используем (слегка измененную) функцию предка для обновления значения sz.
update node me, node anc set anc.sz = anc.sz - me.sz from
node me, node anc where me.tw=18
and ((anc.nc >= me.tw and anc.tw < me.pa) or (anc.tw=me.pa));
тогда нам нужно настроить tw для тех, чей tw выше, чем ветвь для удаления.
update node me, node anc set anc.tw = anc.tw - me.sz from
node me, node anc where me.tw=18 and anc.tw >= me.tw;
Затем нам нужно настроить родителя для тех, чей pa tw выше, чем ветвь, которую нужно удалить.
update node me, node anc set anc.pa = anc.pa - me.sz from
node me, node anc where me.tw=18 and anc.pa >= me.tw;
Если вложенные хэш-карты или массивы могут быть созданы, то я могу просто спуститься по таблице с самого начала и добавить каждый элемент во вложенный массив. Я должен проследить каждую строку до корневого узла, чтобы узнать, какой уровень во вложенном массиве вставить. Я могу использовать memoization, так что мне не нужно искать одного и того же родителя снова и снова.
Edit: я бы сначала прочитал всю таблицу в массив, поэтому он не будет повторно запрашивать БД. Конечно, это будет непрактично. если ваш стол очень большой.
после того, как структура построена, я должен сначала пройти через нее глубину и распечатать HTML.
нет лучшего фундаментального способа сохранить эту информацию, используя одну таблицу (я мог бы ошибаться ;), и хотел бы увидеть лучшее решение ). Однако, если вы создадите схему использования динамически создаваемых таблиц БД, то вы откроете совершенно новый мир, пожертвовав простотой и риском SQL hell ;).
Если элементы расположены в порядке дерева, как показано в вашем примере, вы можете использовать что-то вроде следующего примера Python:
delimiter = '.'
stack = []
for item in items:
while stack and not item.startswith(stack[-1]+delimiter):
print "</div>"
stack.pop()
print "<div>"
print item
stack.append(item)
это поддерживает стек, представляющий текущую позицию в дереве. Для каждого элемента таблицы он выводит элементы стека (закрывая соответствующие divs), пока не найдет родительский элемент текущего элемента. Затем он выводит начало этого узла и толкает его в стек.
Если вы хотите вывести дерево с помощью отступа вместо вложенных элементов можно просто пропустить инструкции print для печати divs и напечатать перед каждым элементом несколько пробелов, кратных размеру стека. Например, в Python:
print " " * len(stack)
вы также можете легко использовать этот метод для построения множества вложенных списков или словарей.
Edit: я вижу из вашего разъяснения, что имена не должны были быть путями узлов. Это предполагает альтернативный подход:
idx = {}
idx[0] = []
for node in results:
child_list = []
idx[node.Id] = child_list
idx[node.ParentId].append((node, child_list))
этот строит дерево массивов кортежей(!). idx[0] представляет корень(ы) дерева. Каждый элемент массива представляет собой 2-кортеж, состоящий из самого узла и список всех его детей. После создания вы можете удерживать idx[0] и отбрасывать idx, если не хотите получить доступ к узлам по их ID.
чтобы расширить решение SQL Билла, вы можете в основном сделать то же самое, используя плоский массив. Более того, если все ваши строки имеют одинаковую длину и известно максимальное количество детей (скажем, в двоичном дереве), вы можете сделать это с помощью одной строки (массива символов). Если у вас произвольное количество детей, это немного усложняет ситуацию... Мне придется проверить свои старые записи, чтобы увидеть, что можно сделать.
затем, жертвуя немного памяти, особенно если ваше дерево разрежено и / или unballanced, вы можете, с помощью немного индекса математики, получить доступ ко всем строкам случайным образом, сохраняя дерево, ширина сначала в массиве, как так (для двоичного дерева):
String[] nodeArray = [L0root, L1child1, L1child2, L2Child1, L2Child2, L2Child3, L2Child4] ...
yo знает вашу длину строки, вы знаете это
Я сейчас на работе, поэтому не могу тратить на это много времени, но с интересом я могу получить немного кода для этого.
мы используем это для поиска в двоичных деревьях из кодонов ДНК, процесс построил дерево, затем мы сплющили его для поиска текстовых шаблонов и когда найден, хотя индекс math (revers сверху) мы получаем узел обратно... очень быстро и эффективно, жестко наше дерево редко имело пустые узлы, но мы могли искать гигабайты данных в один миг.
подумайте об использовании инструментов nosql, таких как neo4j для иерархических структур. e.g сетевое приложение, такое как linkedin, использует couchbase (другое решение nosql)
но используйте nosql только для запросов уровня data-mart, а не для хранения / обслуживания транзакций