从 Postgres 和 jOOQ 中的分层表递归生成 JSON 树

2023-12-31

我在 Postgres 数据库中有一个分层表,例如category。结构很简单,如下所示:

id parent_id name
1 null A
2 null B
3 1 A1
4 3 A1a
5 3 A1b
6 2 B1
7 2 B2

我需要从这个表中得到的是递归的深层树结构,如下所示:

[
  {
    "id": 1,
    "name": "A",
    "children": [
      {
        "id": 3,
        "name": "A1",
        "children": [
          {
            "id": 4,
            "name": "A1a",
            "children": []
          },
          {
            "id": 5,
            "name": "A1b",
            "children": []
          }
        ]
      }
    ]
  },
  {
    "id": 2,
    "name": "B",
    "children": [
      {
        "id": 6,
        "name": "B1",
        "children": []
      },
      {
        "id": 7,
        "name": "B2",
        "children": []
      }
    ]
  },
]

是否有可能在未知深度下使用组合WITH RECURSIVE and json_build_array()或者其他解决方案?


使用 SQL 执行此操作

我在中找到了这个问题的答案这篇优秀的博客文章在这里 https://schinckel.net/2017/07/01/tree-data-as-a-nested-list-redux,因为我想知道如何在 jOOQ 中概括这个问题。帖子中的解决方案有一个缺陷,我已经在这篇博文中在我自己的版本中修复了这个问题 https://blog.jooq.org/how-to-turn-a-list-of-flat-elements-into-a-hierarchy-in-java-sql-or-jooq.

如果 jOOQ 能够以通用方式具体化任意递归对象树,那将会很有用:https://github.com/jOOQ/jOOQ/issues/12341 https://github.com/jOOQ/jOOQ/issues/12341

同时,使用此 SQL 语句,该语句受到上述博客文章的启发,并进行了一些修改。如果必须的话,请转换为 jOOQ,不过您也可以将其存储为视图:

WITH RECURSIVE
  d1 (id, parent_id, name) as (
    values
      (1, null, 'A'),
      (2, null, 'B'),
      (3,    1, 'A1'),
      (4,    3, 'A1a'),
      (5,    3, 'A1b'),
      (6,    2, 'B1'),
      (7,    2, 'B2')
  ),
  d2 AS (
    SELECT d1.*, 0 AS level
    FROM d1
    WHERE parent_id IS NULL
    UNION ALL
    SELECT d1.*, d2.level + 1
    FROM d1
    JOIN d2 ON d2.id = d1.parent_id
  ),
  d3 AS (
    SELECT d2.*, null::jsonb children
    FROM d2
    WHERE level = (SELECT max(level) FROM d2)
    UNION (
      SELECT
        (branch_parent).*, 
        jsonb_strip_nulls(
          jsonb_agg(branch_child - 'parent_id' - 'level'
            ORDER BY branch_child->>'name'
          ) FILTER (
            WHERE branch_child->>'parent_id' = (branch_parent).id::text
          )
        )
      FROM (
        SELECT
          branch_parent,
          to_jsonb(branch_child) AS branch_child
        FROM d2 branch_parent
        JOIN d3 branch_child ON branch_child.level = branch_parent.level + 1
      ) branch
      GROUP BY branch_parent
    )
  )
SELECT
  jsonb_pretty(jsonb_agg(to_jsonb(d3) - 'parent_id' - 'level')) AS tree
FROM d3
WHERE level = 0;

dbfiddle https://dbfiddle.uk/mEFH_bNX。再次,阅读链接的博客文章 https://schinckel.net/2017/07/01/tree-data-as-a-nested-list-redux解释它是如何工作的

在客户端中执行此操作

也许,您实际上不必在 SQL 中具体化层次结构,但可以在客户端中实现。 jOOQ 3.19 将附带一个新的Collectors::intoHierarchy收集器(参见#12341 https://github.com/jOOQ/jOOQ/issues/12341),它可以为您做到这一点:

record Category(int id, String name, List<Category> children) {}

// Optionally, make this a recursive query using WITH RECURSIVE or
// CONNECT BY, if you need to start from some node and then fetch
// only its children
List<Category> roots =
ctx.select(CATEGORY.ID, CATEGORY.PARENT_ID, CATEGORY.NAME)
   .from(CATEGORY)
   .orderBy(CATEGORY.ID)
   .collect(intoHierarchy(
       r -> r.value1(),
       r -> r.value2(),
       (r, l) -> new Category(r.value1(), r.value3(), l)
   ));

The Collector就是这样:

public static final <K, E, R extends Record> Collector<R, ?, List<E>> 
intoHierarchy(
    Function<? super R, ? extends K> keyMapper,
    Function<? super R, ? extends K> parentKeyMapper,
    BiFunction<? super R, ? super List<E>, ? extends E> recordMapper
) {
    return intoHierarchy(
        keyMapper, parentKeyMapper, recordMapper, ArrayList::new
    );
}


public static final <K, E, C extends Collection<E>, R extends Record> 
Collector<R, ?, List<E>> intoHierarchy(
    Function<? super R, ? extends K> keyMapper,
    Function<? super R, ? extends K> parentKeyMapper,
    BiFunction<? super R, ? super C, ? extends E> recordMapper,
    Supplier<? extends C> collectionFactory
) {
    record Tuple3<T1, T2, T3>(T1 t1, T2 t2, T3 t3) {}

    return collectingAndThen(
        intoMap(keyMapper, r -> {
            C e = collectionFactory.get();
            return new Tuple3<R, C, E>(r, e, recordMapper.apply(r, e));
        }),
        m -> {
            List<E> r = new ArrayList<>();

            m.forEach((k, v) -> {
                K parent = parentKeyMapper.apply(v.t1());
                E child = v.t3();

                if (m.containsKey(parent))
                    m.get(parent).t2().add(child);
                else
                    r.add(child);
            });

            return r;
        }
    );
}

现在,您可以使用任何方法将此树结构转换为 JSON 文档,例如使用杰克逊.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从 Postgres 和 jOOQ 中的分层表递归生成 JSON 树 的相关文章

随机推荐

  • 如何从批处理文件执行postgres的sql查询?

    我需要从批处理文件执行 SQL 我正在执行以下连接到 Postgres 并从表中选择数据 C pgsql bin psql h DB HOST p 5432 U DB USER d DB NAME select from test 我能够连
  • MYSQL 两个日期之间的过滤

    我有一个激励表 inc id int 11 inc date date inc projectname varchar 256 inc amount varchar 256 inc developer id int 11 inc sales
  • 克隆、拉取 Mercurial 存储库时出现 404 错误

    我这里有一个存储库http repos joomlaguruteam com http repos joomlaguruteam com 我使用 hgweb cgi 这是我的 hgweb config 文件 web baseurl allo
  • Bootstrap 工具提示显示在模式窗口后面

    我有一个模态窗口 其中包含以下内容div div class input group div class input group addon title Insert here your domain account name Html L
  • 强制 graphviz 中节点的从左到右顺序?

    我想使用 graphviz 绘制决策树图 我想要绘制的图表如下所示 我正在使用以下点语言 graph a A shape box label A B shape box label B al shape none label 0 bl sh
  • 非常简单的 jQuery .load 示例不起作用

    我认为这是一个非常简单的问题 但我似乎无法让它发挥作用 我需要使用 JavaScript 显然是 jQuery 从一个页面获取一些内容 并将其拉入另一个页面 我对此进行了相当多的研究 但似乎连一个非常简单的例子都无法工作 这是我试图从中获取
  • RESTful WCF 服务在发送“原始”XML 时返回 400 代码

    我已经为此苦苦挣扎了两天 所以希望有人能帮我 我拥有的是一个使用 WCF 编写的 RESTful Web 服务 实际上只是两个接受单个字符串参数并返回一个字符串的方法 参数和返回值都是直接的 XML ServiceContract publ
  • 使用 sortItems() 对 QListWidget 进行排序

    我在 QListWidget 上使用 sortItems 时遇到问题 发生的情况如下 改变这个 A z d C 乙 哦 我 to this A C 我 d e 哦 z 但我希望它是 A C d e 我 哦 z 有什么办法可以改变让它变得像这
  • 无法从 Team Foundation Server 2010 取消解决方案的绑定

    我有一些与 TFS 2010 绑定的解决方案 但是团队项目集合不再可用 我需要将此解决方案重新绑定到该服务器上的新团队项目集合 我尝试更改源代码管理 文件 gt 源代码管理 gt 更改源代码管理 按 更改源代码控制 后 出现 您想将解决方案
  • 是否可以修复 geom_text() 生成的锯齿状、质量差的文本?

    在向图中添加注释文本时 我注意到geom text 产生难看的 锯齿状的文本 同时annotate 生成流畅 美观的文本 有谁知道为什么会发生这种情况以及是否有任何方法可以解决它 我知道我可以使用annotate 在这里 但可能存在这样的情
  • 如何在初始化类方法之前加载 Kivy ID(Python with Kivy)

    目标 成功初始化一个类方法 create button 就我而言 在其参数中引用 kv id init 同一个类的方法 我收到以下错误 File C Users phili scrollablelabelexample py line 33
  • jquery 中的多个 attr 选择器?

    我怎样才能在 jquery 中做到这一点 SomeId input type hidden AND name somename SomeId input type hidden name somename
  • 在嵌入式 C 中使用 malloc() [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 在嵌入式C编程语言中 是malloc 在单任务嵌入式系统中有用吗 我在嵌入式系统领域工作了 0 5 年 我从来没有用过malloc 在8位控制器
  • 如何使用 python 关闭程序?

    python 有没有办法关闭 Windows 应用程序 例如 Firefox 我知道如何启动应用程序 但现在我需要知道如何关闭应用程序 I have used subprocess comands for a while this prog
  • Matlab上3D数据的椭球拟合

    我正在研究一个3D体积 of CT肺部图像 为了检测结节 我需要为每个可疑结节拟合一个椭球模型 我该如何为此编写代码 结节是疑似肿瘤的物体 我的算法需要检查每个物体 并将其近似为椭球体 并根据椭球体参数计算8个特征来构建分类器 通过训练和测
  • Rails:切换用户 Gem 以及切换回原始用户的问题

    在我的应用程序中我正在使用switch user https github com flyerhzm switch user https github com flyerhzm switch user gem 允许管理员以另一个用户身份登录
  • BASH:基本 if then 和变量赋值

    我习惯了 csh 所以不得不使用 bash 有点令人恼火 这段代码有什么问题 if time gt 0300 time lt 0900 then mod 2 else mod 0 fi 按照标准应该是 if time gt 300 time
  • 有没有办法了解导致“_NSTouchBarFinderObservation”崩溃的原因?

    我的 Crashlytics 显示多个用户遇到以下崩溃 见下文 我能够重新创建这个的唯一方法 我怀疑用户正在这样做 是打开 Xcode Touch Bar 模拟器并在generations当应用程序运行时 以下设备和操作系统已报告崩溃 ma
  • 一个测试用例应该涵盖多少内容?

    到目前为止 我从未编写过正确的测试 只编写了测试成功后我会处理的小程序 我正在浏览Python的unittest网络上的模块和教程 但我不清楚 一个应该多少钱TestCase覆盖 我在网上看到过一些例子TestCase仅具有一种方法的类 以
  • 从 Postgres 和 jOOQ 中的分层表递归生成 JSON 树

    我在 Postgres 数据库中有一个分层表 例如category 结构很简单 如下所示 id parent id name 1 null A 2 null B 3 1 A1 4 3 A1a 5 3 A1b 6 2 B1 7 2 B2 我需