AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 292572
Accepted
Blender
Blender
Asked: 2021-06-01 15:53:17 +0800 CST2021-06-01 15:53:17 +0800 CST 2021-06-01 15:53:17 +0800 CST

如何将一组扁平的树变成一棵有多个叶子的树?

  • 772

我们有这个漂亮的 Postgres 树生成器。然而,它会同时产生一棵树的切割,而不是一棵整棵树:

item_id jsonb_pretty
1   {
    "title": "PARENT",
    "item_id": 1,
    "children": {
        "title": "LEVEL 2",
        "item_id": 2,
        "children": {
            "title": "LEVEL 3.2",
            "item_id": 6
        }
    }
}
1   {
    "title": "PARENT",
    "item_id": 1,
    "children": {
        "title": "LEVEL 2",
        "item_id": 2,
        "children": {
            "title": "LEVEL 3.1",
            "item_id": 3,
            "children": {
                "title": "LEVEL 4.1",
                "item_id": 4
            }
        }
    }
}
1   {
    "title": "PARENT",
    "item_id": 1,
    "children": {
        "title": "LEVEL 2",
        "item_id": 2,
        "children": {
            "title": "LEVEL 3.1",
            "item_id": 3,
            "children": {
                "title": "LEVEL 4.2",
                "item_id": 5
            }
        }
    }
}

我想用这样的数组从中取出一个树对象:


1   {
    "title": "PARENT",
    "item_id": 1,
    "children": [{
        "title": "LEVEL 2",
        "item_id": 2,
        "children": [{
            "title": "LEVEL 3.2",
            "item_id": 6
        },
        {
            "title": "LEVEL 3.1",
            "item_id": 3,
            "children": [{
                "title": "LEVEL 4.1",
                "item_id": 4
            },
            {
                "title": "LEVEL 4.2",
                "item_id": 5
            }]
        }]
    }]
}

这是生成器:

CREATE TABLE items (
  item_id     serial PRIMARY KEY,
  title text
);
CREATE TABLE joins (
  id          serial PRIMARY KEY,
  item_id     int,
  child_id    int
);
INSERT INTO items (item_id,title) VALUES
  (1,'PARENT'),
  (2,'LEVEL 2'),
  (3,'LEVEL 3.1'),
  (4,'LEVEL 4.1'),
  (5,'LEVEL 4.2'),
  (6,'LEVEL 3.2');
INSERT INTO joins (item_id, child_id) VALUES
  (1,2),
  (2,3),
  (3,4),
  (3,5),
  (2,6);

WITH RECURSIVE t(item_id, json, level) AS (
        SELECT item_id, to_jsonb(items), 1
        FROM items
        WHERE NOT EXISTS (
                SELECT 2
                FROM joins
                WHERE items.item_id = joins.item_id
        )
        UNION ALL
        SELECT parent.item_id, to_jsonb(parent) || jsonb_build_object( 'children', t.json ),
               level + 1
        FROM t
        JOIN joins AS j
                ON t.item_id = j.child_id
        JOIN items AS parent
                ON j.item_id = parent.item_id
        WHERE level < 7
)
SELECT item_id, jsonb_pretty(json)
FROM t
WHERE item_id = 1;

我将如何更改这样的生成器以在 Postgres 13+ 中生成一棵树?

postgresql json
  • 1 1 个回答
  • 643 Views

