ClosureTable直譯過來叫閉包表?不過不重要,ClosureTable以一張表存儲節點之間的關係、其中包含了任何兩個有關係(上下級)節點的關聯信息sql
ClosureTable演示
數據庫
定義關係表CategoryTree,其包含3個字段:閉包
ancestor 祖先:上級節點的id
descendant 子代:下級節點的id
distance 距離:子代到祖先中間隔了幾級ide
這三個字段的組合是惟一的,由於在樹中,一條路徑能夠標識一個節點,因此能夠直接把它們的組合做爲主鍵。以圖爲例,節點6到它上一級的節點(節點4)距離爲1在數據庫中存儲爲ancestor=4,descendant=6,distance=1,到上兩級的節點(節點1)距離爲2,因而有ancestor=1,descendant=6,distance=2,到根節點的距離爲3也是如此,最後還要包含一個到本身的鏈接,固然距離就是0了。code
這樣一來,不盡表中包含了全部的路徑信息,還在帶上了路徑中每一個節點的位置(距離),對於樹結構經常使用的查詢都可以很方便的處理。下面看看如何用用它來實現咱們的需求。
4.1 子節點查詢對象
查詢id爲5的節點的直屬子節點:blog
SELECT descendant FROM CategoryTree WHERE ancestor=5 AND distance=1
查詢全部子節點:排序
SELECT descendant FROM CategoryTree WHERE ancestor=5 AND distance>0
查詢某個上級節點的子節點,換句話說就是查詢具備指定上級節點的節點,也就是ancestor字段等於上級節點id便可,第二個距離distance決定了查詢的對象是由上級往下那一層的,等於1就是往下一層(直屬子節點),大於0就是全部子節點。這兩個查詢都是一句完成。
4.2 路徑查詢遞歸
查詢由根節點到id爲10的節點之間的全部節點,按照層級由小到大排序it
SELECT ancestor FROM CategoryTree WHERE descendant=10 ORDER BY distance DESC
查詢id爲10的節點(含)到id爲3的節點(不含)之間的全部節點,按照層級由小到大排序
SELECT ancestor FROM CategoryTree WHERE descendant=10 AND distance<(SELECT distance FROM CategoryTree WHERE descendant=10 AND ancestor=3) ORDER BY distance DESC
查詢路徑,只須要知道descendant便可,由於descendant字段所在的行就是記錄這個節點與其上級節點的關係。若是要過濾掉一些則能夠限制distance的值。
4.3 查詢節點所在的層級(深度)
查詢id爲5的節點是第幾級的
SELECT distance FROM CategoryTree WHERE descendant=5 AND ancestor=0
查詢id爲5的節點是id爲10的節點往下第幾級
SELECT distance FROM CategoryTree WHERE descendant=5 AND ancestor=10
查詢層級(深度)很是簡單,由於distance字段就是。直接以上下級的節點id爲條件,查詢距離便可。
4.4 查詢某一層的全部節點
查詢全部第三層的節點
SELECT descendant FROM CategoryTree WHERE ancestor=0 AND distance=3
這個就不詳細說了,很是簡單。
4.5 插入
插入和移動就不是那麼方便了,當一個節點插入到某個父節點下方時,它將具備與父節點類似的路徑,而後再加上一個自身鏈接便可。
因此插入操做須要兩條語句,第一條複製父節點的全部記錄,並把這些記錄的distance加一,由於子節點到每一個上級節點的距離都比它的父節點多一。固然descendant也要改爲本身的。
例如把id爲10的節點插入到id爲5的節點下方(這裏用了Mysql的方言)
INSERT INTO CategoryTree(ancestor,descendant,distance) (SELECT ancestor,10,distance+1 FROM CategoryTree WHERE descendant=5)
而後就是加入自身鏈接的記錄。
INSERT INTO CategoryTree(ancestor,descendant,distance) VALUES(10,10,0)
4.6 移動
節點的移動沒有很好的解決方法,由於新位置所在的深度、路徑均可能不同,這就致使移動操做不是僅靠UPDATE語句能完成的,這裏選擇刪除+插入實現移動。
另外,在有子樹的狀況下,上級節點的移動還將致使下級節點的路徑改變,因此移動上級節點以後還須要修復下級節點的記錄,這就須要遞歸全部下級節點。
刪除id=5節點的全部記錄
DELETE FROM CategoryTree WHERE descendant=5
而後配合上面一節的插入操做實現移動。具體的實現直接上代碼吧。