具有递归 CTE 的 Postgres:在保留树结构的同时按受欢迎程度对子节点进行排序/排序(父节点始终高于子节点)
Posted
技术标签:
【中文标题】具有递归 CTE 的 Postgres:在保留树结构的同时按受欢迎程度对子节点进行排序/排序(父节点始终高于子节点)【英文标题】:Postgres WITH RECURSIVE CTE: sorting/ordering children by popularity while retaining tree structure (parents always above children) 【发布时间】:2017-07-03 05:16:21 【问题描述】:我正在建立一个论坛,非常类似于 Reddit/Slashdot,即
无限回复嵌套级别 流行的 cmets(按赞/投票排序)将上升到顶部(在它们自己的嵌套/深度级别内),但需要保留树结构(父级始终显示在子级的正上方)这是一个示例表和数据:
DROP TABLE IF EXISTS "comments";
CREATE TABLE comments (
id BIGINT PRIMARY KEY,
parent_id BIGINT,
body TEXT NOT NULL,
like_score BIGINT,
depth BIGINT
);
INSERT INTO comments VALUES ( 0, NULL, 'Main top of thread post', 5 , 0 );
INSERT INTO comments VALUES ( 1, 0, 'comment A', 5 , 1 );
INSERT INTO comments VALUES ( 2, 1, 'comment A.A', 3, 2 );
INSERT INTO comments VALUES ( 3, 1, 'comment A.B', 1, 2 );
INSERT INTO comments VALUES ( 9, 3, 'comment A.B.A', 10, 3 );
INSERT INTO comments VALUES ( 10, 3, 'comment A.B.B', 5, 3 );
INSERT INTO comments VALUES ( 11, 3, 'comment A.B.C', 8, 3 );
INSERT INTO comments VALUES ( 4, 1, 'comment A.C', 5, 2 );
INSERT INTO comments VALUES ( 5, 0, 'comment B', 10, 1 );
INSERT INTO comments VALUES ( 6, 5, 'comment B.A', 7, 2 );
INSERT INTO comments VALUES ( 7, 5, 'comment B.B', 5, 2 );
INSERT INTO comments VALUES ( 8, 5, 'comment B.C', 2, 2 );
这是我到目前为止提出的递归查询,但我不知道如何为子级排序,但保留树结构(父级应始终在子级之上)...
WITH RECURSIVE tree AS (
SELECT
ARRAY[]::BIGINT[] AS sortable,
id,
body,
like_score,
depth
FROM "comments"
WHERE parent_id IS NULL
UNION ALL
SELECT
tree.sortable || "comments".like_score || "comments".id,
"comments".id,
"comments".body,
"comments".like_score,
"comments".depth
FROM "comments", tree
WHERE "comments".parent_id = tree.id
)
SELECT * FROM tree
ORDER BY sortable DESC
这输出...
+----------------------------------------------------------+
|sortable |id|body |like_score|depth|
+----------------------------------------------------------+
|10,5,7,6 |6 |comment B.A |7 |2 |
|10,5,5,7 |7 |comment B.B |5 |2 |
|10,5,2,8 |8 |comment B.C |2 |2 |
|10,5 |5 |comment B |10 |1 |
|5,1,5,4 |4 |comment A.C |5 |2 |
|5,1,3,2 |2 |comment A.A |3 |2 |
|5,1,1,3,10,9|9 |comment A.B.A |10 |3 |
|5,1,1,3,8,11|11|comment A.B.C |8 |3 |
|5,1,1,3,5,10|10|comment A.B.B |5 |3 |
|5,1,1,3 |3 |comment A.B |1 |2 |
|5,1 |1 |comment A |5 |1 |
| |0 |Main top of thread post|5 |0 |
+----------------------------------------------------------+
...但是请注意,“评论 B”、“评论 A”和“主题帖子的主顶部”在他们的孩子下方?如何保持上下文顺序?即我想要的输出是:
+----------------------------------------------------------+
|sortable |id|body |like_score|depth|
+----------------------------------------------------------+
| |0 |Main top of thread post|5 |0 |
|10,5 |5 |comment B |10 |1 |
|10,5,7,6 |6 |comment B.A |7 |2 |
|10,5,5,7 |7 |comment B.B |5 |2 |
|10,5,2,8 |8 |comment B.C |2 |2 |
|5,1 |1 |comment A |5 |1 |
|5,1,5,4 |4 |comment A.C |5 |2 |
|5,1,3,2 |2 |comment A.A |3 |2 |
|5,1,1,3 |3 |comment A.B |1 |2 |
|5,1,1,3,10,9|9 |comment A.B.A |10 |3 |
|5,1,1,3,8,11|11|comment A.B.C |8 |3 |
|5,1,1,3,5,10|10|comment A.B.B |5 |3 |
+----------------------------------------------------------+
我实际上希望用户能够通过多种方法进行排序:
最受欢迎的第一名 最不受欢迎的第一 最新第一 从大到小 等...但是在所有情况下,父母都需要显示在他们的孩子之上。但是我这里只是以“like_score”为例,其余的应该可以从那里算出来。
花了很多时间研究网络并亲自尝试,感觉自己已经接近了,但无法弄清楚最后一部分。
【问题讨论】:
【参考方案1】:1.
tree.sortable || -"comments".like_score || "comments".id
^
/|\
|
|
2.
ORDER BY sortable
WITH RECURSIVE tree AS (
SELECT
ARRAY[]::BIGINT[] AS sortable,
id,
body,
like_score,
depth
FROM "comments"
WHERE parent_id IS NULL
UNION ALL
SELECT
tree.sortable || -"comments".like_score || "comments".id,
"comments".id,
"comments".body,
"comments".like_score,
"comments".depth
FROM "comments", tree
WHERE "comments".parent_id = tree.id
)
SELECT * FROM tree
ORDER BY sortable
+-------------------+----+-------------------------+------------+-------+
| sortable | id | body | like_score | depth |
+-------------------+----+-------------------------+------------+-------+
| (null) | 0 | Main top of thread post | 5 | 0 |
+-------------------+----+-------------------------+------------+-------+
| -10,5 | 5 | comment B | 10 | 1 |
+-------------------+----+-------------------------+------------+-------+
| -10,5,-7,6 | 6 | comment B.A | 7 | 2 |
+-------------------+----+-------------------------+------------+-------+
| -10,5,-5,7 | 7 | comment B.B | 5 | 2 |
+-------------------+----+-------------------------+------------+-------+
| -10,5,-2,8 | 8 | comment B.C | 2 | 2 |
+-------------------+----+-------------------------+------------+-------+
| -5,1 | 1 | comment A | 5 | 1 |
+-------------------+----+-------------------------+------------+-------+
| -5,1,-5,4 | 4 | comment A.C | 5 | 2 |
+-------------------+----+-------------------------+------------+-------+
| -5,1,-3,2 | 2 | comment A.A | 3 | 2 |
+-------------------+----+-------------------------+------------+-------+
| -5,1,-1,3 | 3 | comment A.B | 1 | 2 |
+-------------------+----+-------------------------+------------+-------+
| -5,1,-1,3,-10,9 | 9 | comment A.B.A | 10 | 3 |
+-------------------+----+-------------------------+------------+-------+
| -5,1,-1,3,-8,11 | 11 | comment A.B.C | 8 | 3 |
+-------------------+----+-------------------------+------------+-------+
| -5,1,-1,3,-5,10 | 10 | comment A.B.B | 5 | 3 |
+-------------------+----+-------------------------+------------+-------+
【讨论】:
太棒了,谢谢!我之前确实尝试了一些0-field
的东西,但是在错误的地方,错误的 ASC/DESC 顺序......这么近,但这么远,哈哈!所以我猜|| "comments".id
现在是多余的,我可以删除它吗?感谢您对差异的非常清晰的解释,并包括示例输出,使其非常容易理解。
不客气 :-) 我不会删除 id
,因为在同一级别中 like_score
相等的情况下它是一个平局
哦,是的,你是对的,不知道为什么我认为我不再需要它了。另外,您是如何生成 ASCII 输出表的?您是否使用为您生成它的 SQL GUI?
tablesgenerator.com/text_tables (File-->Paste table data-->Load,双击底部的ascii表,复制,粘贴到SO并使用Ctrl-K将文本改为格式化代码)【参考方案2】:
检查一下:
WITH RECURSIVE tree AS (
SELECT
ARRAY[]::BIGINT[] AS sortable,
id,
body,
like_score,
depth,
lpad(id::text, 2, '0') as path
FROM "comments"
WHERE parent_id IS NULL
UNION ALL
SELECT
tree.sortable || "comments".like_score || "comments".id,
"comments".id,
"comments".body,
"comments".like_score,
"comments".depth,
tree.path || '/' || lpad("comments".id::text, 2, '0') as path
FROM "comments", tree
WHERE "comments".parent_id = tree.id
)
SELECT * FROM tree
ORDER BY path
请注意,您可以将lpad
上的参数2
替换为您想要的任意位数。
【讨论】:
根据请求的结果验证您的结果 @DuduMarkovitz 你是对的。你的答案是正确的。以上是关于具有递归 CTE 的 Postgres:在保留树结构的同时按受欢迎程度对子节点进行排序/排序(父节点始终高于子节点)的主要内容,如果未能解决你的问题,请参考以下文章