it-swarm.com.de

Was ist die effizienteste / eleganteste Art, einen flachen Tisch in einen Baum zu stecken?

Angenommen, Sie haben eine flache Tabelle, in der eine geordnete Baumhierarchie gespeichert ist:

Id   Name         ParentId   Order
 1   'Node 1'            0      10
 2   'Node 1.1'          1      10
 3   'Node 2'            0      20
 4   'Node 1.1.1'        2      10
 5   'Node 2.1'          3      10
 6   'Node 1.2'          1      20

Hier ist ein Diagramm, in dem wir [id] Name Haben. Wurzelknoten 0 ist fiktiv.

 [0] ROOT 
/\ 
 [1] Node 1 [3] Node 2 
/\\
 [2] Node 1.1 [6] Node 1.2 [5] Node 2.1 
/
 [4] Node 1.1.1 

Welchen minimalistischen Ansatz würden Sie verwenden, um das in HTML (oder Text) als korrekt geordnete, korrekt eingerückte Struktur auszugeben?

Angenommen, Sie haben nur grundlegende Datenstrukturen (Arrays und Hashmaps), keine ausgefallenen Objekte mit Parent/Child-Referenzen, kein ORM, kein Framework, nur Ihre beiden Hände. Die Tabelle wird als Ergebnismenge dargestellt, auf die nach dem Zufallsprinzip zugegriffen werden kann.

Pseudocode oder einfaches Englisch ist in Ordnung, dies ist nur eine konzeptionelle Frage.

Bonusfrage: Gibt es eine grundsätzlich bessere Möglichkeit, eine solche Baumstruktur in einem RDBMS zu speichern?


ÄNDERUNGEN UND ERGÄNZUNGEN

Zur Beantwortung der Frage eines Kommentars ( Mark Bessey ): Ein Wurzelknoten ist nicht erforderlich, da er sowieso nie angezeigt wird. ParentId = 0 ist die Konvention, um auszudrücken, dass dies die oberste Ebene ist. In der Spalte Reihenfolge wird festgelegt, wie Knoten mit demselben übergeordneten Knoten sortiert werden.

Die "Ergebnismenge", von der ich gesprochen habe, kann als eine Reihe von Hashmaps dargestellt werden (um in dieser Terminologie zu bleiben). Denn mein Beispiel sollte schon da sein. Einige Antworten gehen die Extrameile und konstruieren es zuerst, aber das ist okay.

Der Baum kann beliebig tief sein. Jeder Knoten kann N Kinder haben. Ich hatte jedoch nicht genau einen Baum mit "Millionen Einträgen" im Sinn.

Verwechseln Sie meine Wahl des Knotennamens ('Node 1.1.1') nicht mit etwas, auf das Sie sich verlassen können. Die Knoten könnten genauso gut 'Frank' oder 'Bob' genannt werden, es wird keine Namensstruktur impliziert, dies dient lediglich dazu, es lesbar zu machen.

Ich habe meine eigene Lösung veröffentlicht, damit ihr sie in Stücke reißen könnt.

501
Tomalak

Jetzt, da MySQL 8.0 kurz vor der Veröffentlichung steht, alle gängigen SQL-Datenbanken unterstützen rekursive Abfragen in der Standardsyntax.

WITH RECURSIVE MyTree AS (
    SELECT * FROM MyTable WHERE ParentId IS NULL
    UNION ALL
    SELECT m.* FROM MyTABLE AS m JOIN MyTree AS t ON m.ParentId = t.Id
)
SELECT * FROM MyTree;

Ich habe rekursive Abfragen in MySQL 8.0 in meiner Präsentation getestet Recursive Query Throwdown in 2017.

Unten ist meine ursprüngliche Antwort von 2008:


Es gibt verschiedene Möglichkeiten, baumstrukturierte Daten in einer relationalen Datenbank zu speichern. Was Sie in Ihrem Beispiel zeigen, verwendet zwei Methoden:

  • Adjacency List (die "Eltern" -Spalte) und
  • Pfadaufzählung (die gepunkteten Zahlen in Ihrer Namensspalte).

Eine andere Lösung heißt Verschachtelte Mengen und kann auch in derselben Tabelle gespeichert werden. Lesen Sie " Bäume und Hierarchien in SQL für Smarties " von Joe Celko, um weitere Informationen zu diesen Designs zu erhalten.

Normalerweise bevorzuge ich ein Design namens Closure Table (aka "Adjacency Relation") zum Speichern von Daten mit Baumstruktur. Es erfordert eine andere Tabelle, aber dann ist das Abfragen von Bäumen ziemlich einfach.

Ich beschreibe Closure Table in meiner Präsentation Modelle für hierarchische Daten mit SQL und PHP und in meinem Buch SQL-Antimuster: Vermeidung der Fallstricke der Datenbankprogrammierung .

CREATE TABLE ClosureTable (
  ancestor_id   INT NOT NULL REFERENCES FlatTable(id),
  descendant_id INT NOT NULL REFERENCES FlatTable(id),
  PRIMARY KEY (ancestor_id, descendant_id)
);

