Ist Ihnen aufgefallen, dass es bei einem solchen Tabellendesign schwierig ist, alle Nachkommen eines Knotens abzufragen? Sie können zugehörige Abfragen verwenden, um einen Kommentar und seine Nachkommen abzurufen:
SELECT c1.*, c2.* FROM comments c1 LEFT OUTER JOIN comments c2 ON c2.parent_id = c1.comment_id;
Diese Abfrage kann jedoch nur zwei Datenebenen abrufen. Das Merkmal dieses Baumtyps besteht darin, dass er auf jede beliebige Tiefe erweitert werden kann und Sie über eine entsprechende Methode verfügen müssen, um seine Tiefendaten zu erhalten. Beispielsweise müssen Sie möglicherweise die Anzahl der Kommentarzweige zählen oder die Gesamtkosten einer Maschine berechnen.
In manchen Fällen ist die Verwendung von Adjazenzlisten in Projekten genau richtig. Der Vorteil des Adjazenzlistendesigns besteht darin, dass die direkten übergeordneten und untergeordneten Knoten eines bestimmten Knotens schnell abgerufen werden können und auch das Einfügen neuer Knoten einfach ist. Wenn eine solche Anforderung alle Vorgänge Ihres Projekts an hierarchischen Daten betrifft, funktioniert die Verwendung von Adjazenzlisten gut.
Wenn Sie auf das obige Baummodell stoßen, können mehrere Optionen in Betracht gezogen werden: Pfadaufzählung, verschachtelte Menge und Abschlusstabelle. Diese Lösungen sehen oft viel komplexer aus als Adjazenzlisten, erleichtern jedoch bestimmte Vorgänge, die bei der Verwendung von Adjazenzlisten kompliziert oder ineffizient wären. Wenn Ihr Projekt diese Operationen wirklich bereitstellen muss, sind diese Designs die bessere Wahl für Adjazenzlisten.
1. Pfadaufzählung
In der Kommentartabelle verwenden wir das Pfadfeld vom Typ varchar, um das ursprüngliche Feld „parent_id“ zu ersetzen. Der in diesem Pfadfeld gespeicherte Inhalt ist die Sequenz vom obersten Vorfahren des aktuellen Knotens bis zu seiner eigenen Sequenz. Genau wie beim UNIX-Pfad können Sie sogar „/“ als Pfadtrennzeichen verwenden.
comment_id |
path |
author |
comment |
1 |
1 |
小明 |
我不大认同这个观点 |
2 |
1/2 |
小张 |
我也不认同 |
3 |
1/2/3 |
小红 |
我同意楼上 |
4 |
1/4 |
小全 |
你为什么不认同呢 |
5 |
1/4/5 |
小明 |
我以前遇到过这情况 |
6 |
1/4/5/6 |
小张 |
那也不代表你所说是对的 |
7 |
1/4/5/7 |
小新 |
这个视情况而定吧 |
你可以通过比较每个节点的路径来查询一个节点祖先。比如:要找到评论#7, 路径是 1/4/5/7一 的祖先,可以这么做:
SELECT * FROM comments AS c WHERE '1/4/5/7' LIKE c.path || '%' ;
这句话查询语句会匹配到路径为 1/4/5/%,1/4/% 以及 1/% 的节点,而这些节点就是评论#7的祖先。
同时还可以通过将LIKE 关键字两边的参数互换,来查询一个给定节点的所有后代。比如查询评论#4,路径path为 ‘1/4’ 的所有后代,可以使用如下语句:
SELECT * FROM comemnts AS c WHERE c.path LIKE '1/4' || '%' ;
这句查询语句所有能找到的后台路径分别是:1/4/5、1/4/5/6、1/4/5/7。
一旦你可以很简单地获取一棵子树或者从子孙节点到祖先节点的路径,你就可以很简单地实现更多的查询,如查询一颗子树所有节点上值的总和。
插入一个节点也可以像使用邻接表一样地简单。你所需要做的只是复制一份要插入节点的父亲节点路径,并将这个新节点的ID追加到路径末尾即可。
路径枚举也存在一些缺点,比如数据库不能确保路径的格式总是正确或者路径中的节点确实存在。依赖于应用程序的逻辑代码来维护路径的字符串,并且验证字符串的正确性开销很大。无论将varchar 的长度设定为多大,依旧存在长度的限制,因而并不能够支持树结构无限扩展。
二、 嵌套集
嵌套集解决方案是存储子孙节点的相关信息,而不是节点的直接祖先。我们使用两个数字来编码每个节点,从而表示这一信息,可以将这两个数字称为nsleft 和 nsright。
每个节点通过如下的方式确定nsleft 和nsright 的值:nsleft的数值小于该节点所有后代ID,同时nsright 的值大于该节点的所有后代的ID。这些数字和comment_id 的值并没有任何关联。
确定这三个值(nsleft,comment_id,nsright)的简单方法是对树进行一次深度优先遍历,在逐层深入的过程中依次递增地分配nsleft的值,并在返回时依次递增地分配nsright的值。得到数据如下: