2014-12-12 27 views
9

Ich habe das folgende Schema für die Tags Tabelle:Adjazenzliste zu JSON Graph mit Postgres

CREATE TABLE tags (
    id integer NOT NULL, 
    name character varying(255) NOT NULL, 
    parent_id integer 
); 

Ich brauche eine Abfrage zu erstellen, die folgende Struktur zurück (hier als yaml zur besseren Lesbarkeit dargestellt):

- name: Ciencia 
    parent_id: 
    id: 7 
    children: 
    - name: Química 
    parent_id: 7 
    id: 9 
    children: [] 
    - name: Biología 
    parent_id: 7 
    id: 8 
    children: 
    - name: Botánica 
     parent_id: 8 
     id: 19 
     children: [] 
    - name: Etología 
     parent_id: 8 
     id: 18 
     children: [] 

nach einigem Versuch und Irrtum und auf der Suche nach ähnlichen Fragen in SO habe ich kam mit dieser Abfrage nach oben:

WITH RECURSIVE tagtree AS (
     SELECT tags.name, tags.parent_id, tags.id, json '[]' children 
     FROM tags 
     WHERE NOT EXISTS (SELECT 1 FROM tags tt WHERE tt.parent_id = tags.id) 

     UNION ALL 

     SELECT (tags).name, (tags).parent_id, (tags).id, array_to_json(array_agg(tagtree)) children FROM (
     SELECT tags, tagtree 
     FROM tagtree 
     JOIN tags ON tagtree.parent_id = tags.id 
    ) v 
     GROUP BY v.tags 
    ) 

    SELECT array_to_json(array_agg(tagtree)) json 
    FROM tagtree 
    WHERE parent_id IS NULL 

Aber es gibt die folgenden Ergebnisse, wenn zu yaml umgewandelt:

- name: Ciencia 
    parent_id: 
    id: 7 
    children: 
    - name: Química 
    parent_id: 7 
    id: 9 
    children: [] 
- name: Ciencia 
    parent_id: 
    id: 7 
    children: 
    - name: Biología 
    parent_id: 7 
    id: 8 
    children: 
    - name: Botánica 
     parent_id: 8 
     id: 19 
     children: [] 
    - name: Etología 
     parent_id: 8 
     id: 18 
     children: [] 

Der Wurzelknoten dupliziert. Ich könnte die Ergebnisse zu dem erwarteten Ergebnis in meinem App-Code zusammenführen, aber ich fühle, dass ich nah bin und es konnte alles von PG getan werden.

Hier ist ein Beispiel mit SQL Fiddle: http://sqlfiddle.com/#!15/1846e/1/0

Erwartete Ausgabe: https://gist.github.com/maca/e7002eb10f36fcdbc51b

tatsächliche Ausgang: https://gist.github.com/maca/78e84fb7c05ff23f07f4

+0

Können Sie einige Beispieldatensätze posten? – mlinth

+0

Ich habe ein SQL Fiddle Beispiel gepostet und enthält die erwarteten und tatsächlichen Ausgaben. – Macario

+1

Danke für die Daten - vorzüglich strukturierte Frage ... ich hatte es versucht, konnte es aber nicht lösen :-(Aber hier ist ein Link, der dir vielleicht weiterhelfen könnte ... http://bender.io/2013/09/22/Returning-Hierarchical-Data-in-single-SQL-Query/ – mlinth

Antwort

5

Hier ist eine Lösung mit PLV8 für Ihr Schema.

Zuerst erstellen Sie einen materialisierten Pfad mit PLSQL-Funktion und rekursiven CTEs.

CREATE OR REPLACE FUNCTION get_children(tag_id integer) 
RETURNS json AS $$ 
DECLARE 
result json; 
BEGIN 
SELECT array_to_json(array_agg(row_to_json(t))) INTO result 
    FROM (
WITH RECURSIVE tree AS (
    SELECT id, name, ARRAY[]::INTEGER[] AS ancestors 
    FROM tags WHERE parent_id IS NULL 

    UNION ALL 

    SELECT tags.id, tags.name, tree.ancestors || tags.parent_id 
    FROM tags, tree 
    WHERE tags.parent_id = tree.id 
) SELECT id, name, ARRAY[]::INTEGER[] AS children FROM tree WHERE $1 = tree.ancestors[array_upper(tree.ancestors,1)] 
) t; 
RETURN result; 
END; 
$$ LANGUAGE plpgsql; 

Dann bauen Sie den Baum aus der Ausgabe der obigen Funktion.

CREATE OR REPLACE FUNCTION get_tree(data json) RETURNS json AS $$ 

var root = []; 

for(var i in data) { 
    build_tree(data[i]['id'], data[i]['name'], data[i]['children']); 
} 

function build_tree(id, name, children) { 
    var exists = getObject(root, id); 
    if(exists) { 
     exists['children'] = children; 
    } 
    else { 
    root.push({'id': id, 'name': name, 'children': children}); 
    } 
} 


function getObject(theObject, id) { 
    var result = null; 
    if(theObject instanceof Array) { 
     for(var i = 0; i < theObject.length; i++) { 
      result = getObject(theObject[i], id); 
      if (result) { 
       break; 
      } 
     } 
    } 
    else 
    { 
     for(var prop in theObject) { 
      if(prop == 'id') { 
       if(theObject[prop] === id) { 
        return theObject; 
       } 
      } 
      if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) { 
       result = getObject(theObject[prop], id); 
       if (result) { 
        break; 
       } 
      } 
     } 
    } 
    return result; 
} 

    return JSON.stringify(root); 
$$ LANGUAGE plv8 IMMUTABLE STRICT; 

Dies ergibt die erforderliche JSON in Ihrer Frage erwähnt. Ich hoffe, das hilft.

Ich habe eine detaillierte Post/Aufteilung der Funktionsweise dieser Lösung here geschrieben.

+0

das war episch! – Wajahath

1

Versuchen PL/Python und NetworkX.

Zugegebenermaßen ergibt die Verwendung von JSON nicht genau das angeforderte Format, aber die Informationen scheinen alle da zu sein, und wenn PL/Python akzeptabel ist, könnte dies zu einer vollständigen Antwort passen.

CREATE OR REPLACE FUNCTION get_adjacency_data(
    names text[], 
    ids integer[], 
    parent_ids integer[]) 
    RETURNS jsonb AS 
$BODY$ 

    pairs = zip(ids, parent_ids) 

    import networkx as nx 
    import json 
    from networkx.readwrite import json_graph 

    name_dict = dict(zip(ids, names)) 

    G=nx.DiGraph() 
    G.add_nodes_from(ids) 
    nx.set_node_attributes(G, 'name', name_dict) 
    G.add_edges_from(pairs) 
    return json.dumps(json_graph.adjacency_data(G)) 

$BODY$ LANGUAGE plpythonu; 

WITH raw_data AS (
    SELECT array_agg(name) AS names, 
     array_agg(parent_id) AS parent_ids, 
     array_agg(id) AS ids 
    FROM tags 
    WHERE parent_id IS NOT NULL) 
SELECT get_adjacency_data(names, parent_ids, ids) 
FROM raw_data;