Dom*_*ard 8 mysql sql sorting commenting hierarchical-data
我正在敲打我的头,我想知道是否有人能够帮助我.
我的目标是创建一个评论线程,该评论线程在评论评分系统中起作用.
首先,我将解释我目前的情况.
假设我们在一篇文章中有一个评论主题,看起来像下面的例子.parenthasis中的数字是该注释的ID.ID由数据库自动分配,并按时间顺序递增,并发布每个附加注释.注释文本前的短划线数量会重新显示注释深度.
(01)"This is a top level comment."
(02)-"This is a second level comment. A reply to the top level comment above."
(06)-"This is also a second level comment / another reply to comment 01."
(07)--"This is a reply to comment 06."
(03)"This is a different top level comment."
(05)-"This is a reply to the comment above."
(08)--"This is a reply to that comment in turn."
(10)---"This is a deeper comment still."
(04)"This is one more top level comment."
(09)-"This is one more reply."
Run Code Online (Sandbox Code Playgroud)
我的第一个问题是以一种方式存储这些数据,这意味着它可以按正确的顺序返回.如果您只是存储深度字段并按深度排序,它将首先返回所有顶级注释,然后返回第二级注释等.这是不对的,我们必须返回完整的父级仍然完整的注释.
实现此目的的一种方法是为每条评论存储完整的父母.
Comment ID | Parentage
01 | (Comment 01 has no parent because it is top level)
02 | 01- (Comment 02 was a reply to comment 01)
03 |
04 |
05 | 03-
06 | 01-
07 | 01-06- (Comment 07 has two ancestors 01 and then 06)
08 | 03-05-
09 | 04-
10 | 03-05-08-
Run Code Online (Sandbox Code Playgroud)
添加另一条评论记录就像从您回复的评论中获取Parentage一样简单,并附加其ID以形成新的父母.例如,如果我回复评论10,我会接受它的亲子关系(03-05-08-)并附加其ID(10-).数据库会自动将其识别为第11条评论,我们会得到:
Comment ID | Parentage
01 |
02 | 01-
03 |
04 |
05 | 03-
06 | 01-
07 | 01-06-
08 | 03-05-
09 | 04-
10 | 03-05-08-
11 | 03-05-08-10-
Run Code Online (Sandbox Code Playgroud)
现在,当我们订购显示的评论时,我们订购了一个Parentage和Comment ID的串联,它给了我们:
Order by CONCAT(Parentage, ID)
Comment ID | Parentage | CONCAT(Parentage, ID)
01 | | 01-
02 | 01- | 01-02-
06 | 01- | 01-06-
07 | 01-06- | 01-06-07-
03 | | 03-
05 | 03- | 03-05-
08 | 03-05- | 03-05-08-
10 | 03-05-08- | 03-05-08-10-
11 | 03-05-08-10- | 03-05-08-10-11-
04 | | 04-
09 | 04- | 04-09-
Run Code Online (Sandbox Code Playgroud)
其产生与首次演示完全相同的列表.使用注释11,我们后来添加插入到正确的位置:
(01)"This is a top level comment."
(02)-"This is a reply to the top level comment."
(06)-"This is another reply that was posted later than the first."
(07)--"This is a reply to the second level comment directly above."
(03)"This is a different top level comment."
(05)-"This is a reply to the comment above."
(08)--"This is a reply to the comment above."
(10)---"This is a deeper comment still."
(11)----"THIS COMMENT WAS ADDED IN THE EARLIER EXAMPLE."
(04)"This is one more top level comment."
(09)-"This is one more reply."
Run Code Online (Sandbox Code Playgroud)
缩进可以通过检查CONCAT字符串的长度并将len(CONCAT(Parentage,ID))乘以设定的像素数来完成.这很棒,我们有一个以识别其父母的方式存储评论的系统.
现在的问题是:
并非所有评论都是平等的.需要一个评论评分系统来区分好的评论.假设每个评论都有一个upvote按钮..虽然我们想要保留父母,但如果一个评论在同一级别有两个直接回复,那么我们想要首先显示最多的一个评论.我将在下面的[方括号]中添加一些投票.
(01)"This is a top level comment." [6 votes]
(02)-"This is a reply to the top level comment." [2 votes]
(06)-"This is another reply that was posted later than the first." [30 votes]
(07)--"This is a reply to the second level comment directly above." [5 votes]
(03)"This is a different top level comment." [50 votes]
(05)-"This is a reply to the comment above." [4 votes]
(08)--"This is a reply to the comment above." [0 votes]
(10)---"This is a deeper comment still." [0 votes]
(11)----"THIS COMMENT WAS ADDED IN THE EARLIER EXAMPLE." [0 votes]
(04)"This is one more top level comment." [2 votes]
(09)-"This is one more reply." [0 votes]
Run Code Online (Sandbox Code Playgroud)
在这个例子中,评论(01)和(03)都是顶级,但是(03)有[50票]而(01)只有[6票].(01)仅出现在上面的事实,因此它被分配了一个较小的ID.同样地,(02)和(06)都是对(01)的回复,但必须重新排序以允许具有最多票数(06)的那个上升到顶部.
我完全和完全陷入努力实现这一目标.
我想任何排序/重新排序和索引都会在评论投票而不是页面加载时做得更好,这样页面加载时间可以尽可能快,但除此之外,我完全不知道!
你可以在可能的途径上留下的任何想法或亮点真的会减轻负担!一如既往地感谢您的帮助.
编辑:回应@ Paddy的解决方案,
当我在模拟数据上运行@Paddy提供的表达式时,我得到的第一个错误是:
"The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP or FOR XML is also specified."
Run Code Online (Sandbox Code Playgroud)
这可以通过将SELECT"top 100%"添加到递归成员定义来解决.完成后,我收到错误:
'CommentTree' has more columns than were specified in the column list.
Run Code Online (Sandbox Code Playgroud)
这可以通过向CommentTree规范添加"Level"列来解决.然后打印数据,但它首先返回所有顶级注释,然后返回类似(但实际上不匹配)正确排序顺序的内容.
数据如下返回:
ParentId | CommentId | Comment | Vote | Level
NULL | 1 | Text here | 6 | 0
NULL | 3 | Text here | 50 | 0
NULL | 4 | Text here | 2 | 0
4 | 9 | Text here | 0 | 1
3 | 5 | Text here | 4 | 1
5 | 8 | Text here | 0 | 2
8 | 10 | Text here | 0 | 3
10 | 11 | Text here | 0 | 4
1 | 2 | Text here | 2 | 1
1 | 6 | Text here | 30 | 1
6 | 7 | Text here | 5 | 2
Run Code Online (Sandbox Code Playgroud)
我做错了什么或@Paddy错过了什么?请接受我的道歉,递归功能对我来说是非常新的.
下面的代码看起来很适合您的任务。它有点复杂,但将其制作成一个单一的文件对我来说是一个挑战SELECT
。您可以将其拆分为多个SELECT
并预取到临时表中(出于性能目的),或者将其保留在一起。
谢谢你的问题,这很有趣!
请注意,ParentID
对于根节点必须是0
, 不是NULL
。
DECLARE @a TABLE (
CommentID INT,
ParentID INT,
Comment VARCHAR(100),
Vote INT
)
INSERT @a
VALUES
(1, 0, '', 6),
(3, 0, '', 50),
(4, 0, '', 2),
(9, 4, '', 0),
(5, 3, '', 4),
(8, 5, '', 0),
(10, 8, '', 0),
(11, 10, '', 0),
(2, 1, '', 2),
(6, 1, '', 30),
(7, 6, '', 5)
;WITH CTE_1 (ParentId, CommentId, Comment, Vote, Level, LevelPriority, Path) -- prepare base info
AS
(
SELECT c.ParentId, c.CommentId, c.Comment, c.Vote, 0 AS Level, ROW_NUMBER() OVER(ORDER BY c.Vote DESC), CAST('/' + CAST(c.CommentId AS VARCHAR(32)) AS VARCHAR(MAX)) + '/'
FROM @a AS c
WHERE ParentId = 0
UNION ALL
SELECT c.ParentId, c.CommentId, c.Comment, c.Vote, Level + 1 AS Level, ROW_NUMBER() OVER(ORDER BY c.Vote DESC), d.Path + CAST(c.CommentId AS VARCHAR(32)) + '/'
FROM @a AS c
INNER JOIN CTE_1 AS d
ON c.ParentID = d.CommentID
),
CTE_2 (ParentId, CommentId, Comment, Vote, Level, LevelPriority, ChildCount) -- count number of children
AS
(
SELECT p.ParentId, p.CommentId, p.Comment, p.Vote, p.Level, p.LevelPriority, COUNT(*)
FROM CTE_1 AS p
INNER JOIN CTE_1 AS c
ON c.Path LIKE p.Path + '%'
GROUP BY
p.ParentId, p.CommentId, p.Comment, p.Vote, p.Level, p.LevelPriority
),
CTE_3 (ParentId, CommentId, Comment, Vote, Level, LevelPriority, OverAllPriority, ChildCount) -- calculate overall priorities
AS
(
SELECT c.ParentId, c.CommentId, c.Comment, c.Vote, c.Level, c.LevelPriority, 1 AS OverAllPriority, ChildCount
FROM CTE_2 AS c
WHERE Level = 0 AND LevelPriority = 1
UNION ALL
SELECT c.ParentId, c.CommentId, c.Comment, c.Vote, c.Level, c.LevelPriority,
CASE
WHEN c.ParentID = d.CommentID THEN d.OverAllPriority + 1
ELSE d.OverAllPriority + d.ChildCount
END,
c.ChildCount
FROM CTE_2 AS c
INNER JOIN CTE_3 AS d
ON
(c.ParentID = d.CommentID AND c.LevelPriority = 1)
OR (c.ParentID = d.ParentID AND d.LevelPriority + 1 = c.LevelPriority)
)
SELECT ParentId, CommentId, Comment, Vote
FROM CTE_3
ORDER BY OverAllPriority
Run Code Online (Sandbox Code Playgroud)
在此查询中我执行以下操作:
SELECT
。position = 1
position = parent_position + 1
position = prev_sibling_position + prev_sibling_number_of_descendants
编辑相同的解决方案,但没有 CTE。
DECLARE @a TABLE (
CommentID INT,
ParentID INT,
Comment VARCHAR(100),
Vote INT
)
INSERT @a
VALUES
(1, 0, '', 6),
(3, 0, '', 50),
(4, 0, '', 2),
(9, 4, '', 0),
(5, 3, '', 4),
(8, 5, '', 0),
(10, 8, '', 0),
(11, 10, '', 0),
(2, 1, '', 2),
(6, 1, '', 30),
(7, 6, '', 5)
DECLARE @rows INT
DECLARE @temp_table TABLE (
CommentID INT,
ParentID INT,
Comment VARCHAR(100),
Vote INT,
LevelPriority INT,
Path VARCHAR(MAX),
ChildCount INT NULL,
OverAllPriority INT NULL
)
INSERT @temp_table (CommentID, ParentID, Comment, Vote, LevelPriority, Path)
SELECT CommentID, ParentID, Comment, Vote, ROW_NUMBER() OVER(ORDER BY Vote DESC), '/' + CAST(CommentId AS VARCHAR(32)) + '/'
FROM @a
WHERE ParentID = 0
SELECT @rows = @@ROWCOUNT
WHILE @rows > 0
BEGIN
INSERT @temp_table (CommentID, ParentID, Comment, Vote, LevelPriority, Path)
SELECT a.CommentID, a.ParentID, a.Comment, a.Vote, ROW_NUMBER() OVER(PARTITION BY a.ParentID ORDER BY a.Vote DESC), c.Path + CAST(a.CommentId AS VARCHAR(32)) + '/'
FROM @a AS a
INNER JOIN @temp_table AS c
ON a.ParentID = c.CommentID
WHERE NOT
a.CommentID IN (SELECT CommentID FROM @temp_table)
SELECT @rows = @@ROWCOUNT
END
UPDATE c
SET ChildCount = a.cnt
FROM (
SELECT p.CommentID, COUNT(*) AS cnt
FROM @temp_table AS p
INNER JOIN @temp_table AS c
ON c.Path LIKE p.Path + '%'
GROUP BY
p.CommentID
) AS a
INNER JOIN @temp_table AS c
ON a.CommentID = c.CommentID
UPDATE @temp_table
SET OverAllPriority = 1
WHERE ParentID = 0 AND LevelPriority = 1
SELECT @rows = @@ROWCOUNT
WHILE @rows > 0
BEGIN
UPDATE c
SET
OverAllPriority = CASE
WHEN c.ParentID = p.CommentID THEN p.OverAllPriority + 1
ELSE p.OverAllPriority + p.ChildCount
END
FROM @temp_table AS p
INNER JOIN @temp_table AS c
ON (c.ParentID = p.CommentID AND c.LevelPriority = 1)
OR (p.ParentID = c.ParentID AND p.LevelPriority + 1 = c.LevelPriority)
WHERE
c.OverAllPriority IS NULL
AND p.OverAllPriority IS NOT NULL
SELECT @rows = @@ROWCOUNT
END
SELECT * FROM @temp_table
ORDER BY OverAllPriority
Run Code Online (Sandbox Code Playgroud)