it-swarm.com.de

Wie stellt man einen Datenbaum in SQL dar?

Ich schreibe eine Datenbaumstruktur, die aus einem Baum und einem Baumknoten kombiniert wird. Tree enthält die Root- und Top-Level-Aktionen für die Daten ... Ich benutze eine UI-Bibliothek, um den Tree in einem Windows-Formular darzustellen, in dem ich den Tree an die TreeView binden kann.

Ich muss diesen Baum und die Knoten in der Datenbank speichern. Was ist der beste Weg, um den Baum zu speichern und die folgenden Funktionen zu erhalten:

  1. Intuitive Umsetzung.
  2. Einfach zu binden. Wird leicht vom Baum zur DB-Struktur und zurück (falls vorhanden) verschoben

Ich hatte 2 Ideen. Die erste besteht darin, die Daten in einem Einzeiler in einer Tabelle zu serialisieren. Die zweite besteht darin, in Tabellen zu speichern, aber wenn ich zu Datenentitäten wechsle, werde ich die Zeilenzustände in der Tabelle auf geänderten Knoten verlieren.

Irgendwelche Ideen?

38
Avi Harush

Die einfachste Implementierung ist die Struktur adjacency list:

id  parent_id  data

Einige Datenbanken, insbesondere MySQL, haben jedoch Probleme beim Umgang mit diesem Modell, da rekursive Abfragen ausgeführt werden müssen, denen MySQL fehlt.

Ein anderes Modell ist verschachtelte Mengen:

id lft rgt data

wobei lft und rgt beliebige Werte sind, die die Hierarchie definieren (die lft, rgt eines Kindes sollte innerhalb der lft, rgt eines Elternteils liegen)

Dies erfordert keine rekursiven Abfragen, ist jedoch langsamer und schwieriger zu warten.

In MySQL kann dies jedoch mit SPATIAL Abitilies verbessert werden.

Sehen Sie diese Artikel in meinem Blog:

für detailliertere Erklärungen.

30
Quassnoi

Ich habe diese Slidshare über SQL-Antipatterns mit einem Lesezeichen versehen, in der verschiedene Alternativen behandelt werden: http://www.slideshare.net/billkarwin/sql-antipatterns-strike-back?src=embed

Die Empfehlung von dort ist die Verwendung einer Schließungstabelle (wird in den Folien erklärt).

Hier ist die Zusammenfassung (Folie 77):

                  | Query Child | Query Subtree | Modify Tree | Ref. Integrity
Adjacency List    |    Easy     |     Hard      |    Easy     |      Yes
Path Enumeration  |    Easy     |     Easy      |    Hard     |      No
Nested Sets       |    Hard     |     Easy      |    Hard     |      No
Closure Table     |    Easy     |     Easy      |    Easy     |      Yes
23
Björn

Ich bin überrascht, dass niemand die materialized path -Lösung erwähnte, die wahrscheinlich die schnellste Art ist, mit Bäumen in Standard-SQL zu arbeiten.

Bei diesem Ansatz hat jeder Knoten in der Baumstruktur eine Spalte Pfad, in der der vollständige Pfad von der Wurzel zum Knoten gespeichert wird. Dies beinhaltet sehr einfache und schnelle Abfragen.

Schauen Sie sich die Beispieltabelle node an:

+---------+-------+
| node_id | path  |
+---------+-------+
| 0       |       |
| 1       | 1     |
| 2       | 2     |
| 3       | 3     |
| 4       | 1.4   |
| 5       | 2.5   |
| 6       | 2.6   |
| 7       | 2.6.7 |
| 8       | 2.6.8 |
| 9       | 2.6.9 |
+---------+-------+

Um die Kinder des Knotens x zu erhalten, können Sie die folgende Abfrage schreiben:

SELECT * FROM node WHERE path LIKE CONCAT((SELECT path FROM node WHERE node_id = x), '.%')

Beachten Sie, dass die Spalte path indiziert werden sollte, um mit der -ARTIGEN KLAUSEL eine schnelle Ausführung zu erzielen.

8
niutech

Wenn Sie PostgreSQL verwenden, können Sie ltree verwenden, ein Paket in der Contrib-Erweiterung (standardmäßig enthalten), das die Baumdatenstruktur implementiert.

Aus den docs :

CREATE TABLE test (path ltree);
INSERT INTO test VALUES ('Top');
INSERT INTO test VALUES ('Top.Science');
INSERT INTO test VALUES ('Top.Science.Astronomy');
INSERT INTO test VALUES ('Top.Science.Astronomy.Astrophysics');
INSERT INTO test VALUES ('Top.Science.Astronomy.Cosmology');
INSERT INTO test VALUES ('Top.Hobbies');
INSERT INTO test VALUES ('Top.Hobbies.Amateurs_Astronomy');
INSERT INTO test VALUES ('Top.Collections');
INSERT INTO test VALUES ('Top.Collections.Pictures');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Stars');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Galaxies');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Astronauts');
CREATE INDEX path_Gist_idx ON test USING Gist (path);
CREATE INDEX path_idx ON test USING BTREE (path);

