Bây giờ MySQL 8.0 hỗ trợ các truy vấn đệ quy , chúng ta có thể nói rằng tất cả các cơ sở dữ liệu SQL phổ biến đều hỗ trợ các truy vấn đệ quy theo cú pháp chuẩn.
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;
Tôi đã thử nghiệm các truy vấn đệ quy trong MySQL 8.0 trong bài trình bày Truy vấn truy vấn đệ quy năm 2017.
Dưới đây là câu trả lời ban đầu của tôi từ năm 2008:
Có một số cách để lưu trữ dữ liệu có cấu trúc cây trong cơ sở dữ liệu quan hệ. Những gì bạn thể hiện trong ví dụ của bạn sử dụng hai phương pháp:
- Danh sách điều chỉnh (cột "cha mẹ") và
- Đường dẫn liệt kê (các số chấm trong cột tên của bạn).
Một giải pháp khác được gọi là Bộ lồng nhau và nó cũng có thể được lưu trữ trong cùng một bảng. Đọc " Cây và phân cấp trong SQL cho Smarties " của Joe Celko để biết thêm thông tin về các thiết kế này.
Tôi thường thích một thiết kế có tên là Bảng đóng (hay còn gọi là "Quan hệ phụ trợ") để lưu trữ dữ liệu có cấu trúc cây. Nó đòi hỏi một bảng khác, nhưng sau đó truy vấn cây khá dễ dàng.
Tôi trình bày Bảng đóng cửa trong phần trình bày của tôi Các mô hình cho dữ liệu phân cấp với SQL và PHP và trong cuốn sách của tôi Antipotype: Tránh các cạm bẫy của lập trình cơ sở dữ liệu .
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)
);
Lưu trữ tất cả các đường dẫn trong Bảng đóng, nơi có tổ tiên trực tiếp từ nút này sang nút khác. Bao gồm một hàng cho mỗi nút để tham chiếu chính nó. Ví dụ: sử dụng tập dữ liệu bạn đã hiển thị trong câu hỏi của mình:
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);
Bây giờ bạn có thể lấy một cây bắt đầu từ nút 1 như thế này:
SELECT f.*
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1;
Đầu ra (trong máy khách MySQL) trông như sau:
+----+
| id |
+----+
| 1 |
| 2 |
| 4 |
| 6 |
+----+
Nói cách khác, các nút 3 và 5 bị loại trừ, vì chúng là một phần của hệ thống phân cấp riêng biệt, không giảm dần từ nút 1.
Re: nhận xét từ e-satis về trẻ em ngay lập tức (hoặc cha mẹ ngay lập tức). Bạn có thể thêm một path_length
cột "" vào ClosureTable
để dễ dàng truy vấn cụ thể hơn cho một đứa trẻ hoặc cha mẹ ngay lập tức (hoặc bất kỳ khoảng cách nào khác).
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);
Sau đó, bạn có thể thêm một thuật ngữ trong tìm kiếm của bạn để truy vấn con ngay lập tức của một nút nhất định. Đây là những hậu duệ có 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 |
+----+
Nhận xét lại từ @ashraf: "Làm thế nào về việc sắp xếp toàn bộ cây [theo tên]?"
Đây là một truy vấn mẫu để trả về tất cả các nút là hậu duệ của nút 1, nối chúng với FlatTable có chứa các thuộc tính nút khác như name
và sắp xếp theo tên.
SELECT f.name
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
ORDER BY f.name;
Nhận xét lại từ @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 |
+------------+-------------+
Một người dùng đề nghị chỉnh sửa ngày hôm nay. Người điều hành SO đã phê duyệt bản chỉnh sửa, nhưng tôi đang đảo ngược nó.
Bản chỉnh sửa đề xuất rằng ORDER BY trong truy vấn cuối cùng ở trên ORDER BY b.path_length, f.name
, có lẽ là để đảm bảo thứ tự khớp với cấu trúc phân cấp. Nhưng điều này không hoạt động, bởi vì nó sẽ đặt hàng "Nút 1.1.1" sau "Nút 1.2".
Nếu bạn muốn thứ tự khớp với hệ thống phân cấp một cách hợp lý, điều đó là có thể, nhưng không chỉ đơn giản bằng cách sắp xếp theo độ dài đường dẫn. Ví dụ: xem câu trả lời của tôi về cơ sở dữ liệu phân cấp của Bảng đóng cửa MySQL - Cách lấy thông tin theo đúng thứ tự .