Nachdem MySQL 8.0 rekursive Abfragen unterstützt , können wir sagen, dass alle gängigen SQL-Datenbanken rekursive Abfragen in Standardsyntax unterstützen.
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 Recursive Query Throwdown im Jahr 2017 getestet .
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:
- Adjazenzliste (die "übergeordnete" 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. Weitere Informationen zu diesen Designs finden Sie unter " Bäume und Hierarchien in SQL für Smarties " von Joe Celko.
Normalerweise bevorzuge ich ein Design namens Closure Table (auch bekannt als "Adjacency Relation") zum Speichern von Daten mit Baumstruktur. Es erfordert eine andere Tabelle, aber das Abfragen von Bäumen ist ziemlich einfach.
Ich beschreibe die Closure Table in meinen Präsentationsmodellen für hierarchische Daten mit SQL und PHP und in meinem Buch SQL Antipatterns: Vermeiden 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 Table, in der es eine direkte Abstammung von einem Knoten zum anderen gibt. Fügen Sie für jeden Knoten eine Zeile ein, um auf sich selbst zu verweisen. 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 erhalten:
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 zu unmittelbaren Kindern (oder unmittelbaren Eltern). Sie können der path_length
Spalte " " eine Spalte hinzufügen ClosureTable
, um die Abfrage eines unmittelbaren Kindes oder Elternteils (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, die Nachkommen von Knoten 1 sind, zurückzugeben, sie mit der FlatTable zu verknüpfen, die andere Knotenattribute enthält, z. B. name
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 Bearbeitung vorgeschlagen. SO-Moderatoren haben die Bearbeitung genehmigt, aber ich mache sie rückgängig.
Die Bearbeitung schlug vor, dass ORDER BY in der letzten Abfrage oben sein sollte ORDER BY b.path_length, f.name
, vermutlich um sicherzustellen, dass die Reihenfolge mit der Hierarchie übereinstimmt. Dies funktioniert jedoch nicht, da "Knoten 1.1.1" nach "Knoten 1.2" bestellt wird.
Wenn Sie möchten, dass die Reihenfolge auf sinnvolle Weise mit der Hierarchie übereinstimmt, ist dies möglich, jedoch nicht einfach durch die Reihenfolge nach Pfadlänge. Siehe zum Beispiel meine Antwort auf die hierarchische Datenbank der MySQL Closure Table - So ziehen Sie Informationen in der richtigen Reihenfolge ab .