Speichern Sie alle Pfade in der Closure-Tabelle, in der es eine direkte Abstammung von einem Knoten zum anderen gibt. Fügen Sie für jeden Knoten eine Zeile ein, die auf sich selbst verweist. Verwenden Sie beispielsweise den Datensatz, den Sie in Ihrer Frage angezeigt haben:

INSERT INTO ClosureTable (ancestor_id, descendant_id) VALUES
  (1,1), (1,2), (1,4), (1,6),
  (2,2), (2,4),
  (3,3), (3,5),
  (4,4),
  (5,5),
  (6,6);

Jetzt können Sie einen Baum ab Knoten 1 wie folgt erstellen:

SELECT f.* 
FROM FlatTable f 
  JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1;

Die Ausgabe (im MySQL-Client) sieht folgendermaßen aus:

+----+
| id |
+----+
|  1 | 
|  2 | 
|  4 | 
|  6 | 
+----+

Mit anderen Worten, die Knoten 3 und 5 werden ausgeschlossen, da sie Teil einer separaten Hierarchie sind und nicht von Knoten 1 abstammen.


Betreff: Kommentar von e-satis über unmittelbare Kinder (oder unmittelbare Eltern). Sie können eine Spalte "path_length" Zu ClosureTable hinzufügen, um die Abfrage nach einem unmittelbaren Kind oder Elternteil (oder einer anderen Entfernung) zu vereinfachen.

INSERT INTO ClosureTable (ancestor_id, descendant_id, path_length) VALUES
  (1,1,0), (1,2,1), (1,4,2), (1,6,1),
  (2,2,0), (2,4,1),
  (3,3,0), (3,5,1),
  (4,4,0),
  (5,5,0),
  (6,6,0);

Anschließend können Sie Ihrer Suche einen Begriff hinzufügen, um die unmittelbaren untergeordneten Elemente eines bestimmten Knotens abzufragen. Dies sind Nachkommen, deren path_length 1 ist.

SELECT f.* 
FROM FlatTable f 
  JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
  AND path_length = 1;

+----+
| id |
+----+
|  2 | 
|  6 | 
+----+

Kommentar von @ashraf: "Wie wäre es, den ganzen Baum [nach Namen] zu sortieren?"

Hier ist eine Beispielabfrage, um alle Knoten zurückzugeben, die Nachkommen von Knoten 1 sind, sie mit der FlatTable zu verbinden, die andere Knotenattribute wie name enthält, und nach dem Namen zu sortieren.

SELECT f.name
FROM FlatTable f 
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
ORDER BY f.name;

Kommentar von @Nate:

SELECT f.name, GROUP_CONCAT(b.ancestor_id order by b.path_length desc) AS breadcrumbs
FROM FlatTable f 
JOIN ClosureTable a ON (f.id = a.descendant_id) 
JOIN ClosureTable b ON (b.descendant_id = a.descendant_id) 
WHERE a.ancestor_id = 1 
GROUP BY a.descendant_id 
ORDER BY f.name

+------------+-------------+
| name       | breadcrumbs |
+------------+-------------+
| Node 1     | 1           |
| Node 1.1   | 1,2         |
| Node 1.1.1 | 1,2,4       |
| Node 1.2   | 1,6         |
+------------+-------------+

Ein Benutzer hat heute eine Änderung vorgeschlagen. SO Moderatoren haben der Bearbeitung zugestimmt, aber ich kehre sie um.

Die Bearbeitung schlug vor, dass ORDER BY in der letzten obigen Abfrage ORDER BY b.path_length, f.name Lauten sollte, vermutlich um sicherzustellen, dass die Reihenfolge mit der Hierarchie übereinstimmt. Dies funktioniert jedoch nicht, da "Node 1.1.1" nach "Node 1.2" bestellt wird.

Wenn Sie möchten, dass die Reihenfolge der Hierarchie auf sinnvolle Weise entspricht, ist dies möglich, jedoch nicht einfach durch Sortieren nach der Pfadlänge. Siehe zum Beispiel meine Antwort auf hierarchische MySQL Closure Table-Datenbank - Informationen in der richtigen Reihenfolge abrufen .

437
Bill Karwin

Wenn Sie verschachtelte Mengen verwenden (manchmal als "Modified Pre-Order Tree Traversal" bezeichnet), können Sie die gesamte Baumstruktur oder einen beliebigen Teilbaum in Baumreihenfolge mit einer einzigen Abfrage extrahieren, wobei die Kosten für Einfügungen bei Bedarf höher sind Spalten verwalten, die einen geordneten Pfad durch die Baumstruktur beschreiben.

Für Django-mptt habe ich eine Struktur wie diese verwendet:

 id parent_id tree_id level lft rght 
 - --------- ------- ----- --- ---- 
 1 null 1 0 1 14 
 2 1 1 1 2 7 
 3 2 1 2 3 4 
 4 2 1 2 5 6 
 5 1 1 1 8 13 
 6 5 1 2 9 10 
 7 5 1 2 11 12 

Das beschreibt einen Baum, der so aussieht (wobei id für jeden Gegenstand steht):

 1 
 + - 2 
 | + - 3 
 | + - 4 
 | 
 + - 5 
 + - 6 
 + - 7 

Oder als verschachteltes Set-Diagramm, das die Funktionsweise der Werte lft und rght verdeutlicht:

 __________________________________________________________________________ 
 | Wurzel 1 | 
 | ________________________________ ________________________________ | 
 | | Kind 1.1 | | Kind 1.2 | | 
 | | ___________ ___________ | | ___________ ___________ | | 
 | | | C 1.1.1 | | C 1.1.2 | | | | C 1.2.1 | | C 1.2.2 | | | 
 1 2 3___________4 5___________6 7 8 9___________10 11__________12 13 14 
 | | ________________________________ | | ________________________________ | | 
 | __________________________________________________________________________ | 

Um den gesamten Teilbaum für einen bestimmten Knoten in Baumreihenfolge abzurufen, müssen Sie einfach alle Zeilen auswählen, deren lft - und rght -Werte zwischen den lft -Werten liegen. und rght Werte. Es ist auch einfach, den Stammbaum der Vorfahren für einen bestimmten Knoten abzurufen.

Die level -Spalte ist mehr aus Bequemlichkeitsgründen ein bisschen denormalisiert, und die tree_id Spalte ermöglicht es Ihnen, die lft und rght Nummerierung für jeden Knoten der obersten Ebene neu zu starten, wodurch die Anzahl der Spalten, die von Einfügungen, Verschiebungen und Löschungen betroffen sind, als lft und rght Spalten müssen entsprechend angepasst werden, wenn diese Operationen stattfinden, um Lücken zu erstellen oder zu schließen. Ich habe einige Entwicklungsnotizen gemacht, als ich versuchte, die für jede Operation erforderlichen Abfragen zu beantworten.

Um tatsächlich mit diesen Daten zu arbeiten, um einen Baum anzuzeigen, habe ich ein tree_item_iterator Dienstprogrammfunktion, die für jeden Knoten genügend Informationen zur Verfügung stellen sollte, um die gewünschte Art der Anzeige zu generieren.

Weitere Informationen zu MPTT:

55
Jonny Buchanan

Es ist eine ziemlich alte Frage, aber da es viele Ansichten gibt, denke ich, dass es sich lohnt, eine alternative und meiner Meinung nach sehr elegante Lösung vorzustellen.

Um eine Baumstruktur zu lesen, können Sie rekursive Common Table Expressions (CTEs) verwenden. Es gibt die Möglichkeit, die gesamte Baumstruktur auf einmal abzurufen, Informationen über die Ebene des Knotens, seines übergeordneten Knotens und die Reihenfolge innerhalb der untergeordneten Knoten des übergeordneten Knotens zu erhalten.

Lassen Sie mich Ihnen zeigen, wie dies in PostgreSQL 9.1 funktionieren würde.

  1. Erstellen Sie eine Struktur

    CREATE TABLE tree (
        id int  NOT NULL,
        name varchar(32)  NOT NULL,
        parent_id int  NULL,
        node_order int  NOT NULL,
        CONSTRAINT tree_pk PRIMARY KEY (id),
        CONSTRAINT tree_tree_fk FOREIGN KEY (parent_id) 
          REFERENCES tree (id) NOT DEFERRABLE
    );
    
    
    insert into tree values
      (0, 'ROOT', NULL, 0),
      (1, 'Node 1', 0, 10),
      (2, 'Node 1.1', 1, 10),
      (3, 'Node 2', 0, 20),
      (4, 'Node 1.1.1', 2, 10),
      (5, 'Node 2.1', 3, 10),
      (6, 'Node 1.2', 1, 20);
    
  2. Schreibe eine Anfrage

    WITH RECURSIVE 
    tree_search (id, name, level, parent_id, node_order) AS (
      SELECT 
        id, 
        name,
        0,
        parent_id, 
        1 
      FROM tree
      WHERE parent_id is NULL
    
      UNION ALL 
      SELECT 
        t.id, 
        t.name,
        ts.level + 1, 
        ts.id, 
        t.node_order 
      FROM tree t, tree_search ts 
      WHERE t.parent_id = ts.id 
    ) 
    SELECT * FROM tree_search 
    WHERE level > 0 
    ORDER BY level, parent_id, node_order;
    

    Hier sind die Ergebnisse:

     id |    name    | level | parent_id | node_order 
    ----+------------+-------+-----------+------------
      1 | Node 1     |     1 |         0 |         10
      3 | Node 2     |     1 |         0 |         20
      2 | Node 1.1   |     2 |         1 |         10
      6 | Node 1.2   |     2 |         1 |         20
      5 | Node 2.1   |     2 |         3 |         10
      4 | Node 1.1.1 |     3 |         2 |         10
    (6 rows)
    

    Die Baumknoten sind nach Tiefe geordnet. In der endgültigen Ausgabe würden wir sie in den folgenden Zeilen präsentieren.

    Für jede Ebene werden sie nach parent_id und node_order innerhalb des übergeordneten Elements sortiert. Hier erfahren wir, wie wir sie im Output-Link-Knoten dem übergeordneten Element in dieser Reihenfolge präsentieren können.

    Mit einer solchen Struktur wäre es nicht schwierig, eine wirklich schöne Präsentation in HTML zu erstellen.

    Rekursive CTEs sind in PostgreSQL, IBM DB2, MS SQL Server und Oracle verfügbar .

    Wenn Sie mehr über rekursive SQL-Abfragen erfahren möchten, können Sie entweder die Dokumentation Ihres bevorzugten DBMS überprüfen oder meine beiden Artikel zu diesem Thema lesen:

19

Ab Oracle 9i können Sie CONNECT BY verwenden.

SELECT LPAD(' ', (LEVEL - 1) * 4) || "Name" AS "Name"
FROM (SELECT * FROM TMP_NODE ORDER BY "Order")
CONNECT BY PRIOR "Id" = "ParentId"
START WITH "Id" IN (SELECT "Id" FROM TMP_NODE WHERE "ParentId" = 0)

Ab SQL Server 2005 können Sie einen rekursiven allgemeinen Tabellenausdruck (CTE) verwenden.

WITH [NodeList] (
  [Id]
  , [ParentId]
  , [Level]
  , [Order]
) AS (
  SELECT [Node].[Id]
    , [Node].[ParentId]
    , 0 AS [Level]
    , CONVERT([varchar](MAX), [Node].[Order]) AS [Order]
  FROM [Node]
  WHERE [Node].[ParentId] = 0
  UNION ALL
  SELECT [Node].[Id]
    , [Node].[ParentId]
    , [NodeList].[Level] + 1 AS [Level]
    , [NodeList].[Order] + '|'
      + CONVERT([varchar](MAX), [Node].[Order]) AS [Order]
  FROM [Node]
    INNER JOIN [NodeList] ON [NodeList].[Id] = [Node].[ParentId]
) SELECT REPLICATE(' ', [NodeList].[Level] * 4) + [Node].[Name] AS [Name]
FROM [Node]
  INNER JOIN [NodeList] ON [NodeList].[Id] = [Node].[Id]
ORDER BY [NodeList].[Order]

Beide geben die folgenden Ergebnisse aus.

 Name 
 'Knoten 1' 
 'Node 1.1' 
 'Node 1.1 .1 '
' Node 1.2 '
' Node 2 '
' Node 2.1 '
18
Eric Weilnau

Bills Antwort ist verdammt gut. Diese Antwort fügt einige Dinge hinzu, die mich dazu bringen, mir zu wünschen, dass SO unterstützte Antworten mit Threads).

Auf jeden Fall wollte ich die Baumstruktur und die Order-Eigenschaft unterstützen. Ich habe in jede Node mit dem Namen leftSibling eine einzelne Eigenschaft eingefügt, die dasselbe tut, was Order in der ursprünglichen Frage tun soll (von links nach rechts pflegen) Bestellung).

 mysql> desc node; 
 + ------------- + -------------- + ----- - + ----- + --------- + ---------------- + 
 | Feld | Typ | Null | Schlüssel | Standard | Extra | 
 + ------------- + -------------- + ------ + ----- + --------- + ---------------- + 
 | id | int (11) | NEIN | PRI | NULL | auto_increment | 
 | Name | varchar (255) | YES | | NULL | 
 | LeftSibling | int (11) | NO | | 0 | | 
 + ------------- + -------------- + ------ + ----- + --------- + ---------------- + 
 3 Zeilen im Satz (0,00 Sek.) 
 
 mysql> desc adjacencies; 
 + ------------ + -------- + ------ + ----- + - ------- + ---------------- + 
 | Feld | Typ | Null | Schlüssel | Standard | Extra | 
 + - ----------- + --------- + ------ + ----- + --------- + ----- ----------- + 
 | relationId | int (11) | NO | PRI | NULL | auto_increment | 
 | parent | int (11) | NO | | NULL | | 
 | child | int (11) | NO | | NULL | 
 | pathLen | int (11) | NO | | NULL | | .____.] + ------------ + --------- + ------ + ----- + --------- + ---------------- + 
 4 Zeilen im Satz (0,00 Sek.) 

Weitere Details und SQL-Code auf meinem Blog .

Vielen Dank, Bill, Ihre Antwort war hilfreich für den Einstieg!

9
bobobobo

Nun, wenn ich die Wahl hätte, würde ich Objekte verwenden. Ich würde ein Objekt für jeden Datensatz erstellen, in dem jedes Objekt eine children -Auflistung hat, und sie alle in einem Assoc-Array (/ hashtable) speichern, in dem die ID der Schlüssel ist. Und blitz einmal durch die Sammlung und füge die Kinder den entsprechenden Kinderfeldern hinzu. Einfach.

