The code below looks good for your task. It is a bit complicated, but for me it was a difficult task to do it in one SELECT . You can split it into multiple SELECT with prefetching into temporary tables (to improve performance) or save them together.
Thanks for the question, it was interesting!
Note that the ParentID for root nodes must be 0 , not 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
In this question, I do the following:
- In CTE_1, I compute the ordering positions within the same parent comment (based on votes) and create a tree path to collect information about all nodes in the hierarchy.
- In CTE_2, I calculate the number of children belonging to each node +1. The tree path allows you to count the descendants of the entire level by one
SELECT . - In CTE_3, I calculate the general ordering positions based on three simple rules:
- The topmost line has
position = 1 - The top child node has
position = parent_position + 1 - The next brother should go after all descendatns of the previous one and has
position = prev_sibling_position + prev_sibling_number_of_descendants
EDIT The same solution, but without 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