Gebruik WITH RECURSIVE
(https://www.postgresql.org/docs/current/static/queries-with.html) en JSON-functies (https://www.postgresql.org/docs/current/static/functions-json.html) I bouw deze oplossing:
db<>viool
De kernfunctionaliteit:
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
Elke WITH RECURSIVE
bevat een start SELECT
en een recursiegedeelte (de tweede SELECT
) gecombineerd door een UNION
.
A:Opnieuw deelnemen aan de tafel voor het vinden van de kinderen van een node_id
.
B:Het json-object voor het kind bouwen dat in het bovenliggende item kan worden ingevoegd
C:Het pad bouwen waar het onderliggende object moet worden ingevoegd (vanaf root). De vensterfunctie row_number()
(https://www.postgresql.org/docs/current/static/tutorial-window.html) genereert de index van het kind binnen de reeks kinderen van de ouder.
D:Het recursiegedeelte werkt als het eerste gedeelte met één verschil:het zoekt niet naar het root-element, maar naar het element dat het bovenliggende knooppunt van de laatste recursie heeft.
E:Het uitvoeren van de recursie en het filteren van alle elementen zonder kinderen geeft dit resultaat:
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": []}
Hoewel ik geen manier vond om alle onderliggende elementen in de recursie toe te voegen (de oorsprong-json is geen globale variabele, dus het kent altijd de veranderingen van de directe voorouders, niet hun broers en zussen), moest ik de rijen in een secondestap herhalen.
Daarom bouw ik de functie. Daarin kan ik de iteratie voor een globale variabele doen. Met de functie jsonb_insert
Ik voeg alle berekende elementen in een root-json-object in - met behulp van het berekende pad.
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;
De laatste stap is het aanroepen van de functie en het leesbaarder maken van de 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": []
}]
}]
}
Ik weet zeker dat het mogelijk is om de query te optimaliseren, maar voor een schets werkt het.