Aber weil es Ihnen keinen Spaß macht, die Verwendung eines guten OOP einzuschränken, würde ich wahrscheinlich iterieren, basierend auf:

function PrintLine(int pID, int level)
    foreach record where ParentID == pID
        print level*tabs + record-data
        PrintLine(record.ID, level + 1)

PrintLine(0, 0)

Bearbeiten: Dies ähnelt ein paar anderen Einträgen, aber ich denke, es ist etwas sauberer. Eines möchte ich hinzufügen: Dies ist extrem SQL-intensiv. Es ist böse . Wenn Sie die Wahl haben, gehen Sie den Weg OOP

7
Oli

Dies wurde schnell geschrieben und ist weder hübsch noch effizient (außerdem ist das Konvertieren zwischen int und Integer sehr ärgerlich), aber es funktioniert.

Es verstößt wahrscheinlich gegen die Regeln, da ich meine eigenen Objekte erstelle, aber hey, ich mache das als Ablenkung von der eigentlichen Arbeit :)

Dies setzt auch voraus, dass die Ergebnismenge/-tabelle vollständig in eine Struktur eingelesen wird, bevor Sie mit der Erstellung von Knoten beginnen. Dies wäre nicht die beste Lösung, wenn Sie über Hunderttausende von Zeilen verfügen.

public class Node {

    private Node parent = null;

    private List<Node> children;

    private String name;

    private int id = -1;

    public Node(Node parent, int id, String name) {
        this.parent = parent;
        this.children = new ArrayList<Node>();
        this.name = name;
        this.id = id;
    }

    public int getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public void addChild(Node child) {
        children.add(child);
    }

    public List<Node> getChildren() {
        return children;
    }

    public boolean isRoot() {
        return (this.parent == null);
    }

    @Override
    public String toString() {
        return "id=" + id + ", name=" + name + ", parent=" + parent;
    }
}

public class NodeBuilder {

    public static Node build(List<Map<String, String>> input) {

        // maps id of a node to it's Node object
        Map<Integer, Node> nodeMap = new HashMap<Integer, Node>();

        // maps id of a node to the id of it's parent
        Map<Integer, Integer> childParentMap = new HashMap<Integer, Integer>();

        // create special 'root' Node with id=0
        Node root = new Node(null, 0, "root");
        nodeMap.put(root.getId(), root);

        // iterate thru the input
        for (Map<String, String> map : input) {

            // expect each Map to have keys for "id", "name", "parent" ... a
            // real implementation would read from a SQL object or resultset
            int id = Integer.parseInt(map.get("id"));
            String name = map.get("name");
            int parent = Integer.parseInt(map.get("parent"));

            Node node = new Node(null, id, name);
            nodeMap.put(id, node);

            childParentMap.put(id, parent);
        }

        // now that each Node is created, setup the child-parent relationships
        for (Map.Entry<Integer, Integer> entry : childParentMap.entrySet()) {
            int nodeId = entry.getKey();
            int parentId = entry.getValue();

            Node child = nodeMap.get(nodeId);
            Node parent = nodeMap.get(parentId);
            parent.addChild(child);
        }

        return root;
    }
}

public class NodePrinter {

    static void printRootNode(Node root) {
        printNodes(root, 0);
    }

    static void printNodes(Node node, int indentLevel) {

        printNode(node, indentLevel);
        // recurse
        for (Node child : node.getChildren()) {
            printNodes(child, indentLevel + 1);
        }
    }

    static void printNode(Node node, int indentLevel) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < indentLevel; i++) {
            sb.append("\t");
        }
        sb.append(node);

        System.out.println(sb.toString());
    }

    public static void main(String[] args) {

        // setup dummy data
        List<Map<String, String>> resultSet = new ArrayList<Map<String, String>>();
        resultSet.add(newMap("1", "Node 1", "0"));
        resultSet.add(newMap("2", "Node 1.1", "1"));
        resultSet.add(newMap("3", "Node 2", "0"));
        resultSet.add(newMap("4", "Node 1.1.1", "2"));
        resultSet.add(newMap("5", "Node 2.1", "3"));
        resultSet.add(newMap("6", "Node 1.2", "1"));

        Node root = NodeBuilder.build(resultSet);
        printRootNode(root);

    }

    //convenience method for creating our dummy data
    private static Map<String, String> newMap(String id, String name, String parentId) {
        Map<String, String> row = new HashMap<String, String>();
        row.put("id", id);
        row.put("name", name);
        row.put("parent", parentId);
        return row;
    }
}
5
matt b

Es gibt wirklich gute Lösungen, die die interne Btree-Darstellung von SQL-Indizes ausnutzen. Dies basiert auf großartigen Recherchen, die um 1998 durchgeführt wurden.

Hier ist eine Beispieltabelle (in MySQL).

CREATE TABLE `node` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `tw` int(10) unsigned NOT NULL,
  `pa` int(10) unsigned DEFAULT NULL,
  `sz` int(10) unsigned DEFAULT NULL,
  `nc` int(11) GENERATED ALWAYS AS (tw+sz) STORED,
  PRIMARY KEY (`id`),
  KEY `node_tw_index` (`tw`),
  KEY `node_pa_index` (`pa`),
  KEY `node_nc_index` (`nc`),
  CONSTRAINT `node_pa_fk` FOREIGN KEY (`pa`) REFERENCES `node` (`tw`) ON DELETE CASCADE
)

Die einzigen Felder, die für die Baumdarstellung benötigt werden, sind:

  • tw: Der DFS-Vorbestellungsindex von links nach rechts, wobei root = 1 ist.
  • pa: Der Verweis (mit tw) auf den übergeordneten Knoten root hat den Wert null.
  • sz: Die Größe des Zweigs des Knotens einschließlich seiner selbst.
  • nc: wird als syntaktischer Zucker verwendet. es ist tw + nc und repräsentiert das tw des "nächsten Kindes" des Knotens.

Hier ist ein Beispiel einer 24-Knoten-Population, geordnet nach Tw:

+-----+---------+----+------+------+------+
| id  | name    | tw | pa   | sz   | nc   |
+-----+---------+----+------+------+------+
|   1 | Root    |  1 | NULL |   24 |   25 |
|   2 | A       |  2 |    1 |   14 |   16 |
|   3 | AA      |  3 |    2 |    1 |    4 |
|   4 | AB      |  4 |    2 |    7 |   11 |
|   5 | ABA     |  5 |    4 |    1 |    6 |
|   6 | ABB     |  6 |    4 |    3 |    9 |
|   7 | ABBA    |  7 |    6 |    1 |    8 |
|   8 | ABBB    |  8 |    6 |    1 |    9 |
|   9 | ABC     |  9 |    4 |    2 |   11 |
|  10 | ABCD    | 10 |    9 |    1 |   11 |
|  11 | AC      | 11 |    2 |    4 |   15 |
|  12 | ACA     | 12 |   11 |    2 |   14 |
|  13 | ACAA    | 13 |   12 |    1 |   14 |
|  14 | ACB     | 14 |   11 |    1 |   15 |
|  15 | AD      | 15 |    2 |    1 |   16 |
|  16 | B       | 16 |    1 |    1 |   17 |
|  17 | C       | 17 |    1 |    6 |   23 |
| 359 | C0      | 18 |   17 |    5 |   23 |
| 360 | C1      | 19 |   18 |    4 |   23 |
| 361 | C2(res) | 20 |   19 |    3 |   23 |
| 362 | C3      | 21 |   20 |    2 |   23 |
| 363 | C4      | 22 |   21 |    1 |   23 |
|  18 | D       | 23 |    1 |    1 |   24 |
|  19 | E       | 24 |    1 |    1 |   25 |
+-----+---------+----+------+------+------+

Jedes Baumergebnis kann nicht rekursiv durchgeführt werden. Zum Beispiel, um eine Liste der Vorfahren des Knotens bei tw = '22 'zu erhalten

Ahnen

select anc.* from node me,node anc 
where me.tw=22 and anc.nc >= me.tw and anc.tw <= me.tw 
order by anc.tw;
+-----+---------+----+------+------+------+
| id  | name    | tw | pa   | sz   | nc   |
+-----+---------+----+------+------+------+
|   1 | Root    |  1 | NULL |   24 |   25 |
|  17 | C       | 17 |    1 |    6 |   23 |
| 359 | C0      | 18 |   17 |    5 |   23 |
| 360 | C1      | 19 |   18 |    4 |   23 |
| 361 | C2(res) | 20 |   19 |    3 |   23 |
| 362 | C3      | 21 |   20 |    2 |   23 |
| 363 | C4      | 22 |   21 |    1 |   23 |
+-----+---------+----+------+------+------+

Geschwister und Kinder sind trivial.

Nachkommen

Zum Beispiel die Menge (Verzweigung) von Knoten, die bei tw = 17 verwurzelt sind.

select des.* from node me,node des 
where me.tw=17 and des.tw < me.nc and des.tw >= me.tw 
order by des.tw;
+-----+---------+----+------+------+------+
| id  | name    | tw | pa   | sz   | nc   |
+-----+---------+----+------+------+------+
|  17 | C       | 17 |    1 |    6 |   23 |
| 359 | C0      | 18 |   17 |    5 |   23 |
| 360 | C1      | 19 |   18 |    4 |   23 |
| 361 | C2(res) | 20 |   19 |    3 |   23 |
| 362 | C3      | 21 |   20 |    2 |   23 |
| 363 | C4      | 22 |   21 |    1 |   23 |
+-----+---------+----+------+------+------+

Zusätzliche Hinweise

Diese Methode ist äußerst nützlich, wenn eine weitaus größere Anzahl von Lesevorgängen vorhanden ist als Einfügungen oder Aktualisierungen.

