Тепер, коли 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 у своїй презентації " Рекрусивне запит", що знижується у 2017 році.
Нижче моя оригінальна відповідь з 2008 року:
Існує кілька способів зберігання структурованих деревом даних у реляційній базі даних. Те, що ви показуєте у своєму прикладі, використовує два способи:
- Список суміжності (стовпець "батьків") та
- Перерахування шляху (пунктирні числа у стовпці вашого імені).
Інше рішення називається Nested Sets , і воно може зберігатися і в одній таблиці. Прочитайте " Дерева та ієрархії в SQL для розумних " Джо Челко для отримання додаткової інформації про ці проекти.
Зазвичай я віддаю перевагу дизайну під назвою " Таблиця закриття" (він же "співвідношення суміжності") для зберігання структурованих деревом даних. Для цього потрібна інша таблиця, але тоді запити дерев досить легко.
Я висвітлюю Таблицю закриття в моїй презентації Моделі ієрархічних даних із 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-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
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;
Повторити коментар від @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 - Як вивести інформацію у правильному порядку .