Có rất nhiều cách. Đây là một cách tiếp cận mà tôi thích (và sử dụng thường xuyên).
Cơ sở dữ liệu
Xem xét cấu trúc cơ sở dữ liệu sau:
CREATE TABLE comments (
id int(11) unsigned NOT NULL auto_increment,
parent_id int(11) unsigned default NULL,
parent_path varchar(255) NOT NULL,
comment_text varchar(255) NOT NULL,
date_posted datetime NOT NULL,
PRIMARY KEY (id)
);
dữ liệu của bạn sẽ trông như thế này:
+-----+-------------------------------------+--------------------------+---------------+
| id | parent_id | parent_path | comment_text | date_posted |
+-----+-------------------------------------+--------------------------+---------------+
| 1 | null | / | I'm first | 1288464193 |
| 2 | 1 | /1/ | 1st Reply to I'm First | 1288464463 |
| 3 | null | / | Well I'm next | 1288464331 |
| 4 | null | / | Oh yeah, well I'm 3rd | 1288464361 |
| 5 | 3 | /3/ | reply to I'm next | 1288464566 |
| 6 | 2 | /1/2/ | this is a 2nd level reply| 1288464193 |
... and so on...
Khá dễ dàng để chọn mọi thứ theo cách có thể sử dụng được:
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted;
đặt hàng theo parent_path, date_posted
thường sẽ tạo ra kết quả theo thứ tự bạn sẽ cần khi tạo trang của mình; nhưng bạn sẽ muốn chắc chắn rằng bạn có một chỉ mục trên bảng nhận xét sẽ hỗ trợ đúng cách này - nếu không thì truy vấn hoạt động, nhưng nó thực sự, thực sự không hiệu quả:
create index comments_hier_idx on comments (parent_path, date_posted);
Đối với bất kỳ nhận xét đơn lẻ nào đã cho, thật dễ dàng nhận được toàn bộ cây nhận xét trẻ em của nhận xét đó. Chỉ cần thêm mệnh đề where:
select id, parent_path, parent_id, comment_text, date_posted
from comments
where parent_path like '/1/%'
order by parent_path, date_posted;
mệnh đề where được thêm vào sẽ sử dụng cùng một chỉ mục mà chúng ta đã xác định, vì vậy chúng ta nên bắt đầu.
Lưu ý rằng chúng tôi chưa sử dụng parent_id
chưa. Trên thực tế, nó không hoàn toàn cần thiết. Nhưng tôi bao gồm nó vì nó cho phép chúng ta xác định một khóa ngoại truyền thống để thực thi tính toàn vẹn của tham chiếu và thực hiện xóa và cập nhật theo tầng nếu chúng ta muốn. Ràng buộc khóa ngoại và quy tắc xếp tầng chỉ có sẵn trong bảng INNODB:
ALTER TABLE comments ENGINE=InnoDB;
ALTER TABLE comments
ADD FOREIGN KEY ( parent_id ) REFERENCES comments
ON DELETE CASCADE
ON UPDATE CASCADE;
Quản lý hệ thống phân cấp
Tất nhiên, để sử dụng phương pháp này, bạn sẽ phải đảm bảo rằng bạn đặt parent_path
đúng cách khi bạn chèn mỗi nhận xét. Và nếu bạn di chuyển các nhận xét xung quanh (phải thừa nhận là một usecase kỳ lạ), bạn sẽ phải đảm bảo rằng bạn cập nhật thủ công từng đường dẫn_của mỗi nhận xét phụ với nhận xét đã di chuyển. ... nhưng đó đều là những thứ khá dễ dàng để theo kịp.
Nếu bạn thực sự muốn trở nên ưa thích (và nếu db của bạn hỗ trợ nó), bạn có thể viết các trình kích hoạt để quản lý đường dẫn parent_path một cách minh bạch - Tôi sẽ để lại bài tập này cho người đọc, nhưng ý tưởng cơ bản là trình kích hoạt chèn và cập nhật sẽ kích hoạt trước khi một phụ trang mới được cam kết. họ sẽ đi bộ lên cây (sử dụng parent_id
mối quan hệ khóa ngoại) và xây dựng lại giá trị của parent_path
theo đó.
Thậm chí có thể phá vỡ parent_path
ra một bảng riêng biệt được quản lý hoàn toàn bởi các trình kích hoạt trên bảng nhận xét, với một vài dạng xem hoặc các thủ tục được lưu trữ để triển khai các truy vấn khác nhau mà bạn cần. Do đó, hoàn toàn tách biệt mã cấp giữa của bạn khỏi nhu cầu biết hoặc quan tâm đến cơ chế lưu trữ thông tin phân cấp.
Tất nhiên, không có bất kỳ thứ gì ưa thích là bắt buộc - thường là khá đủ để chỉ cần thả đường dẫn_đường_trục vào bảng và viết một số mã trong lớp giữa của bạn để đảm bảo rằng nó được quản lý đúng cách cùng với tất cả các trường khác bạn đã phải quản lý.
Áp đặt giới hạn
MySQL (và một số cơ sở dữ liệu khác) cho phép bạn chọn "trang" dữ liệu bằng cách sử dụng LIMIT
mệnh đề:
SELECT * FROM mytable LIMIT 25 OFFSET 0;
Thật không may, khi xử lý dữ liệu phân cấp như thế này, chỉ riêng mệnh đề LIMIT sẽ không mang lại kết quả mong muốn.
-- the following will NOT work as intended
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted
LIMIT 25 OFFSET 0;
Thay vào đó, chúng ta cần phải có một lựa chọn riêng biệt ở cấp mà chúng ta muốn áp đặt giới hạn, sau đó chúng ta kết hợp trở lại đó cùng với truy vấn "cây con" của chúng ta để đưa ra kết quả mong muốn cuối cùng.
Một cái gì đó như thế này:
select
a.*
from
comments a join
(select id, parent_path
from comments
where parent_id is null
order by parent_path, post_date DESC
limit 25 offset 0) roots
on a.parent_path like concat(roots.parent_path,roots.id,'/%') or a.id=roots.id)
order by a.parent_path , post_date DESC;
Lưu ý câu lệnh limit 25 offset 0
, được chôn ở giữa vùng chọn bên trong. Câu lệnh này sẽ truy xuất 25 nhận xét "cấp cơ sở" gần đây nhất.
[sửa:bạn có thể thấy rằng bạn phải chơi với mọi thứ một chút để có khả năng sắp xếp và / hoặc giới hạn mọi thứ theo cách bạn muốn. điều này có thể bao gồm việc thêm thông tin trong hệ thống phân cấp được mã hóa trong parent_path
. ví dụ:thay vì /{id}/{id2}/{id3}/
, bạn có thể quyết định bao gồm post_date như một phần của đường dẫn gốc:/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/
. Điều này sẽ giúp bạn rất dễ dàng có được thứ tự và thứ bậc mà bạn muốn, với chi phí là phải điền trước trường và quản lý nó khi dữ liệu thay đổi]
hy vọng điều này sẽ hữu ích. chúc may mắn!