【问题标题】:Converting relational tree to nested json document in postgres在postgres中将关系树转换为嵌套的json文档
【发布时间】:2022-01-17 23:45:17
【问题描述】:

在 PostgreSQL 中,有一个关系数据模型来表示组织内的层次结构。

create table employee (
    id              integer primary key, 
    name            varchar(40) not null, 
    supervisor_id   integer references employee 
);

只有 CEO 有supervisor_id=NULL。其他所有员工都有一名主管,其中有一些supervisor_id。 我想将数据导出为单个嵌套的 json 文档

{ 
  "id": 1, 
  "name": "Name of company's CEO", 
  "supervises": [
    { 
      "id": 2, 
      "name": "Name of 1st EC member",
      "supervises": [ ... nested employees ... ]
    }, 
    {
      "id": 3, 
      "name": "Name of 2nd EC member",
      "supervises": [ ... nested employees ... ]
    }
    ... 
  ]
}

我按照https://www.postgresqltutorial.com/postgresql-recursive-query/ 中的示例进行操作,但它只能帮助我使用 WITH RECURSIVE 子句从上到下沿报告线识别所有员工。 我知道我需要从树中深度最高的员工(不仅仅是叶节点)开始聚合,然后自下而上聚合它们,但我没有设法编写完成这项工作的查询。

感谢您的帮助!

【问题讨论】:

    标签: postgresql recursion


    【解决方案1】:

    乍一看你的问题使用递归查询听起来很简单,但这并不是因为可能有同一个主管的员工列表,这需要一个聚合函数来构建相应的json数组,而是递归查询中不允许使用聚合函数...

    第一步 - 我们为每个员工构建 json 对象,并在他/她是主管时构建相应的受监管员工数组:

    SELECT s.id AS father
         , CASE WHEN array_agg(e.id) = array[NULL :: integer] THEN NULL ELSE array_agg(e.id) END  AS children_list
         , jsonb_build_object
           ( 'id', s.id
           , 'name', s.name
           , 'supervises', array_agg(e.id)
           ) AS json_tree
      FROM employee AS s
      LEFT JOIN employee AS e
        ON s.id = e.supervisor_id
     GROUP BY s.id, s.name
    

    第二步 - 使用递归查询,我们以自上而下的方式遍历员工树,我们为每个员工分配一个等级并构建将在下一步:

    WITH RECURSIVE elt AS
    (
    SELECT s.id AS father
         , CASE WHEN array_agg(e.id) = array[NULL :: integer] THEN NULL ELSE array_agg(e.id) END  AS children_list
         , jsonb_build_object
           ( 'id', s.id
           , 'name', s.name
           , 'supervises', array_agg(e.id)
           ) AS json_tree
      FROM employee AS s
      LEFT JOIN employee AS e
        ON s.id = e.supervisor_id
     GROUP BY s.id, s.name
    ), list (father, json_tree, children_list, rank, path) AS
    (
    SELECT c.father, c.json_tree, c.children_list, 1, '{}' :: text[]
      FROM elt AS c
      LEFT JOIN elt AS f
        ON array[c.father] <@ f.children_list
     WHERE f.father IS NULL
    UNION ALL
    SELECT c.father
         , c.json_tree
         , c.children_list
         , f.rank + 1
         , f.path || array['supervises',(array_position(f.children_list, c.father)-1) :: text]
      FROM list AS f
     INNER JOIN elt AS c
        ON array[c.father] <@ f.children_list
    ) --, ordered_list AS (
    SELECT *
      FROM list
     ORDER BY rank DESC
    

    第三步 - 我们创建 jsonb_set 函数的聚合版本,以便在迭代前一个结果列表时构建最终的 jsonb 数据

    CREATE OR REPLACE FUNCTION jsonb_set(x jsonb, y jsonb, p text[], e jsonb, b boolean)
    RETURNS jsonb LANGUAGE sql AS $$
    SELECT CASE WHEN x IS NULL THEN e ELSE jsonb_set(x, p, e, b) END ; $$ ;
    
    CREATE OR REPLACE AGGREGATE jsonb_set_agg(x jsonb, p text[], e jsonb, b boolean)
    ( STYPE = jsonb, SFUNC = jsonb_set) ;
    

    最终查询

    WITH RECURSIVE elt AS
    (
    SELECT s.id AS father
         , CASE WHEN array_agg(e.id) = array[NULL :: integer] THEN NULL ELSE array_agg(e.id) END  AS children_list
         , jsonb_build_object
           ( 'id', s.id
           , 'name', s.name
           , 'supervises', array_agg(e.id)
           ) AS json_tree
      FROM employee AS s
      LEFT JOIN employee AS e
        ON s.id = e.supervisor_id
     GROUP BY s.id, s.name
    ), list (father, json_tree, children_list, rank, path) AS
    (
    SELECT c.father, c.json_tree, c.children_list, 1, '{}' :: text[]
      FROM elt AS c
      LEFT JOIN elt AS f
        ON array[c.father] <@ f.children_list
     WHERE f.father IS NULL
    UNION ALL
    SELECT c.father
         , c.json_tree
         , c.children_list
         , f.rank + 1
         , f.path || array['supervises',(array_position(f.children_list, c.father)-1) :: text]
      FROM list AS f
     INNER JOIN elt AS c
        ON array[c.father] <@ f.children_list
    )
    SELECT jsonb_set_agg(NULL :: jsonb, path, json_tree, true ORDER BY rank ASC)
      FROM list
    

    测试结果在dbfiddle

    【讨论】:

      猜你喜欢
      • 2020-01-16
      • 2012-08-07
      • 2017-07-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-09-30
      相关资源
      最近更新 更多