PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Postgres - Convertir la liste de contiguïté en objet JSON imbriqué

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.