Поток комментариев с факторингом оценки - PullRequest
8 голосов
/ 08 февраля 2012

Я бьюсь головой о что-то, и мне было интересно, может ли кто-нибудь более опытный, чем я, помочь мне.

Моя цель - создать ветку комментариев, которая учитывает систему оценки комментариев.

Сначала я объясню, где я сейчас нахожусь.

Скажем, у нас есть ветка комментариев к статье, которая выглядит как пример ниже. Число в скобках является идентификатором этого комментария. Идентификаторы присваиваются автоматически базой данных и увеличиваются в хронологическом порядке с каждым дополнительным комментарием. Количество тире перед текстом комментария соответствует глубине комментария.

(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."

Моей первой проблемой было сохранение этих данных таким образом, чтобы они могли быть возвращены в правильном порядке. Если вы просто сохраните поле глубины и упорядочите по глубине, то сначала вернутся все комментарии верхнего уровня, а затем комментарии второго уровня и т. Д. Это не правильно, мы должны вернуть комментарии с полным происхождением без изменений.

Один из способов добиться этого - сохранить полное происхождение для каждого комментария.

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-

Добавление еще одной записи комментария так же просто, как извлечь происхождение из комментария, на который вы отвечаете, и добавить его идентификатор, чтобы сформировать новое происхождение. Например, если бы я отвечал на комментарий 10, я взял бы его происхождение (03-05-08-) и добавил его идентификатор (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-

Теперь, когда мы заказываем комментарии для отображения, мы заказываем объединение идентификаторов происхождения и комментариев, которое дает нам:

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-

Который производит тот же список, что и первый продемонстрированный. С комментарием 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."

Отступ можно сделать, проверив длину строки 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]

В этом примере комментарии (01) и (03) имеют высший уровень, но (03) имеет [50 голосов] и (01) имеет только [6 голосов]. (01) появляется выше только в силу того факта, что он был опубликован ранее и, следовательно, ему был присвоен меньший идентификатор. Аналогичным образом (02) и (06) оба являются ответами на (01), но должны быть переупорядочены, чтобы позволить человеку с наибольшим количеством голосов (06) подняться на вершину.

Я полностью и совершенно застрял в попытке достичь этого.

Я полагаю, что любое упорядочение / переупорядочение и индексирование было бы лучше выполнять при голосовании по комментариям, а не при загрузке страницы, так что время загрузки страницы может быть как можно более коротким, но за этим я абсолютно не знаю!

Любые идеи или свет, которые вы могли бы пролить на возможные пути, действительно сняли бы нагрузку! Спасибо за вашу помощь, как всегда.

----------------------------------------------- ---------------------------------

Редактировать: в ответ на решение @ Пэдди,

Когда я запускаю приведенное ниже @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." 

Это можно исправить, добавив SELECT 'top 100%' к определению рекурсивного члена. Как только это будет сделано, я получаю сообщение об ошибке:

'CommentTree' has more columns than were specified in the column list.

Эту проблему можно решить, добавив столбец «Уровень» в спецификацию CommentTree. Затем он печатает данные, но сначала возвращает все комментарии верхнего уровня, а затем что-то, напоминающее (но на самом деле не совпадающее) правильный порядок сортировки после.

Данные возвращаются как таковые:

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    

Я сделал что-то не так или @Paddy что-то упустил? Пожалуйста, примите мои извинения, рекурсивные функции очень новы для меня.

Ответы [ 3 ]

4 голосов
/ 11 мая 2013

Код ниже выглядит хорошо для вашей задачи. Это немного сложно, но мне было сложно сделать это за один 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

В этом запросе я делаю следующее:

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

Хотя это не имеет прямого отношения к вашему вопросу, я бы посоветовал перейти на модель Nested Set *1002*. Я знаю, что это большая переделка, но рано или поздно вы поймете, что это лучший выбор:)

0 голосов
/ 08 февраля 2012

Использование определения таблицы примерно так (ключ самореференции):

Comment ID  |   Parent ID    |   Comment    |  Vote

Затем вы можете использовать рекурсивное общее табличное выражение (это в MS SQL), чтобы получить ваши результаты:

WITH CommentTree (ParentId, CommentId, Comment, Vote)
AS
(
-- Anchor member definition
    SELECT c.ParentId, c.CommentId, c.Comment, c.Vote,
        0 AS Level
    FROM dbo.Comments AS c
    WHERE ParentId IS NULL
    UNION ALL
-- Recursive member definition
    SELECT c.ParentId, c.CommentId, c.Comment, c.Vote,
        Level + 1 AS Level
    FROM dbo.Comments AS c
    INNER JOIN CommentTree AS d
        ON c.ParentID = d.CommentID
    Order by C.Vote
)
SELECT ParentId, CommentId, Comment, Vote FROM CommentTree

CTE ссылка:

http://msdn.microsoft.com/en-us/library/ms186243.aspx

...