Da das Einfügen, Verschieben oder Aktualisieren eines Knotens im Baum eine Anpassung des Baums erfordert, muss die Tabelle vor Beginn der Aktion gesperrt werden.

Die Einfüge-/Löschkosten sind hoch, da die Werte für tw index und sz (Verzweigungsgröße) auf allen Knoten nach dem Einfügepunkt bzw. für alle Vorfahren aktualisiert werden müssen.

Beim Verschieben von Zweigen wird der Wert tw des Zweigs außerhalb des Bereichs verschoben. Daher müssen beim Verschieben eines Zweigs auch die Fremdschlüsseleinschränkungen deaktiviert werden. Es sind im Wesentlichen vier Abfragen erforderlich, um einen Zweig zu verschieben:

  • Bewegen Sie den Zweig außerhalb der Reichweite.
  • Schließen Sie die verbleibende Lücke. (Der verbleibende Baum ist jetzt normalisiert).
  • Öffne die Lücke, in die es gehen wird.
  • Bewegen Sie den Zweig in seine neue Position.

Baumabfragen anpassen

Das Öffnen/Schließen von Lücken im Baum ist eine wichtige Unterfunktion, die von Erstellungs-/Aktualisierungs-/Löschmethoden verwendet wird. Deshalb füge ich sie hier ein.

Wir brauchen zwei Parameter - ein Flag, das angibt, ob wir verkleinern oder vergrößern, und den Tw-Index des Knotens. Also zum Beispiel tw = 18 (was eine Verzweigungsgröße von 5 hat). Nehmen wir an, wir verkleinern (entfernen tw). Dies bedeutet, dass wir in den Aktualisierungen des folgenden Beispiels '-' anstelle von '+' verwenden.

Wir verwenden zuerst eine (leicht veränderte) Vorfahrenfunktion, um den sz-Wert zu aktualisieren.

update node me, node anc set anc.sz = anc.sz - me.sz from 
node me, node anc where me.tw=18 
and ((anc.nc >= me.tw and anc.tw < me.pa) or (anc.tw=me.pa));

Dann müssen wir das Tw für diejenigen anpassen, deren Tw höher als der zu entfernende Zweig ist.

update node me, node anc set anc.tw = anc.tw - me.sz from 
node me, node anc where me.tw=18 and anc.tw >= me.tw;

Dann müssen wir das übergeordnete Element für diejenigen anpassen, deren Pa-Tw höher ist als der zu entfernende Zweig.

update node me, node anc set anc.pa = anc.pa - me.sz from 
node me, node anc where me.tw=18 and anc.pa >= me.tw;
4
Konchog

Sie können jede andere Datenstruktur mit einer Hashmap emulieren, das ist also keine schreckliche Einschränkung. Durch Scannen von oben nach unten erstellen Sie eine Hashmap für jede Zeile der Datenbank mit einem Eintrag für jede Spalte. Fügen Sie jede dieser Hashmaps einer "Master" -Hashmap hinzu, die auf der ID angegeben ist. Wenn ein Knoten ein "übergeordnetes Element" hat, das Sie noch nicht gesehen haben, erstellen Sie einen Platzhaltereintrag für dieses Element in der Master-Hashmap und füllen Sie ihn aus, wenn Sie den tatsächlichen Knoten sehen.

Führen Sie zum Ausdrucken einen einfachen Tiefendurchlauf durch die Daten durch und verfolgen Sie dabei die Einzugsebene. Sie können dies vereinfachen, indem Sie für jede Zeile einen "untergeordneten" Eintrag speichern und beim Scannen der Daten mit Daten füllen.

Ob es eine "bessere" Möglichkeit gibt, einen Baum in einer Datenbank zu speichern, hängt davon ab, wie Sie die Daten verwenden. Ich habe Systeme mit einer bekannten maximalen Tiefe gesehen, die für jede Ebene in der Hierarchie eine andere Tabelle verwendeten. Das ist sehr sinnvoll, wenn die Ebenen im Baum doch nicht ganz gleich sind (Kategorien der obersten Ebene unterscheiden sich von den Blättern).

3
Mark Bessey

Angenommen, Sie wissen, dass die Stammelemente Null sind, dann ist hier der Pseudocode, der in Text ausgegeben werden soll:

function PrintLevel (int curr, int level)
    //print the indents
    for (i=1; i<=level; i++)
        print a tab
    print curr \n;
    for each child in the table with a parent of curr
        PrintLevel (child, level+1)


for each elementID where the parentid is zero
    PrintLevel(elementID, 0)
3
wcm

Wenn verschachtelte Hash-Maps oder Arrays erstellt werden können, kann ich einfach die Tabelle von Anfang an durchgehen und jedes Element dem verschachtelten Array hinzufügen. Ich muss jede Zeile bis zum Wurzelknoten verfolgen, um zu wissen, in welche Ebene des verschachtelten Arrays eingefügt werden soll. Ich kann Memoization verwenden, damit ich nicht immer wieder nach demselben Elternteil suchen muss.

