Код ниже выглядит хорошо для вашей задачи. Это немного сложно, но мне было сложно сделать это за один 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
В этом запросе я делаю следующее:
- В CTE_1 я вычисляю позиции заказа в пределах одного и того же родительского комментария (на основе голосов) и строю путь к дереву для сбора информации обо всех узлах в иерархии.
- В CTE_2 я вычисляю количество потомков, принадлежащих каждому узлу +1. Древовидный путь позволяет подсчитывать потомков всех уровней на один
SELECT
.
- В CTE_3 я рассчитываю общие позиции заказа на основе 3 простых правил:
- Самый верхний ряд имеет
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