Sie können Abfragen wie:

ltreetest=> SELECT path FROM test WHERE path <@ 'Top.Science';
                path
------------------------------------
 Top.Science
 Top.Science.Astronomy
 Top.Science.Astronomy.Astrophysics
 Top.Science.Astronomy.Cosmology
(4 rows)
4

Es hängt davon ab, wie Sie die Daten abfragen und aktualisieren. Wenn Sie alle Daten in einer Zeile speichern, handelt es sich im Grunde um eine einzelne Einheit, die Sie nicht abfragen oder teilweise aktualisieren können, ohne alle Daten neu zu schreiben.

Wenn Sie jedes Element als Zeile speichern möchten, lesen Sie zunächst Verwalten von hierarchischen Daten in MySQL (MySQL-spezifisch, der Hinweis gilt jedoch auch für viele andere Datenbanken).

Wenn Sie immer nur auf einen gesamten Baum zugreifen, ist es aufgrund des Adjazenzlistenmodells schwierig, alle Knoten unter dem Stamm abzurufen, ohne eine rekursive Abfrage zu verwenden. Wenn Sie eine zusätzliche Spalte hinzufügen, die wieder mit dem Kopf verknüpft ist, können Sie SELECT * WHERE head_id = @id ausführen und den gesamten Baum in einer nicht rekursiven Abfrage anzeigen, die Datenbank wird jedoch nicht normalisiert.

Einige Datenbanken verfügen über benutzerdefinierte Erweiterungen, die das Speichern und Abrufen von hierarchischen Daten vereinfachen. Oracle verfügt beispielsweise über CONNECT BY .

3
Mark Byers

Da dies die beste Antwort ist, wenn Sie bei einer Google-Suche nach "SQL-Bäumen" fragen, werde ich versuchen, diese aus heutiger Sicht (Dezember 2018) zu aktualisieren.

Die meisten Antworten implizieren, dass die Verwendung einer Adjazenzliste sowohl einfach als auch langsam ist und daher andere Methoden empfehlen.

Seit Version 8 (veröffentlicht im April 2018) unterstützt MySQL rekursive Common Table-Ausdrücke (CTE) . MySQL ist etwas spät in der Show, aber dies eröffnet eine neue Option.

Es gibt ein Tutorial here , das die Verwendung rekursiver Abfragen zur Verwaltung einer Adjazenzliste erläutert.

Da die Rekursion jetzt vollständig innerhalb der Datenbank-Engine ausgeführt wird, ist sie wesentlich schneller als früher (wenn sie in der Skript-Engine ausgeführt werden musste).

Das Blog hier gibt einige Messungen an (die sowohl für Postgres als für MySQL voreingenommen sind), aber es zeigt, dass Adjazenzlisten nicht langsam sein müssen.

Meine Schlussfolgerung heute ist also:

  • Die einfache Adjazenzliste kann schnell genug sein, wenn das Datenbankmodul Rekursion unterstützt.
  • Machen Sie einen Benchmark mit Ihren eigenen Daten und Ihrer eigenen Engine.
  • Verlassen Sie sich nicht auf veraltete Empfehlungen, um auf die "beste" Methode hinzuweisen.
2
Holger Waldmann

Der beste Weg, denke ich in der Tat, ist, jedem Knoten eine ID und eine parent_id zu geben, wobei die Eltern-ID die ID des Elternknotens ist. Dies hat einige Vorteile

  1. Wenn Sie einen Knoten aktualisieren möchten, müssen Sie nur die Daten dieses Knotens neu schreiben.
  2. Wenn Sie nur einen bestimmten Knoten abfragen möchten, können Sie genau die Informationen abrufen, die Sie möchten, wodurch die Datenbankverbindung weniger belastet wird
  3. Viele Programmiersprachen verfügen über Funktionen, um MySQL-Daten in XML oder Json zu transformieren. Dadurch wird es einfacher, Ihre Anwendung mit einer API zu öffnen.
0
bigblind

So etwas wie Tabellenknoten, in denen jede Knotenzeile eine übergeordnete ID enthält (zusätzlich zu den normalen Knotendaten). Für root ist das übergeordnete Element NULL.

Natürlich ist das Auffinden von Kindern etwas zeitaufwändiger, aber auf diese Weise wird die Datenbank recht einfach.

0
Kimvais