【问题标题】:Postgres - Convert adjacency list to nested JSON objectPostgres - 将邻接列表转换为嵌套的 JSON 对象
【发布时间】:2018-10-07 21:54:17
【问题描述】:

我在 Postgres 中有一个包含此数据的表,我很难将其转换为 JSON 对象。

node_id    parent_node    name
-------    -----------    ----
1                         node1
2          1              node2
3          1              node3
4          2              node4
5          2              node5
6          2              node6
7          3              node7
8          3              node8

如何转换成这样?

{
    name: 'node1'
    childs: [
        { 
            name: 'node2',
            childs: [
                {
                    name: 'node4',
                    childs: []
                },
                {
                    name: 'node5',
                    childs: []
                },
                {
                    name: 'node6',
                    childs: []
                }
            ]
        },
        ...    
    ]
}

任何建议都会有所帮助。谢谢

【问题讨论】:

    标签: json postgresql


    【解决方案1】:

    使用WITH RECURSIVE (https://www.postgresql.org/docs/current/static/queries-with.html) 和 JSON 函数 (https://www.postgresql.org/docs/current/static/functions-json.html) 我构建了这个解决方案:

    db<>fiddle

    核心功能:

        WITH RECURSIVE tree(node_id, ancestor, child, path, json) AS  (
          SELECT 
              t1.node_id, 
              NULL::int, 
              t2.node_id,
              '{children}'::text[] || 
                 (row_number() OVER (PARTITION BY t1.node_id ORDER BY t2.node_id) - 1)::text,-- C
              jsonb_build_object('name', t2.name, 'children', array_to_json(ARRAY[]::int[])) -- B
          FROM test t1
          LEFT JOIN test t2 ON t1.node_id = t2.parent_node                                   -- A
          WHERE t1.parent_node IS NULL
    
          UNION
    
          SELECT
              t1.node_id, 
              t1.parent_node, 
              t2.node_id,
              tree.path || '{children}' || (row_number() OVER (PARTITION BY t1.node_id ORDER BY t2.node_id) - 1)::text, 
              jsonb_build_object('name', t2.name, 'children', array_to_json(ARRAY[]::int[]))
          FROM test t1
          LEFT JOIN test t2 ON t1.node_id = t2.parent_node
          INNER JOIN tree ON (t1.node_id = tree.child)
          WHERE t1.parent_node = tree.node_id                                                -- D
        )
        SELECT                                                                               -- E
            child as node_id, path, json 
        FROM tree 
        WHERE child IS NOT NULL ORDER BY path
    

    每个WITH RECURSIVE 都包含一个开始SELECT 和一个由UNION 组合的递归部分(第二个SELECT)。

    答:为了找到node_id 的孩子而加入表本身。

    B:为可以插入其父级的子级构建json对象

    C:构建必须插入子对象的路径(从根)。窗口函数row_number()https://www.postgresql.org/docs/current/static/tutorial-window.html)在父级的children数组中生成child的索引。

    D:递归部分作为初始部分工作,但有一个区别:它不是搜索根元素,而是搜索具有最后一次递归的父节点的元素。

    E:执行递归并过滤没有任何子元素的所有元素会得到以下结果:

    node_id   path                      json
    2         children,0                {"name": "node2", "children": []}
    4         children,0,children,0     {"name": "node4", "children": []}
    5         children,0,children,1     {"name": "node5", "children": []}
    6         children,0,children,2     {"name": "node6", "children": []}
    3         children,1                {"name": "node3", "children": []}
    7         children,1,children,0     {"name": "node7", "children": []}
    8         children,1,children,1     {"name": "node8", "children": []}
    

    虽然我发现没有办法在递归中添加所有子元素(原始 json 不是全局变量;所以它总是知道直接祖先的变化,而不是他们的兄弟姐妹),我不得不在几秒钟内迭代行一步。

    这就是我构建函数的原因。在那里我可以对全局变量进行迭代。使用 jsonb_insert 函数,我将所有计算的元素插入到根 json 对象中 - 使用计算的路径。

    CREATE OR REPLACE FUNCTION json_tree() RETURNS jsonb AS $$
    DECLARE
        _json_output jsonb;
        _temprow record;
    BEGIN
        SELECT 
            jsonb_build_object('name', name, 'children', array_to_json(ARRAY[]::int[])) 
        INTO _json_output 
        FROM test 
        WHERE parent_node IS NULL;
    
        FOR _temprow IN
            /* Query above */
        LOOP
            SELECT jsonb_insert(_json_output, _temprow.path, _temprow.json) INTO _json_output;
        END LOOP;
    
        RETURN _json_output;
    END;
    $$ LANGUAGE plpgsql;
    

    最后一步是调用函数并使 JSON 更具可读性 (jsonb_pretty())

    {
        "name": "node1",
        "children": [{
            "name": "node2",
            "children": [{
                "name": "node4",
                "children": []
            },
            {
                "name": "node5",
                "children": []
            },
            {
                "name": "node6",
                "children": []
            }]
        },
        {
            "name": "node3",
            "children": [{
                "name": "node7",
                "children": []
            },
            {
                "name": "node8",
                "children": []
            }]
        }]
    }
    

    我确信可以优化查询,但对于草图它可以工作。

    【讨论】:

    • 哇,谢谢伙计。如果你没有给我一个 db fiddle,我无法想象如何做到这一点。
    猜你喜欢
    • 1970-01-01
    • 2016-10-05
    • 2020-07-14
    • 2019-10-03
    • 1970-01-01
    • 1970-01-01
    • 2019-07-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多