-
Dữ liệu dạng cây là dạng dữ liệu thường được thấy ở các ứng dụng như:
- cấu trúc sơ đồ phòng ban, bộ phận của một doanh nghiệp
- danh sách các chuyên mục
- bình luận theo dạng thread-based (vd: fb, tiktok, …)
-
Điểm chung của cấu trúc dạng cây là mỗi node trên cây thường có một node cha và có từ 0-n node con, các node con cũng có thể có rất nhiều các node con khác tuỳ vào độ sâu của cây. Các node ban đầu (root node) thì không có node cha.
-
Các khái niệm cơ bản:
- Nút gốc : Là nút trên cùng của cây, nút duy nhất không có nút cha.
- Nút lá : Là các nút có 0 nút con.
- Nút nhánh : Là các nút có cả nút cha và nút con.
- Mức của nút : Là các số nguyên đếm từ 0. Các nút ngang hàng có cùng mức với nhau. Nút gốc có mức là 0, các nút con trực tiếp của gốc có mức là 1 …
- Chiều cao của cây : Là mức của nút là lớn nhất.
- Phổ biến:
-
Adjacency List (Danh sách kề):
- vd với tính năng comments facebook theo dạng thread-based (người dùng có thể trả lời lẫn nhau vô hạn), tức là mỗi bình luận có thể được trả lời bởi nhiều bình luận khác, tạo ra nhiều các phân lớp khác nhau. Mỗi bình luận (ngoại trừ bình luận gốc) sẽ có duy nhất một bình luận cha.
- theo cách Adjacency List, với mỗi bản ghi của một bình luận sẽ lưu thêm một trường parent_id để trỏ đến bình luận cha của bình luận hiện tại. Nếu bình luận đã là bình luận gốc thì parent_id sẽ được đặt là null.
CREATE TABLE comments ( comment_id primary key, parent_id bigint unsigned, comment_date datetime not null, comment text not null, foreign key (parent_id) references comments(comment_id) );
- với adjacency list để query node cha hoặc node con trực tiếp khá dễ dàng
select c1.*, c2.* from comments c1 left join comments c2 on c2.parent_id = c1.comment_id;
- Tuy nhiên để query lấy hết mọi con cháu của một node cha không hề đơn giản.
WITH RECURSIVE menu_tree (id, parent_id, level) AS ( SELECT comment_id, parent_id, 0 FROM comments WHERE parent_id is null UNION ALL SELECT mn.comment_id, mn.parent_id, mt.level + 1 FROM comments mn, menu_tree mt WHERE mn.parent_id = mt.comment_id ) SELECT * FROM menu_tree ORDER BY level, parent_id, comment_id;
- Để loại bỏ một node bất kỳ ra khỏi tree, bằng cách thực hiện query như bảng trên và thay
parent_id is null
thành giá trị muốn loại bỏ để lấy hết con cháu của node đó. Hoặc có thể sử dụng đệ quy để vét cạn. - Để thêm một node vào vị trí bất kỳ chỉ cần xác định node cha.
-
Closure Table:
- closure table là cách thêm một bảng phụ làm bảng tham chiếu mối quan hệ ancestor-descendant. Điểm mấu chốt là mối quan hệ ancestor-descendant sẽ lưu lại mọi quan hệ (cha-cha, cha-con, cha-cháu, …) chứ không chỉ lưu mỗi mối quan hệ cha-con trực tiếp.
- Bên cạnh bảng
comments
, với cách này chỉ cần tạo thêm một bảng phụ TreePaths với các trường ancestor-descendant và lưu lại ở đó các quan cặp quan hệ.
CREATE TABLE comments ( comment_id primary key, comment_date datetime not null, comment text not null ); CREATE TABLE TreePaths ( ancestor bigint unsigned not null, descendant bigint unsigned not null, primary key(ancestor, descendant), foreign key (ancestor) references comments(comment_id), foreign key (descendant) references comments(comment_id) );
- Tương ứng với mỗi node, thì ở bảng TreePaths sẽ có một bản ghi chỉ mối quan hệ lên chính node hiện tại, và nhiều bản ghi chỉ mối quan hệ đến mọi descendant của node hiện tại đó. Nhờ đó mà việc truy vấn query node cha, node con trực tiếp sẽ dễ dàng hơn. Ngay cả query lấy hết mọi con cháu cũng đơn giản hơn
Adjacency List
nhiều. - vd để tìm các ancestor của comment_id = 6:
select c.* from comments as c join TreePaths as t on c.comment_id = t.ancestor where t.descendant = 6;
- vd để tìm các descendant của comment_id = 4;
select c.* from comments as c join TreePaths as t on c.comment_id = t.descendant where t.ancestor = 4;
- Việc thêm các node cũng dễ dàng hơn. Chỉ có một điều phải lưu ý là khi dữ liệu cây nhiều và sâu hơn thì số lượng bản ghi của TreePath cũng tăng lên rất nhanh, đánh đổi lại việc truy xuất dễ dàng hơn.
- Để loại bỏ một node bất kỳ ra khỏi tree, bằng cách thực hiện query như bảng trên và thay
c1.ancestor = 1
thành giá trị muốn loại bỏ để lấy hết con cháu của node đó.
select c1.descendant, c2.descendant from tree_datastructure.TreePaths c1 left join tree_datastructure.TreePaths c2 on c2.ancestor = c1.descendant where c1.ancestor = 1
Hoặc để loại bỏ tất cả row liên quan đến node cần xoá
select * from tree_datastructure.TreePaths where `descendant = 4` and descendant != ancestor UNION ALL select c2.* from tree_datastructure.TreePaths c1 left join tree_datastructure.TreePaths c2 on c2.ancestor = c1.descendant where `c1.ancestor = 4` order by ancestor, descendant
Slide tham khảo: Tree Datastructure
Tôi là Hoàng Đức – Founder của MaiLike.xyz, Tôi đã có 10 năm trong ngành dịch vụ hỗ trợ tăng tương tác mạng xã hội. Blog này, sẽ giúp bạn có kiến thức hữu ích nhất về các mạng xã hội phổ biến hiện nay.”