postgresql 在SQL中使用递归CTE遍历前序树

0s0u357o  于 2023-10-18  发布在  PostgreSQL
关注(0)|答案(1)|浏览(127)

我需要遍历一个节点树,以生成一个菜单列表,到目前为止,我已经想出了这个递归CTE:

  1. WITH RECURSIVE nodes AS (
  2. SELECT '' as spacer,
  3. 1::numeric as level,
  4. (rank() OVER (PARTITION BY parent_id order by name ASC ))::numeric as node_rank,
  5. node_tree.*
  6. FROM node_tree
  7. where parent_id IS NULL
  8. UNION
  9. SELECT parent.spacer || '-' as spacer,
  10. parent.level * 10.0 as level,
  11. parent.node_rank + (rank() OVER (PARTITION BY node.parent_id order by node.name ASC )) / (parent.level * 10.0) as node_rank,
  12. node.*
  13. FROM node_tree node
  14. join nodes parent
  15. on parent.id = node.parent_id
  16. )
  17. SELECT
  18. trim(spacer || ' ' || name) as item_name,
  19. id,
  20. parent_id,
  21. name
  22. FROM nodes
  23. order by node_rank nulls first, name

假设我需要保持表的自引用结构,有没有更好的方法?
这是最小的模式和示例数据:

  1. CREATE TABLE node_tree (
  2. id uuid primary key,
  3. parent_id uuid default null,
  4. name text
  5. );
  6. ALTER TABLE node_tree ADD CONSTRAINT node_parent_check
  7. CHECK (parent_id is NULL OR parent_id <> id);
  8. ALTER TABLE node_tree
  9. ADD CONSTRAINT node_parent_fk
  10. FOREIGN KEY (parent_id)
  11. REFERENCES node_tree(id);
  12. INSERT INTO node_tree (id,parent_id,name) VALUES
  13. ('B4352249-3BCC-4110-964B-368AAA2DCE4C', null, 'Node 1'),
  14. ('AE3C21B1-9F4E-4C62-A2C7-2C729591CE60', 'B4352249-3BCC-4110-964B-368AAA2DCE4C', 'Node 1.1'),
  15. ('CED81F20-6598-42EA-95F8-2D5E8B1FCA73', 'AE3C21B1-9F4E-4C62-A2C7-2C729591CE60', 'Node 1.1.1'),
  16. ('9B2BC4D7-D387-4726-A3E2-2466883152CC', 'AE3C21B1-9F4E-4C62-A2C7-2C729591CE60', 'Node 1.1.2'),
  17. ('1492A9D9-D24F-47E6-9926-9D198B7F57A0', 'B4352249-3BCC-4110-964B-368AAA2DCE4C', 'Node 1.2'),
  18. ('F1B22F5B-34A7-4A92-9792-C2D023147E4D', '1492A9D9-D24F-47E6-9926-9D198B7F57A0', 'Node 1.2.1'),
  19. ('5B0AA9B2-1DB8-4DDE-A184-77872F2BC990', '1492A9D9-D24F-47E6-9926-9D198B7F57A0', 'Node 1.2.2'),
  20. ('7450E2EC-3E20-45F0-BE59-9F5BEE7D2955', null, 'Node 2'),
  21. ('F60B7772-D6E3-42A7-8AAE-D6CB862F4D96', '7450E2EC-3E20-45F0-BE59-9F5BEE7D2955', 'Node 2.1'),
  22. ('954C6010-DA37-41B1-8933-93B15552E6CB', 'F60B7772-D6E3-42A7-8AAE-D6CB862F4D96', 'Node 2.1.1'),
  23. ('2F3EBEC7-B038-4E7D-B7AF-F22D524B1AF8', '954C6010-DA37-41B1-8933-93B15552E6CB', 'Node 2.1.1.1'),
  24. ('4BA13434-2DA5-4913-B9F6-5AFEF4892000', '954C6010-DA37-41B1-8933-93B15552E6CB', 'Node 2.1.1.2'),
  25. ('337238CC-FED0-40D2-B54B-603A36C075A0', 'F60B7772-D6E3-42A7-8AAE-D6CB862F4D96', 'Node 2.1.2'),
  26. ('F7996999-D024-428A-BF61-78958695DDB3', null, 'Node 3'),
  27. ('81BAE0B8-70CF-4152-BCBF-632964E30904', 'F7996999-D024-428A-BF61-78958695DDB3', 'Node 3.1'),
  28. ('E88F9BD9-4288-46FC-9972-215623BE3949', '81BAE0B8-70CF-4152-BCBF-632964E30904', 'Node 3.1.1'),
  29. ('992AE037-9DAA-49D9-A725-AC1BBCE46037', null, 'Node 4'),
  30. ('9CFC7634-E1DA-4AC3-9D9D-4995B28EB148', '992AE037-9DAA-49D9-A725-AC1BBCE46037', 'Node 4.1'),
  31. ('E53C022B-1E4E-4D6F-A740-26A23211BF1F', '9CFC7634-E1DA-4AC3-9D9D-4995B28EB148', 'Node 4.1.1');

