有很多方法。这是我喜欢的一种方法(并且经常使用)。
数据库
考虑以下数据库结构:
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)
);
你的数据将如下所示:
+-----+-------------------------------------+--------------------------+---------------+
| 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...
以可用的方式选择所有内容相当容易:
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted;
订购依据parent_path, date_posted
通常会按照生成页面时需要的顺序生成结果;但您需要确保注释表上有一个可以正确支持这一点的索引——否则查询可以工作,但效率真的非常低:
create index comments_hier_idx on comments (parent_path, date_posted);
对于任何给定的单个评论,很容易获得该评论的整个子评论树。只需添加一个 where 子句:
select id, parent_path, parent_id, comment_text, date_posted
from comments
where parent_path like '/1/%'
order by parent_path, date_posted;
添加的 where 子句将使用我们已经定义的相同索引,所以我们可以开始了。
请注意,我们还没有使用parent_id
然而。事实上,这并不是绝对必要的。但我之所以将其包括在内,是因为它允许我们定义传统的外键来强制引用完整性,并根据需要实现级联删除和更新。外键约束和级联规则仅在 INNODB 表中可用:
ALTER TABLE comments ENGINE=InnoDB;
ALTER TABLE comments
ADD FOREIGN KEY ( parent_id ) REFERENCES comments
ON DELETE CASCADE
ON UPDATE CASCADE;
管理层次结构
当然,为了使用这种方法,您必须确保设置parent_path
当您插入每条评论时正确地插入。如果您移动评论(这无疑是一个奇怪的用例),您必须确保手动更新从属于移动评论的每个评论的每个parent_path。 ...但这些都是相当容易跟上的事情。
如果你真的想变得更奇特(并且如果你的数据库支持它),你可以编写触发器来透明地管理parent_path——我将把这个作为练习留给读者,但基本思想是插入和更新触发器将触发在提交新插入之前。他们会走上树(使用parent_id
外键关系),并重建值parent_path
因此。
甚至有可能打破parent_path
放入一个单独的表中,该表完全由注释表上的触发器管理,并使用一些视图或存储过程来实现您需要的各种查询。因此,将中间层代码与了解或关心存储层次结构信息的机制完全隔离。
当然,无论如何都不需要任何花哨的东西——通常只需将parent_path放入表中,并在中间层编写一些代码以确保它与所有其他字段一起得到正确管理就足够了你已经必须管理了。
施加限制
MySQL(和其他一些数据库)允许您使用LIMIT
clause:
SELECT * FROM mytable LIMIT 25 OFFSET 0;
不幸的是,在处理这样的分层数据时,仅使用 LIMIT 子句不会产生所需的结果。
-- 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;
相反,我们需要在想要施加限制的级别进行单独的选择,然后将其与“子树”查询连接起来以给出最终所需的结果。
像这样的东西:
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;
注意声明limit 25 offset 0
,埋在内部选择的中间。该语句将检索最近 25 条“根级”评论。
[编辑:您可能会发现您必须稍微尝试一些东西才能完全按照您喜欢的方式排序和/或限制事物。这可能包括在编码的层次结构中添加信息parent_path
。例如:而不是/{id}/{id2}/{id3}/
,您可能决定将 post_date 作为 Parent_path 的一部分:/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/
。这将使您很容易获得所需的顺序和层次结构,但代价是必须预先填充字段,并在数据更改时对其进行管理]
希望这可以帮助。
祝你好运!