当前位置: 首页 > 知识库问答 >
问题:

如何将具有树结构的表聚合到单个嵌套的JSON对象?

东方玉泽
2023-03-14

我在Postgres 11.4数据库中有一个表,具有自引用树结构:

+------------+
| account    |  
+------------+
| id         |
| code       | 
| type       |
| parentId   | -- references account.id
+------------+

每个孩子可以有另一个孩子,嵌套级别没有限制。

我想从中生成一个JSON对象,嵌套所有子对象(复活)。

可以用单个查询解决这个问题吗?或者使用Typeform和一个表的任何其他解决方案<否则我将不得不在服务器端手动绑定数据。

我尝试了这个查询:

SELECT account.type, json_agg(account) as accounts
FROM account
-- LEFT JOIN "account" "child" ON "child"."parentId"="account"."id" -- tried to make one column child
GROUP BY account.type   

结果:

[
  ...
  {
    "type": "type03",
    "accounts": [
      {
        "id": 28,
        "code": "acc03.001",
        "type": "type03",
        "parentId": null
      },
      {
        "id": 29,
        "code": "acc03.001.001",
        "type": "type03",
        "parentId": 28
      },
      {
        "id": 30,
        "code": "acc03.001.002",
        "type": "type03",
        "parentId": 28
      }
    ]
  }
  ...
]

相反,我期望这样:

[
  ...
  {
    "type": "type03",
    "accounts": [
      {
        "id": 28,
        "code": "acc03.001",
        "type": "type03",
        "parentId": null,
        "child": [
          {
            "id": 29,
            "code": "acc03.001.001",
            "type": "type03",
            "parentId": 28
          },
          {
            "id": 30,
            "code": "acc03.001.002",
            "type": "type03",
            "parentId": 28
          }
        ]
      }
    ]
  }
  ...
]

共有1个答案

姬正文
2023-03-14

这很棘手。这是一个递归问题,但是标准的递归CTE没有能力处理它,因为我们需要在每个级别上聚合,而CTE不允许在递归术语中聚合。

我用PL/pgSQL函数解决了它:

CREATE OR REPLACE FUNCTION f_build_jsonb_tree(_type text = NULL)
  RETURNS jsonb
  LANGUAGE plpgsql AS
$func$
DECLARE
   _nest_lvl int;

BEGIN
   -- add level of nesting recursively
   CREATE TEMP TABLE t ON COMMIT DROP AS
   WITH RECURSIVE t AS (
      SELECT *, 1 AS lvl
      FROM   account
      WHERE  "parentId" IS NULL
      AND   (type = _type OR _type IS NULL) -- default: whole table

      UNION ALL
      SELECT a.*, lvl + 1
      FROM   t
      JOIN   account a ON a."parentId" = t.id
      )
   TABLE t;
   
   -- optional idx for big tables with many levels of nesting
   -- CREATE INDEX ON t (lvl, id);

   _nest_lvl := (SELECT max(lvl) FROM t);

   -- no nesting found, return simple result
   IF _nest_lvl = 1 THEN 
      RETURN (  -- exits functions
      SELECT jsonb_agg(sub) -- AS result
      FROM  (
         SELECT type
              , jsonb_agg(sub) AS accounts
         FROM  (
            SELECT id, code, type, "parentId", NULL AS children
            FROM   t
            ORDER  BY type, id
            ) sub
         GROUP BY 1
         ) sub
      );
   END IF;

   -- start collapsing with leaves at highest level
   CREATE TEMP TABLE j ON COMMIT DROP AS
   SELECT "parentId" AS id
        , jsonb_agg (sub) AS children
   FROM  (
      SELECT id, code, type, "parentId"  -- type redundant?
      FROM   t
      WHERE  lvl = _nest_lvl
      ORDER  BY id
      ) sub
   GROUP  BY "parentId";

   -- optional idx for big tables with many levels of nesting
   -- CREATE INDEX ON j (id);

   -- iterate all the way down to lvl 2
   -- write to same table; ID is enough to identify
   WHILE _nest_lvl > 2
   LOOP
      _nest_lvl := _nest_lvl - 1;

      INSERT INTO j(id, children)
      SELECT "parentId"     -- AS id
           , jsonb_agg(sub) -- AS children
      FROM  (
         SELECT id, t.code, t.type, "parentId", j.children  -- type redundant?
         FROM   t
         LEFT   JOIN j USING (id)  -- may or may not have children
         WHERE  t.lvl = _nest_lvl
         ORDER  BY id
         ) sub
      GROUP  BY "parentId";
   END LOOP;

   -- nesting found, return nested result
   RETURN ( -- exits functions
   SELECT jsonb_agg(sub) -- AS result
   FROM  (
      SELECT type
           , jsonb_agg (sub) AS accounts
      FROM  (
         SELECT id, code, type, "parentId", j.children
         FROM   t
         LEFT   JOIN j USING (id)
         WHERE  t.lvl = 1
         ORDER  BY type, id
         ) sub
      GROUP  BY 1
      ) sub
   );
