如何显示基于赞成票的递归评论?
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);
我的网站上有一个评论部分 (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
.
找到每个节点的 depth
很容易 - 只需在执行第二个 select.
更棘手的部分是获得您需要的排序顺序(通过投票和 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);