Теперь, когда 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 в своей презентации Recursive Query Throwdown в 2017 году.
Ниже мой оригинальный ответ от 2008 года:
Существует несколько способов хранения древовидных данных в реляционной базе данных. То, что вы показываете в своем примере, использует два метода:
- Список смежности (родительский столбец) и
- Перечисление пути (точечные числа в вашем столбце имени).
Другое решение называется Nested Sets , и оно также может храниться в той же таблице. Прочтите « Деревья и иерархии в SQL для умников » Джо Селко, чтобы узнать больше об этих проектах.
Я обычно предпочитаю проект под названием Closure Table (он же «Соотношение смежности») для хранения данных с древовидной структурой. Требуется другая таблица, но тогда запросить деревья довольно просто.
Я закрываю таблицу замыканий в своей презентации « Модели для иерархических данных с использованием SQL и PHP» и в своей книге « Антипаттерны SQL: предотвращение ловушек программирования баз данных» .
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-satun о непосредственных детях (или непосредственных родителях). Вы можете добавить 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
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 |
+----+
Комментарий от @ashraf: «Как насчет сортировки всего дерева [по имени]?»
Вот пример запроса, чтобы вернуть все узлы, которые являются потомками узла 1, соединить их с FlatTable, который содержит другие атрибуты узла, такие как 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 в последнем запросе выше ORDER BY b.path_length, f.name
, вероятно, должен убедиться, что порядок соответствует иерархии. Но это не работает, потому что он будет заказывать «Node 1.1.1» после «Node 1.2».
Если вы хотите, чтобы порядок соответствовал иерархии разумным образом, это возможно, но не просто путем упорядочения по длине пути. Например, см. Мой ответ на иерархическую базу данных MySQL Closure Table - Как вытащить информацию в правильном порядке .