END
$func$;

调用(精确返回所需结果):

SELECT jsonb_pretty(f_build_jsonb_tree());

数据库

我选择了键名children,而不是children,因为可以嵌套多个。

jsonb_pretty()美化显示是可选的。

这是假设参照完整性;应使用FK约束实现。

对于您的特定情况,该解决方案可能更简单,如果它显示(未公开)有用的属性,那么可以利用code列。我们可以在没有rCTE的情况下导出嵌套级别,并添加临时表t。但我的目标是只基于ID引用的通用解决方案。

函数中有很多内容。我添加了内联注释。基本上,它是这样做的:

  1. 使用添加的嵌套级别(lvl)创建临时表

该函数将类型作为参数,仅返回给定的类型。否则,将处理整个表。

旁白:尽可能避免在Postgres中使用混合大小写标识符,如“parentId”。请参阅:

  • PostgreSQL列名区分大小写吗

使用递归函数的相关后续答案:

  • 如何将一组扁平的树变成一棵多叶的树?

 类似资料:
  • 我想映射这个JSON数据: 对于此java对象: 但是它失败了,所有的值都是。我尝试在变量上方添加,但没有成功。 是否可以反序列化JSON,使其仅映射到(这个)一个类? 编辑:我使用Spring StreamListener进行如下映射:

  • 问题内容: 好的,现在我这里有一个无序列表: 基本上,我只想将此数据转换为JSON实体。我想在 Jquery中 完成此操作,而且我觉得我很难过。上面的列表只是一个例子,实际上,我的列表理想情况下将有更多的孩子,并且深度可能是n级(意思是,它将有孙子孙的孙子……或更多)我失去了无数数小时的睡眠,我不认为我会去任何地方:( 我想提取这些东西:锚点内的文本,锚点的url和锚点的标题,并将它们放到JSON

  • 我正在开发一个使用Go的网站,并将其连接到弹性搜索。在弹性搜索中,我可以为索引类型设置动态字段。当我从弹性搜索中读取文档时,它将返回一个JSON对象作为结果,该对象可以包括具有动态名称的字段(或用户定义的字段)。 我可以获取JSON结果并将其解组到Go结构中,但我不知道将这些动态字段作为Go结构的一部分的最佳方法是什么。 这就是我正在做的。例如,如果我从弹性搜索中获得一个联系人的文档,它可能看起来

  • 我需要使用MapStruct将一些实体和实体列表转换为单个DTO。此DTO包括嵌套列表。 假设,我有以下持久性支持的POJO: 以及这些实体的一些集合: DTO如下所示: 和嵌套的DTO: 除SomeLink列表外,我需要从实体映射的所有字段: 我填充的嵌套列表如下: 结果涉及大量手动转换: 有没有办法通过使用MapStruct的功能来完成同样的工作?就像这样:

  • 问题内容: http://play.golang.org/p/f6ilWnWTjm 我正在尝试解码以下字符串,但仅获取空值。 如何在Go中解码嵌套的JSON结构? 我想将以下内容转换为地图数据结构。 问题答案: 在Go中使用嵌套结构来匹配JSON中的嵌套结构。 这是一个如何处理示例JSON的示例: 游乐场链接 您还可以对内部结构使用匿名类型: 游乐场链接 或外部和内部结构: 游乐场链接 如果您不知

  • 给定对象的复杂嵌套集合,如: 是否存在一个泛型方法来将其平坦化并获得包含在其中的所有的单个? null