ClosureTable實現高效無限分類

ClosureTable直譯過來叫閉包表?不過不重要,ClosureTable以一張表存儲節點之間的關係、其中包含了任何兩個有關係(上下級)節點的關聯信息

ClosureTable演示
ClosureTable實現高效無限分類

定義關係表CategoryTree,其包含3個字段:

ancestor 祖先:上級節點的id
descendant 子代:下級節點的id
distance 距離:子代到祖先中間隔了幾級

這三個字段的組合是唯一的,因爲在樹中,一條路徑可以標識一個節點,所以可以直接把它們的組合作爲主鍵。以圖爲例,節點6到它上一級的節點(節點4)距離爲1在數據庫中存儲爲ancestor=4,descendant=6,distance=1,到上兩級的節點(節點1)距離爲2,於是有ancestor=1,descendant=6,distance=2,到根節點的距離爲3也是如此,最後還要包含一個到自己的連接,當然距離就是0了。

這樣一來,不盡表中包含了所有的路徑信息,還在帶上了路徑中每個節點的位置(距離),對於樹結構常用的查詢都能夠很方便的處理。下面看看如何用用它來實現我們的需求。
4.1 子節點查詢

查詢id爲5的節點的直屬子節點:

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的節點之間的所有節點,按照層級由小到大排序

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

然後配合上面一節的插入操作實現移動。具體的實現直接上代碼吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章