1 个回答

  • Voted
  1. Best Answer
    Erwin Brandstetter
    2021-06-10T17:18:27+08:002021-06-10T17:18:27+08:00

    递归 CTE ( rCTE ) 是遵循路径并识别树的所有节点的正确工具。但它不允许在递归项中进行聚合。有一个优雅的选择:

    递归函数

    CREATE OR REPLACE FUNCTION f_item_tree(_item_id int, _level int = 7)
      RETURNS jsonb
      LANGUAGE sql STABLE PARALLEL SAFE AS
    $func$
    SELECT CASE WHEN _level > 1
                THEN jsonb_agg(sub)
                ELSE CASE WHEN count(*) > 0 THEN to_jsonb(count(*) || ' - stopped recursion at max level') END
           END
    FROM  (
       SELECT i.*, CASE WHEN _level > 1 THEN f_item_tree(i.item_id, _level - 1) END AS children
       FROM   joins j
       JOIN   items i ON i.item_id = j.child_id
       WHERE  j.item_id = _item_id
       ORDER  BY i.item_id
       ) sub
    $func$;
    

    db<>在这里摆弄

    使用默认最大级别 (7) 调用:

    SELECT jsonb_pretty(jsonb_strip_nulls(to_jsonb(sub))) AS tree
    FROM  (
       SELECT *, f_item_tree(item_id) AS children
       FROM   items
       WHERE  item_id = 1  -- root item_id HERE
       ) sub;
    

    产生您想要的结果:

    {
        "title": "PARENT",
        "item_id": 1,
        "children": [
            {
                "title": "LEVEL 2",
                "item_id": 2,
                "children": [
                    {
                        "title": "LEVEL 3.1",
                        "item_id": 3,
                        "children": [
                            {
                                "title": "LEVEL 4.1",
                                "item_id": 4
                            },
                            {
                                "title": "LEVEL 4.2",
                                "item_id": 5
                            }
                        ]
                    },
                    {
                        "title": "LEVEL 3.2",
                        "item_id": 6
                    }
                ]
            }
        ]
    }
    

    只需 3 个级别即可调用:

    SELECT jsonb_pretty(jsonb_strip_nulls(to_jsonb(sub))) AS tree
    FROM  (
       SELECT *, f_item_tree(item_id, 3) AS children  -- max level HERE
       FROM   items
       WHERE  item_id = 1  -- root item_id HERE
       ) sub;
    

    请注意递归过早停止时的特殊输出:

    {
        "title": "PARENT",
        "item_id": 1,
        "children": [
            {
                "title": "LEVEL 2",
                "item_id": 2,
                "children": [
                    {
                        "title": "LEVEL 3.1",
                        "item_id": 3,
                        "children": "2 - stopped recursion at max level"
                    },
                    {
                        "title": "LEVEL 3.2",
                        "item_id": 6
                    }
                ]
            }
        ]
    }
    

    当然,外部jsonb_pretty()是可选的。

    您可以将外部调用包装到另一个函数中或将其塞进同一个函数中,可能使用 PL/pgSQL 并使用IF. 这是品味和风格的问题。我在这里保持简单。

    这是实际的递归,而rCTE 实际上只是iterate。你需要理解这个概念才能理解函数:它调用自己。每次调用减少最大值_level是最终终止递归的原因。

    我为最后一个递归级别构建了特殊行为:如果最大级别切断了更多子级,该函数会插入一个计数和一个提示。

    (可选!)调用jsonb_strip_nulls()...

    从给定的 JSON 值中递归删除所有具有空值的对象字段。

    删除空的子字段(带NULL值)。如果您想保留其他具有 NULL 值的字段,请考虑jsonb_set_lax()在函数中代替,就像下面使用纯 SQL 解决方案一样。

    理想情况下,您有一个索引joins(item_id, child_id)- 或该索引隐式附带的UNIQUE约束。(item_id, child_id)

    有关的:

    • 对于每个类别,使用 PostgreSQL 递归 CTE 查找所有子类别中外键项的计数

    纯 SQL

    虽然只有一堆级别,但您也可以拼出级联子查询或具有聚合的非递归 CTE。

    查询变得相当冗长,但与原始查询相比,性能应该仍然不错。很大程度上是因为我恢复了最初的查询方法。由于您对给定的根 ID 感兴趣,因此从根而不是叶子开始应该更有效- 坚持树作为隐喻。这样,我们只选择属于树的行。

    按照您的方式,正在处理整个表的所有行,只是为了过滤最后源自给定根的一棵树。这是很多浪费的努力。

    此外,我在选择所有相关节点后加入items一次,而不是像你那样每次都在递归术语中。

    选择树的所有节点后,需要再次自顶向下聚合,从叶子到根,因为每个较低级别都集成了下一个较高级别的聚合数组。

    最多查询4 ​​个级别以覆盖您的测试数据:

    WITH RECURSIVE path AS (
       SELECT ARRAY[item_id, child_id] AS path, child_id AS item_id, 2 AS lvl
       FROM   joins
       WHERE  item_id = 1  -- root item_id HERE
       
       UNION ALL
       SELECT p.path || j.child_id, j.child_id, p.lvl + 1
       FROM   path  p
       JOIN   joins j ON j.item_id = p.item_id
       WHERE  p.lvl < 7  -- HARD limit
       )
    , tree AS (
       SELECT p.*, to_jsonb(i) AS item
       FROM   path p
       JOIN   items i USING (item_id)
       ORDER  BY lvl, item_id
       )
    , lvl4 AS (  -- leaf!
       SELECT path[3] AS item_id, jsonb_agg(item) AS children
       FROM   tree
       WHERE  lvl = 4
       GROUP  BY 1
       )
    ,  lvl3 AS (
       SELECT path[2] AS item_id
            , jsonb_agg(jsonb_set_lax(item, '{children}', children, true, 'return_target')) AS children
       FROM   tree t
       LEFT   JOIN lvl4 USING (item_id)
       WHERE  lvl = 3
       GROUP  BY 1
       )
    ,  lvl2 AS ( -- stem!
       SELECT jsonb_agg(jsonb_set_lax(item, '{children}', children, true, 'return_target')) AS children
       FROM   tree t
       LEFT   JOIN lvl3 USING (item_id)
       WHERE  lvl = 2
       )
    SELECT i.item_id
         , jsonb_pretty(jsonb_set_lax(to_jsonb(i), '{children}', children, true, 'return_target')) AS tree
    FROM   items i
    LEFT   JOIN lvl2 ON true
    WHERE  item_id = 1;  -- root item_id HERE
    

    产生您想要的结果。也适用于更少的级别。或者(与您的原始版本不同)甚至是根本没有任何子项的裸根项。

    如何扩展查询以允许更多级别应该很明显。我添加了7 个级别的版本以适应您WHERE level < 7的小提琴,以防万一:

    db<>在这里摆弄

    • 6

相关问题

  • 我可以在使用数据库后激活 PITR 吗?

  • 运行时间偏移延迟复制的最佳实践

  • 存储过程可以防止 SQL 注入吗?

  • PostgreSQL 中 UniProt 的生物序列

  • PostgreSQL 9.0 Replication 和 Slony-I 有什么区别?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目

    • 12 个回答
  • Marko Smith

    如何让sqlplus的输出出现在一行中?

    • 3 个回答
  • Marko Smith

    选择具有最大日期或最晚日期的日期

    • 3 个回答
  • Marko Smith

    如何列出 PostgreSQL 中的所有模式?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Jin 连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane 如何列出 PostgreSQL 中的所有模式? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve