有很多方法。这是我喜欢(并经常使用)的一种方法。
数据库
考虑以下数据库结构:
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 排序通常会按照您在生成页面时需要的顺序生成结果;但是您需要确保在 cmets 表上有一个可以正确支持这一点的索引 - 否则查询可以工作,但它确实非常非常低效:
create index comments_hier_idx on comments (parent_path, date_posted);
对于任何给定的单个评论,很容易获得该评论的整个 child-cmets 树。只需添加一个 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。如果您移动 cmets(这无疑是一个奇怪的用例),您必须确保手动更新从属于移动评论的每个评论的每个 parent_path。 ...但这些都是相当容易跟上的事情。
如果你真的想要花哨(并且如果你的数据库支持它),你可以编写触发器来透明地管理 parent_path ——我将把这个练习留给读者,但基本思想是插入和更新触发器将在提交新插入之前触发。他们会走上树(使用parent_id外键关系),并相应地重建parent_path的值。
甚至可以将parent_path 拆分为一个单独的表,该表完全由 cmets 表上的触发器管理,并带有一些视图或存储过程来实现您需要的各种查询。从而将您的中间层代码与了解或关心存储层次结构信息的机制完全隔离开来。
当然,任何花哨的东西都不是必需的——通常只需将 parent_path 放入表中,并在中间层编写一些代码以确保它与所有内容一起得到正确管理您已经必须管理的其他字段。
施加限制
MySQL(和其他一些数据库)允许您使用 LIMIT 子句选择“页面”数据:
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 个“根级”cmets。
[编辑:你可能会发现你必须玩一些东西才能完全按照你喜欢的方式订购和/或限制东西。这可能包括在以parent_path 编码的层次结构中添加信息。例如:代替/{id}/{id2}/{id3}/,您可能决定将post_date 作为父路径的一部分:/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/。这将使获得所需的顺序和层次结构变得非常容易,但代价是必须预先填充字段,并在数据更改时对其进行管理]
希望这会有所帮助。
祝你好运!