Bearbeiten: Ich würde zuerst die gesamte Tabelle in ein Array einlesen, damit der DB nicht wiederholt abgefragt wird. Dies ist natürlich nicht praktisch, wenn Ihr Tisch sehr groß ist.

Nachdem die Struktur erstellt wurde, muss ich zuerst eine Tiefe durchlaufen und den HTML-Code ausdrucken.

Es gibt keine bessere grundsätzliche Möglichkeit, diese Informationen in einer Tabelle zu speichern (ich könnte mich jedoch irren;) und würde gerne eine bessere Lösung finden). Wenn Sie jedoch ein Schema erstellen, um dynamisch erstellte DB-Tabellen zu verwenden, haben Sie auf Grund der Einfachheit und des Risikos der SQL-Hölle eine ganz neue Welt erschlossen.

1
tchen

Um Bills SQL-Lösung zu erweitern, können Sie im Grunde dasselbe mit einem flachen Array tun. Wenn Ihre Zeichenfolgen alle die gleiche Länge haben und Ihre maximale Anzahl von untergeordneten Elementen bekannt ist (z. B. in einem Binärbaum), können Sie dies auch mit einer einzelnen Zeichenfolge (Zeichenfeld) tun. Wenn Sie eine beliebige Anzahl von Kindern haben, erschwert dies die Dinge ein wenig ... Ich müsste meine alten Notizen überprüfen, um zu sehen, was getan werden kann.

Wenn Sie dann ein wenig Speicherplatz opfern, insbesondere wenn Ihr Baum dünn und/oder unausgeglichen ist, können Sie mit ein wenig Indexmathematik zufällig auf alle Zeichenfolgen zugreifen, indem Sie Ihren Baum mit der Breite zuerst wie folgt im Array speichern (für eine Binärdatei) Baum):

String[] nodeArray = [L0root, L1child1, L1child2, L2Child1, L2Child2, L2Child3, L2Child4] ...

du kennst deine Saitenlänge, du kennst sie

Ich bin gerade auf der Arbeit, kann also nicht viel Zeit damit verbringen, aber mit Interesse kann ich ein bisschen Code holen, um dies zu tun.

Wir verwenden dies, um in binären Bäumen zu suchen, die aus DNA-Codons bestehen, einem Prozess, der den Baum erstellt hat. Dann haben wir ihn abgeflacht, um nach Textmustern zu suchen. Wenn er gefunden wird, erhalten wir den Knoten durch Indexmathematik (von oben umgekehrt) zurück ... sehr schnell und effizient, robust Unser Baum hatte selten leere Knoten, aber wir konnten Gigabyte an Daten im Handumdrehen suchen.

1
Newtopian

Wenn sich die Elemente in der in Ihrem Beispiel gezeigten Baumreihenfolge befinden, können Sie etwa das folgende Python Beispiel verwenden:

delimiter = '.'
stack = []
for item in items:
  while stack and not item.startswith(stack[-1]+delimiter):
    print "</div>"
    stack.pop()
  print "<div>"
  print item
  stack.append(item)

Auf diese Weise wird ein Stapel verwaltet, der die aktuelle Position im Baum darstellt. Für jedes Element in der Tabelle werden Stapelelemente angezeigt (wobei die entsprechenden Divs geschlossen werden), bis das übergeordnete Element des aktuellen Elements gefunden wird. Dann gibt es den Start dieses Knotens aus und schiebt ihn zum Stapel.

Wenn Sie den Baum mit Einrückungen anstatt mit verschachtelten Elementen ausgeben möchten, können Sie die print-Anweisungen einfach überspringen, um die divs zu drucken, und vor jedem Element eine Anzahl von Leerzeichen ausgeben, die einem Vielfachen der Größe des Stapels entspricht. Zum Beispiel in Python:

print "  " * len(stack)

Mit dieser Methode können Sie auch problemlos eine Reihe verschachtelter Listen oder Wörterbücher erstellen.

Bearbeiten: Ich sehe aus Ihrer Klarstellung, dass die Namen nicht als Knotenpfade gedacht waren. Das legt einen alternativen Ansatz nahe:

idx = {}
idx[0] = []
for node in results:
  child_list = []
  idx[node.Id] = child_list
  idx[node.ParentId].append((node, child_list))

Dies konstruiert einen Baum von Tupelarrays (!). idx [0] repräsentiert die Wurzel (n) des Baums. Jedes Element in einem Array ist ein 2-Tupel, das aus dem Knoten selbst und einer Liste aller untergeordneten Elemente besteht. Einmal erstellt, können Sie IDX [0] beibehalten und IDX verwerfen, es sei denn, Sie möchten über ihre ID auf Knoten zugreifen.

1
Nick Johnson

Denken Sie daran, nosql-Tools wie neo4j für hierarchische Strukturen zu verwenden. Beispielsweise verwendet eine Netzwerkanwendung wie linkedin couchbase (eine andere nosql-Lösung).

Verwenden Sie nosql jedoch nur für Abfragen auf Datamart-Ebene und nicht zum Speichern/Verwalten von Transaktionen