PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Postgres - Converti l'elenco di adiacenze in un oggetto JSON nidificato

Usando WITH RECURSIVE (https://www.postgresql.org/docs/current/static/queries-with.html) e funzioni JSON (https://www.postgresql.org/docs/current/static/functions-json.html) I costruisci questa soluzione:

db<>violino

La funzionalità principale:

    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

Ogni WITH RECURSIVE contiene un inizio SELECT e una parte ricorsiva (la seconda SELECT ) combinato da un UNION .

A:Unirsi nuovamente al tavolo per trovare i figli di un node_id .

B:Costruire l'oggetto json per il figlio che può essere inserito nel suo genitore

C:Costruire il percorso in cui inserire l'oggetto figlio (dalla radice). La funzione della finestra row_number() (https://www.postgresql.org/docs/current/static/tutorial-window.html) genera l'indice del figlio all'interno dell'array figli del genitore.

D:La parte di ricorsione funziona come parte iniziale con una differenza:non cerca l'elemento radice ma l'elemento che ha il nodo padre dell'ultima ricorsione.

E:L'esecuzione della ricorsione e il filtraggio di tutti gli elementi senza figli dà questo risultato:

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": []}

Anche se non ho trovato il modo di aggiungere tutti gli elementi figli nella ricorsione (l'origine json non è una variabile globale, quindi conosce sempre i cambiamenti degli antenati diretti, non dei loro fratelli), ho dovuto iterare le righe in un secondo passaggio.

Ecco perché costruisco la funzione. Lì posso eseguire l'iterazione per una variabile globale. Con la funzione jsonb_insert Sto inserendo tutti gli elementi calcolati in un oggetto json radice, utilizzando il percorso calcolato.

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;

L'ultimo passaggio è chiamare la funzione e rendere più leggibile il 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": []
        }]
    }]
}

Sono sicuro che è possibile ottimizzare la query ma per uno schizzo funziona.