sql >> Database >  >> RDS >> PostgreSQL

Postgres - Aangrenzende lijst converteren naar genest JSON-object

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.




  1. Beste equivalent voor IsInteger in SQL Server

  2. MySQL - Voorwaardelijke beperkingen voor externe sleutels

  3. ZDLRA – RMAN-20035 ongeldige hoge RECID

  4. Wat is het DATALENGTH()-equivalent in MySQL?