具有递归 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:在保留树结构的同时按受欢迎程度对子节点进行排序/排序(父节点始终高于子节点)的主要内容,如果未能解决你的问题,请参考以下文章

MariaDB表表达式:CTE

在 C# 中模拟 CTE 递归

SQL Server 公用表表达式(CTE)实现递归

SQL Server 公用表表达式(CTE)实现递归

Postgres递归查询以在遍历parent_id时更新字段的值

带参数的存储过程中每一行的递归 CTE