所以我正在尝试创建一个评论系统,在这个系统中你可以回复已经回复的评论(允许你在理论上创建无限的回复线程)。我希望它们按时间顺序显示(最新的在顶部),但当然,回复应该直接在原始评论的下面。如果有多条评论回复同一条评论,回复也应该按时间顺序排列(仍然在原始评论下面)。我还想限制评论组的数量(一组评论,只有一个评论,根本不是回复),比如25个。我应该如何设置MySQL表,以及我将使用哪种查询来提取我想要的内容?
这是我的数据库的简化版本:ID int(11) NOT NULL AUTO_INCREMENT,DatePosted datetime NOT NULL,InReplyTo int(11) NOT NULL DEFAULT '0',
抱歉,如果这有点让人困惑,我不知道怎么用不同的方式来表达。这个问题已经在我的脑海里存在了几个月了,每次我解决一个问题,我都会解决另一个问题……
发布于 2010-10-31 03:12:07
有很多种方法。这里有一种我喜欢的方法(并且经常使用)。
对数据库执行操作
考虑以下数据库结构:
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排序通常会按照您在生成页面时需要的顺序生成结果;但是您需要确保comments表上的索引能够正确地支持这一点--否则查询会正常工作,但效率会非常非常低:
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拆分到一个单独的表中,该表完全由comments表上的触发器管理,使用一些视图或存储过程来实现所需的各种查询。从而将您的中间层代码与了解或关心存储层次结构信息的机制的需要完全隔离。
当然,无论如何都不需要任何花哨的东西--只需将parent_path放入表中,然后在中间层中编写一些代码就足够了,以确保它与您必须管理的所有其他字段一起得到正确管理。
Imposing
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,然后我们将其与我们的“子树”查询连接在一起,以给出最终所需的结果。
如下所示:
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;请注意隐藏在内部select中间的语句limit 25 offset 0。该语句将检索最新的25条“根级”注释。
编辑:你可能会发现你不得不玩一些东西,以获得完全按照你喜欢的方式排序和/或限制东西的能力。这可能包括在层次结构中添加以parent_path编码的信息。例如:您可以决定将post_date作为parent_path:/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/的一部分,而不是/{id}/{id2}/{id3}/。这将使获取所需顺序和层次结构变得非常容易,但代价是必须预先填充字段并在数据更改时对其进行管理
希望这能有所帮助。祝好运!
发布于 2010-10-30 17:05:59
你应该考虑将你的注释嵌套在树中--我对数据树不是很熟悉,但我可以完成一些相对容易的事情--我愿意接受任何优化代码的建议(和解释)--但一个想法可能是这样的:
<?php
$mysqli = new mysqli('localhost', 'root', '', 'test');
/** The class which holds the comments */
class Comment
{
public $id, $parent, $content;
public $childs = array();
public function __construct($id, $parent, $content)
{
$this->id = $id;
$this->parent = $parent;
$this->content = $content;
}
public function addChild( Comment $obj )
{
$this->childs[] = $obj;
}
}
/** Function to locate an object from it's id to help nest the comments in a hieraci */
function locateObject( $id, $comments )
{
foreach($comments as $commentObject)
{
if($commentObject->id == $id)
return $commentObject;
if( count($commentObject->childs) > 0 )
return locateObject($id, $commentObject->childs);
}
}
/** Function to recursively show comments and their nested child comments */
function showComments( $commentsArray )
{
foreach($commentsArray as $commentObj)
{
echo $commentObj->id;
echo $commentObj->content;
if( count($commentObj->childs) > 0 )
showComments($commentObj->childs);
}
}
/** SQL to select the comments and order dem by their parents and date */
$sql = "SELECT * FROM comment ORDER BY parent, date ASC";
$result = $mysqli->query($sql);
$comments = array();
/** A pretty self-explainatory loop (I hope) */
while( $row = $result->fetch_assoc() )
{
$commentObj = new Comment($row["id"], $row["parent"], $row["content"]);
if($row["parent"] == 0)
{
$comments[] = $commentObj;
continue;
}
$tObj = locateObject($row["parent"], $comments);
if( $tObj )
$tObj->addChild( $commentObj );
else
$comments[] = $commentObj;
}
/** And then showing the comments*/
showComments($comments);
?>我希望你对我的建议有所了解,我相信这里的一些其他用户可以对我的建议提供一些有经验的想法,并帮助优化它。
发布于 2010-10-30 17:06:08
在数据库中,您可以创建一个带有外键列(parent_comment)的表,它引用了注释表本身。例如:
CREATE TABLE comments (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
parent_comment INT FOREIGN KEY REFERENCES comments(id),
date_posted DATETIME,
...)为了显示单个项目的注释,您必须选择特定项目的所有注释,并使用深度优先算法在脚本中递归解析它们。在遍历算法中应考虑时间顺序。
https://stackoverflow.com/questions/4057947
复制相似问题