如何显示基于赞成票的递归评论?

How to show recursive comments based on upvotes?

我的网站上有一个评论部分 (privacyfirstproducts.com),我很乐意以这种结构显示评论(就像黑客新闻一样):

comment 1 (10 upvotes)
  comment 4 (reply on comment 1, 7 upvotes)
  comment 5 (reply on comment 1, 5 upvotes)
    comment 8 (reply on comment 5, 8 upvotes)
      ...
  comment 9 (reply on comment 1, 3 upvotes)
  comment 3 (reply on comment 1, 0 upvotes)
  comment 10 (reply on comment 1, 0 upvotes)
  ...
comment 6 (2 upvotes)
  comment 7 (reply on comment 3, 2 upvotes)
comment 2 (0 upvotes)
...

我有这个 postgresql comments-table:

comment_id | original_id | upvotes | text | ...
------------------------------------------------------
         1 |        NULL |      10 | Hi.. | ...
         2 |        NULL |       0 | Je.. | ...
         3 |           1 |       0 | Di.. | ...
         4 |           1 |       7 | Si.. | ...
         5 |           1 |       5 | Op.. | ...
         6 |        NULL |       2 | Op.. | ...
         7 |           6 |       2 | Op.. | ...
         8 |           5 |       8 | Op.. | ...
         9 |           1 |       3 | Op.. | ...
        10 |           1 |       0 | Th.. | ...

我希望将其作为 postgresql 的输出:

comment_id | original_id | upvotes | deep | text | ...
------------------------------------------------------
         1 |        NULL |      10 |    0 | Hi.. | ...
         4 |           1 |       7 |    1 | Si.. | ...
         5 |           1 |       5 |    1 | Op.. | ...
         8 |           5 |       8 |    2 | Op.. | ...
         9 |           1 |       3 |    1 | Op.. | ...
         3 |           1 |       0 |    1 | Di.. | ...
        10 |           1 |       0 |    1 | Th.. | ...
         6 |        NULL |       2 |    0 | Op.. | ...
         7 |           6 |       2 |    1 | Op.. | ...
         2 |        NULL |       0 |    0 | Je.. | ...

我想这应该用递归来完成,但我不知道怎么做。

递归查询记录在手册的 CTE 部分。

您从 select 根行开始(在您的例子中,是顶级评论;original_id IS NULL 所在的评论)。

递归查询的第二部分(在下面示例中的 UNION 之后)将 child 评论加入到已经找到的评论中。它会自动重复,直到找不到更多行。在你的情况下,第二个 select 需要在 child.original_id = parent.comment_id.

上加入 child 对 parent 的评论

找到每个节点的 depth 很容易 - 只需在执行第二个 select.

时将 parent 行的深度加 1

更棘手的部分是获得您需要的排序顺序(通过投票和 ID,将评论按 parent 分组)。这可以通过在一个数组(下例中的 path 列)中累积投票以及每个评论的祖先 ID,然后按数组对行进行排序来完成。请注意,示例中的投票计数已被否定,以便首先对较高的值进行排序。这可以通过排序 DESC 来完成,但是当评论具有相同的票数时,评论 ID 必须取反才能首先对较早的评论进行排序。

WITH RECURSIVE comment_tree AS (
  -- First select performed to get top level rows
  SELECT
     comment_id,
     original_id,
     upvotes,
     text,
     0 depth,                           -- depth in the tree
     ARRAY[-upvotes, comment_id] path   -- used to sort by vote then ID
  FROM comment WHERE original_id IS NULL
  UNION
  -- Self referential select performed repeatedly until no more rows are found
  SELECT
    c.comment_id,
    c.original_id,
    c.upvotes,
    c.text,
    ct.depth + 1,
    ct.path || ARRAY[-c.upvotes, c.comment_id]
  FROM comment c
    JOIN comment_tree ct ON c.original_id = ct.comment_id
)
SELECT * FROM comment_tree ORDER BY path;

Javascript 解决方案可以通过 id 对评论进行索引,遍历它们并让它们指向正确的父级(通过使用索引)。最后我们可以 return 来自索引的根节点(original_id === null):

const comments = [
  { id: 1, original_id: null, upvotes: 10, text: 'Hi..' },
  { id: 2, original_id: null, upvotes: 0, text: 'Je..' },
  { id: 3, original_id: 1, upvotes: 0, text: 'Di..' },
  { id: 4, original_id: 1, upvotes: 7, text: 'Si..' },
  { id: 5, original_id: 1, upvotes: 5, text: 'Op..' },
  { id: 6, original_id: null, upvotes: 2, text: 'Op..' },
  { id: 7, original_id: 6, upvotes: 2, text: 'Op..' },
  { id: 8, original_id: 5, upvotes: 3, text: 'Op..' },
  { id: 9, original_id: 1, upvotes: 3, text: 'Op..' }
];

let index = comments.reduce((a, c) => {
  let comment = Object.assign({}, c);
  comment.children = [];
  a.set(c.id, comment);
  return a;
}, new Map());

Array.from(index.values()).forEach(comment => {
  if (comment.original_id) index.get(comment.original_id).children.push(comment)
});

const res = Array.from(index.values()).filter(c => c.original_id === null);
console.log(res);