在Postgres中将邻接列表作为嵌套JSON返回

时间:2016-06-05 21:11:07

标签: json postgresql recursion adjacency-list

鉴于两列Postgres表(id,parent_id)的最简单的基本情况,有没有办法查询id并将所有子项作为嵌套的json结构取回,如下所示?

{
    "id": 1,
    "children": [{
        "id": 2,
        "children": [{
            "id": 3,
            "children": []
        }]
    }]
}

我理解如何通过表进行递归,但是我无法拼凑出如何使用任何psql json函数来返回上面的结果。也许我应该只使用我选择的语言进行后处理转换?

SQLFiddle目前的进展。

1 个答案:

答案 0 :(得分:2)

吓坏了,花了我几个小时才解决:-P

WITH RECURSIVE 
  tree AS (
    SELECT 1 AS round, id, parent_id, ARRAY(SELECT id FROM foo WHERE parent_id = f.id) AS children
    FROM foo f
    WHERE id = 1
    UNION ALL
    SELECT round+1, f.id, f.parent_id, ARRAY(SELECT id FROM foo WHERE parent_id = f.id) AS children
    FROM tree t
    JOIN foo f ON (f.id = ANY(t.children))
    ),
  rev AS (
    SELECT r.round-1 AS round,
           to_jsonb(ARRAY(
                    SELECT a
                    FROM (
                      SELECT f.parent_id AS id, json_agg(jsonb_build_object('id', f.id, 'children', '{}'::text[])) AS children
                      FROM tree t
                      JOIN foo f ON (f.id = t.id)
                      WHERE t.round = r.round
                      GROUP BY f.parent_id
                      ) a
                    )) AS list
    FROM (SELECT MAX(round)::int AS round FROM tree) r
    UNION ALL
    SELECT r.round-1,
           to_jsonb(ARRAY(
                    SELECT a
                    FROM (
                      SELECT f.parent_id AS id, json_agg(jsonb_build_object('id', f.id, 'children', t->'children')) AS children
                      FROM jsonb_array_elements(list) t
                      JOIN foo f ON (f.id = (t->>'id')::int)
                      GROUP BY f.parent_id
                      ) a
                    )) AS list
    FROM rev r
    WHERE round > 1
    )
SELECT list as nested_json_tree
FROM rev
WHERE round = 1

复杂性在于需要首先构建树(自上而下),然后从树中自下而上构建对象。由于递归查询的限制,递归自下而上是棘手的,例如UNION ALL部分中的递归别名无法分组,也不包含在子查询中。我通过反向回合来解开这个问题。

此查询应正确构建复杂树,每个节点有多个子节点,以及任意数量的嵌套级别。