这是一个DB小提琴:DB Fiddle

hmtdttj4

hmtdttj41#

arrays的路径枚举

这将为深度嵌套的树占用大量“不必要”的内存,但这是我能想到的最好的方法,至少在某些用例中应该有效。
表:

  1. psql <<'EOF'
  2. DROP TABLE IF EXISTS "tmp";
  3. CREATE TABLE "tmp" (
  4. "id" INTEGER PRIMARY KEY,
  5. "parentId" INTEGER,
  6. "childIndex" INTEGER NOT NULL,
  7. "value" INTEGER NOT NULL,
  8. "name" TEXT NOT NULL,
  9. FOREIGN KEY ("parentId") REFERENCES "tmp"("id")
  10. );
  11. EOF

数据类型:

  1. psql <<'EOF'
  2. INSERT INTO "tmp" VALUES
  3. (3, NULL, 0, 1, 'one' ),
  4. (1, 3, 0, 2, 'two' ),
  5. (2, 3, 1, 3, 'three'),
  6. (0, 1, 0, 4, 'four' ),
  7. (4, 1, 1, 5, 'five' )
  8. EOF

代表树:

  1. 1
  2. / \
  3. 2 3
  4. / \
  5. 4 5

查询方式:

  1. psql <<'EOF'
  2. WITH RECURSIVE "cte" (
  3. "id",
  4. "parentId",
  5. "childIndex",
  6. "value",
  7. "name",
  8. "prefix"
  9. ) AS (
  10. SELECT
  11. "id",
  12. "parentId",
  13. "childIndex",
  14. "value",
  15. "name",
  16. array[0]
  17. FROM "tmp"
  18. WHERE "parentId" IS NULL
  19. UNION ALL
  20. SELECT
  21. "child"."id",
  22. "child"."parentId",
  23. "child"."childIndex",
  24. "child"."value",
  25. "child"."name",
  26. "parent"."prefix" || "child"."childIndex"
  27. FROM "tmp" AS "child"
  28. JOIN "cte" AS "parent"
  29. ON "child"."parentId" = "parent"."id"
  30. )
  31. SELECT * FROM "cte"
  32. ORDER BY "prefix"
  33. EOF

按所需的前序输出:

  1. id | parentId | childIndex | value | name | prefix
  2. ----+----------+------------+-------+-------+---------
  3. 3 | | 0 | 1 | one | {0}
  4. 1 | 3 | 0 | 2 | two | {0,0}
  5. 0 | 1 | 0 | 4 | four | {0,0,0}
  6. 4 | 1 | 1 | 5 | five | {0,0,1}
  7. 2 | 3 | 1 | 3 | three | {0,1}

SEARCH是一个快捷关键字,它可以产生完全相同的查询,但代码更少:

  1. psql <<'EOF'
  2. WITH RECURSIVE "cte" (
  3. "id",
  4. "parentId",
  5. "childIndex",
  6. "value",
  7. "name"
  8. ) AS (
  9. SELECT
  10. "id",
  11. "parentId",
  12. "childIndex",
  13. "value",
  14. "name"
  15. FROM "tmp"
  16. WHERE "parentId" IS NULL
  17. UNION ALL
  18. SELECT
  19. "child"."id",
  20. "child"."parentId",
  21. "child"."childIndex",
  22. "child"."value",
  23. "child"."name"
  24. FROM "tmp" AS "child"
  25. JOIN "cte" AS "parent"
  26. ON "child"."parentId" = "parent"."id"
  27. )
  28. SEARCH DEPTH FIRST BY "childIndex" SET "prefix"
  29. SELECT * FROM "cte"
  30. ORDER BY "prefix"
  31. EOF

技巧是创建一个prefix数组列,它注册每个元素的完整路径,然后按字典顺序进行排序。
本手册中还记录了这一点和其他一些相关技术,网址为:https://www.postgresql.org/docs/16/queries-with.html#QUERIES-WITH-RECURSIVE
在Postgresql 15.4,Ubuntu 23.04上测试。
标签:https://dba.stackexchange.com/questions/63153/how-do-i-sort-the-results-of-a-recursive-query-in-an-expanded-tree-like-fashion

展开查看全部

相关问题