WITH RECURSIVE
を使用する (https://www.postgresql.org/docs/current/static/queries-with.html)およびJSON関数(https://www.postgresql.org/docs/current/static/functions-json.html)Iこのソリューションを構築する:
db<>フィドル
コア機能:
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
が含まれています および再帰部分(2番目のSELECT
)UNION
で結合 。
A:node_id
の子を見つけるために、テーブルに再度参加します 。
B:親に挿入できる子のjsonオブジェクトを作成する
C:子オブジェクトを(ルートから)挿入する必要があるパスを作成します。ウィンドウ関数row_number()
(https://www.postgresql.org/docs/current/static/tutorial-window.html)は、親の子配列内に子のインデックスを生成します。
D:再帰部分は最初の部分として機能しますが、1つの違いがあります。ルート要素を検索するのではなく、最後の再帰の親ノードを持つ要素を検索します。
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": []
}]
}]
}
クエリを最適化することは可能だと確信していますが、スケッチの場合は機能します。