PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Postgres - Adjazenzliste in verschachteltes JSON-Objekt konvertieren

Verwendung von WITH RECURSIVE (https://www.postgresql.org/docs/current/static/queries-with.html) und JSON-Funktionen (https://www.postgresql.org/docs/current/static/functions-json.html) I Erstellen Sie diese Lösung:

db<>Geige

Die Kernfunktionalität:

    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

Alle WITH RECURSIVE enthält ein Start-SELECT und einen Rekursionsteil (die zweite SELECT ) kombiniert durch eine UNION .

A:Die Tabelle mit sich selbst verknüpfen, um die Kinder einer node_id zu finden .

B:Erstellen des json-Objekts für das Kind, das in sein Elternteil eingefügt werden kann

C:Erstellen des Pfads, wo das untergeordnete Objekt eingefügt werden muss (von root). Die Fensterfunktion row_number() (https://www.postgresql.org/docs/current/static/tutorial-window.html) generiert den Index des untergeordneten Elements innerhalb des Arrays children des übergeordneten Elements.

D:Der Rekursionsteil funktioniert wie der Anfangsteil mit einem Unterschied:Er sucht nicht nach dem Wurzelelement, sondern nach dem Element, das den Elternknoten der letzten Rekursion hat.

E:Das Ausführen der Rekursion und das Filtern aller Elemente ohne untergeordnete Elemente ergibt dieses Ergebnis:

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

Obwohl ich keine Möglichkeit gefunden habe, alle untergeordneten Elemente in die Rekursion einzufügen (die Ursprungs-json ist keine globale Variable; sie kennt also immer die Änderungen der direkten Vorfahren, nicht ihrer Geschwister), musste ich die Zeilen in einem Sekundenschritt iterieren.

Deshalb baue ich die Funktion. Dort kann ich die Iteration für eine globale Variable durchführen. Mit der Funktion jsonb_insert Ich füge alle berechneten Elemente in ein Root-JSON-Objekt ein - unter Verwendung des berechneten Pfads.

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;

Der letzte Schritt besteht darin, die Funktion aufzurufen und den JSON besser lesbar zu machen (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": []
        }]
    }]
}

Ich bin sicher, dass es möglich ist, die Abfrage zu optimieren, aber für eine Skizze funktioniert es.