Utilisation de WITH RECURSIVE
(https://www.postgresql.org/docs/current/static/queries-with.html) et les fonctions JSON (https://www.postgresql.org/docs/current/static/functions-json.html) I créez cette solution :
db<>violon
La fonctionnalité de base :
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
Chaque WITH RECURSIVE
contient un début SELECT
et une partie récursive (la seconde SELECT
) combinés par un UNION
.
A :Rejoindre la table contre elle-même pour trouver les enfants d'un node_id
.
B :Construire l'objet json pour l'enfant qui peut être inséré dans son parent
C :Construire le chemin où l'objet enfant doit être inséré (à partir de la racine). La fonction de fenêtre row_number()
(https://www.postgresql.org/docs/current/static/tutorial-window.html) génère l'index de l'enfant dans le tableau children du parent.
D :La partie récursive fonctionne comme la partie initiale avec une différence :elle ne recherche pas l'élément racine mais l'élément qui a le nœud parent de la dernière récursivité.
E :Exécuter la récursivité et filtrer tous les éléments sans aucun enfant donne ce résultat :
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": []}
Bien que je n'aie trouvé aucun moyen d'ajouter tous les éléments enfants dans la récursivité (l'origine json n'est pas une variable globale ; il connaît donc toujours les modifications des ancêtres directs, pas de leurs frères et sœurs), j'ai dû parcourir les lignes en quelques secondes.
C'est pourquoi je construis la fonction. Là, je peux faire l'itération pour une variable globale. Avec la fonction jsonb_insert
J'insère tous les éléments calculés dans un objet json racine - en utilisant le chemin calculé.
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;
La dernière étape consiste à appeler la fonction et à rendre le JSON plus lisible (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": []
}]
}]
}
Je suis sûr qu'il est possible d'optimiser la requête, mais pour un croquis, cela fonctionne.