15

我有一个存储对文章的评论的 cms。这些注释既可以是线程的,也可以是非线程的。尽管从技术上讲,它们是相同的,只是在未线程化时将回复列留空。我的应用程序适用于 sqlLite、MySQL 和 pgsql,所以我需要相当标准的 SQL。

我目前有一个评论表

comment_id
article_id
user_id
comment
timestamp
thread (this is the reply column)

我的问题是弄清楚如何最好地表示数据库中的线程评论。也许在一个单独的表格中,它支持没有内容的树集和一个简单的表格来保存文本?也许它已经是这样了?也许是另一种方式?

如果评论没有线程,我可以很容易地按时间戳排序。

如果它们有螺纹,我会这样排序

ORDER BY SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))

正如您从 ORDER BY 中看到的那样,注释查询永远不会使用索引作为基于函数的索引,仅真正存在于 Oracle 中。帮助我拥有闪电般的快速评论页面。

4

6 回答 6

21

我真的很喜欢Drupal如何解决这个问题。它为每个评论分配一个线程 ID。对于第一条评论,此 id 从 1 开始。如果对此评论添加回复,则会为其1.1分配 id。对评论的回复1.1给出了线程 id 1.1.1。注释的兄弟姐妹1.1被赋予线程 id 1.2。你明白了。添加评论时,只需一次查询即可轻松计算这些线程 ID。

当线程被渲染时,属于该线程的所有评论都在一个查询中获取,并按线程 ID 排序。这会按升序为您提供线程。此外,使用线程 ID,您可以找到每个评论的嵌套级别,并相应地缩进。

1
1.1
1.1.1
1.2
1.2.1

有几个问题需要解决:

  • 如果线程 id 的一个组件增长到 2 位,则按线程 id 排序将不会产生预期的顺序。一个简单的解决方案是确保线程 id 的所有组件都用零填充以具有相同的宽度。
  • 按线程 id 降序排序不会产生预期的降序。

Drupal 使用称为 vancode 的编号系统以更复杂的方式解决了第一个问题。至于第二个问题,它是通过在按降序排序时在线程ID后附加一个反斜杠(其ASCII码高于数字)来解决的。您可以通过查看comments 模块的源代码找到有关此实现的更多详细信息(请参阅函数 comment_get_thread 之前的大注释)。

于 2009-05-10T22:23:40.293 回答
6

我知道答案有点晚了,但是对于树数据使用闭包表,这是正确的关系方式。 http://www.slideshare.net/billkarwin/models-for-hierarchical-data

它描述了 4 种方法:

  • 邻接表(简单的父外键)
  • 路径枚举(已接受答案中提到的 Drupal 策略)
  • 嵌套集
  • 闭包表(将祖先/后代事实存储在单独的关系 [table] 中,并带有可能的距离列)

与其他选项相比,最后一个选项具有易于 CRUD 操作的优点。成本是空间,在最坏的情况下,它是数字树节点中的 O(n^2) 大小,但在实践中可能并没有那么糟糕。

于 2013-04-07T08:15:09.513 回答
2

实际上,我只是自己做的!我使用嵌套集模型来表示关系数据库中的分层数据。

在 MySQL 中管理分层数据对我来说是纯金。嵌套集是该文章中描述的第二个模型。

于 2009-05-10T22:01:42.903 回答
2

您可以在邻接集模型和嵌套集模型之间进行选择。在 MySQL中管理分层数据一文作了很好的介绍。

有关理论讨论,请参阅 Celko 的Trees and Hierarchies

如果您的数据库支持窗口函数,则实现线程列表相当容易。您所需要的只是目标数据库表中的递归引用,例如:

create Tablename (
  RecordID integer not null default 0 auto_increment,
  ParentID integer default null references RecordID,
  ...
)

然后,您可以使用递归公用表表达式来显示线程视图。此处提供了一个示例。

于 2009-05-10T22:52:36.567 回答
2

不幸的是,执行此操作的纯 SQL 方法非常慢。

NESTED SETSby 提出的建议@Marc W非常优雅,但如果您的树枝达到范围,它们可能需要更新整个树,这可能会很慢。

请参阅我的博客中的这篇文章,了解如何快速完成MySQL

您需要创建一个函数:

CREATE FUNCTION hierarchy_connect_by_parent_eq_prior_id(value INT) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
        DECLARE _id INT;
        DECLARE _parent INT;
        DECLARE _next INT;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET @id = NULL;

        SET _parent = @id;
        SET _id = -1;

        IF @id IS NULL THEN
                RETURN NULL;
        END IF;

        LOOP
                SELECT  MIN(id)
                INTO    @id
                FROM    t_hierarchy
                WHERE   parent = _parent
                        AND id > _id;
                IF @id IS NOT NULL OR _parent = @start_with THEN
                        SET @level = @level + 1;
                        RETURN @id;
                END IF;
                SET @level := @level - 1;
                SELECT  id, parent
                INTO    _id, _parent
                FROM    t_hierarchy
                WHERE   id = _parent;
        END LOOP;
END

并在这样的查询中使用它:

SELECT  hi.*
FROM    (
        SELECT  hierarchy_connect_by_parent_eq_prior_id(id) AS id, @level AS level
        FROM    (
                SELECT  @start_with := 0,
                        @id := @start_with,
                        @level := 0
                ) vars, t_hierarchy
        WHERE   @id IS NOT NULL
        ) ho
JOIN    t_hierarchy hi
ON      hi.id = ho.id

这当然是MySQL特定的,但它真的很快。

PostgreSQL如果您希望它在和之间可移植MySQL,您可以使用PostgreSQL'contrib forCONNECT BY并将查询包装到两个系统具有相同名称的存储过程中。

于 2009-05-10T23:24:47.120 回答
0

实际上,它必须在读取和写入之间取得平衡。

如果您可以在每次插入时更新一堆行,那么嵌套集(或等效的)将为您提供简单、快速的读取。

除此之外,父级上的简单 FK 将为您提供超简单的插入,但很可能是检索的噩梦。

我想我会使用嵌套集,但要注意预期的数据量和使用模式(为每个插入更新两个索引列(用于左右信息)上的几个,也许很多行可能是一个问题在某一点)。

于 2009-05-10T22:17:29.463 回答