一種理想的在關係資料庫中儲存樹型結構資料的方法
那麼理想中的樹型結構應具備哪些特點呢?資料儲存冗餘小、直觀性強;方便返回整個樹型結構資料;可以很輕鬆的返回某一子樹(方便分層載入);快整獲以某節點的祖譜路徑;插入、刪除、移動節點效率高等等。帶著這些需求我查找了很多資料,發現了一種理想的樹型結構資料儲存及操作演算法,改進的前序遍歷樹模型(The Nested Set Model)。
一、資料
在本文中,舉一個線上食品店樹形圖的例子。這個食品店通過類別、顏色和品種來組織食品。樹形圖如下:
二、鄰接列表模型(The Adjacency List Model)
在這種模型下,上述資料在關係資料庫的表結構資料通常如下圖所示:
由於該模型比較簡單,在此不再詳細介紹其演算法,下面列出它的一些不足:
在大多數程式語言中,他執行很慢,效率很差。這主要是“遞迴”造成的。我們每次查詢節點都要訪問資料庫。每次資料庫查詢都要花費一些時間,這讓函式處理龐大的樹時會十分慢。造成這個函式不是太快的第二個原因可能是你使用的語言。不像Lisp這類語言,大多數語言不是針對遞迴函式設計的。對於每個節點造成這個函式不是太快的第二個原因可能是你使用的語言。不像Lisp這類語言,大多數語言不是針對遞迴函式設計的。對於每個節點,函式都要呼叫他自己,產生新的例項。這樣,對於一個4層的樹,你可能同時要執行4個函式副本。對於每個函式都要佔用一塊記憶體並且需要一定的時間初始化,這樣處理大樹時遞迴就很慢了。
三、改進的前序遍歷樹模型(The Nested Set Model)
原理:
我們先把樹按照水平方式擺開。從根節點開始(“Food”),然後他的左邊寫上1。然後按照樹的順序(從上到下)給“Fruit”的左邊寫上2。這樣,你沿著樹的邊界走啊走(這就是“遍歷”),然後同時在每個節點的左邊和右邊寫上數字。最後,我們回到了根節點“Food”在右邊寫上18。下面是標上了數字的樹,同時把遍歷的順序用箭頭標出來了。
我們稱這些數字為左值和右值(如,“Food”的左值是1,右值是18)。正如你所見,這些數字按時了每個節點之間的關係。因為“Red”有3和6兩個值,所以,它是有擁有1-18值的“Food”節點的後續。同樣的,我們可以推斷所有左值大於2並且右值小於11的節點,都是有2-11的“Food”節點的後續。這樣,樹的結構就通過左值和右值儲存下來了。這種數遍整棵樹算節點的方法叫做“改進前序遍歷樹”演算法。
表結構設計:
常用的操作:
下面列出一些常用操作的SQL語句
返回完整的樹(Retrieving a Full Tree) SELECT node.nameFROM nested_category node, nested_category parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND parent.name ='electronics'ORDERBY node.lft
返回某結點的子樹(Find the Immediate Subordinates of a Node) SELECT V.*FROM (SELECT node.name,
(COUNT(parent.name) - (AVG(sub_tree.depth) +1)) depth
FROM nested_category node,
nested_category parent,
nested_category sub_parent,
(SELECT V.*FROM (SELECT node.name, (COUNT(parent.name) -1) depth
FROM nested_category node, nested_category parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.name ='portable electronics'GROUPBY node.name) V,
nested_category T
WHERE V.name = T.name
ORDERBY T.lft) sub_tree
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
AND sub_parent.name = sub_tree.name
GROUPBY node.name) V,
nested_category T
WHERE V.name = T.name
and V.depth <=1and V.depth >0ORDERBY T.Lft
返回某結點的祖譜路徑(Retrieving a Single Path) SELECT parent.name
FROM nested_category node, nested_category parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.name ='flash'ORDERBY node.lft
返回所有節點的深度(Finding the Depth of the Nodes) SELECT V.*FROM (SELECT node.name, (COUNT(parent.name) -1) depth
FROM nested_category node, nested_category parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUPBY node.name) V,
nested_category T
WHERE V.name = T.name
ORDERBY T.Lft
返回子樹的深度(Depth of a Sub-Tree) SELECT V.*FROM (SELECT node.name,
(COUNT(parent.name) - (AVG(sub_tree.depth) +1)) depth
FROM nested_category node,
nested_category parent,
nested_category sub_parent,
(SELECT V.*FROM (SELECT node.name, (COUNT(parent.name) -1) depth
FROM nested_category node, nested_category parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.name ='portable electronics'GROUPBY node.name) V,
nested_category T
WHERE V.name = T.name
ORDERBY T.lft) sub_tree
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
AND sub_parent.name = sub_tree.name
GROUPBY node.name) V,
nested_category T
WHERE V.name = T.name
ORDERBY T.Lft
返回所有的葉子節點(Finding all the Leaf Nodes) SELECT name FROM nested_category WHERE rgt = lft +1
插入節點(Adding New Nodes) LOCK TABLE nested_category WRITE;
SELECT@myRight := rgt FROM nested_category WHERE name ='TELEVISIONS';
UPDATE nested_category SET rgt = rgt +2WHERE rgt >@myRight;
UPDATE nested_category SET lft = lft +2WHERE lft >@myRight;
INSERTINTO nested_category
(name, lft, rgt)
VALUES
('GAME CONSOLES', @myRight+1, @myRight+2);
UNLOCK TABLES;
刪除節點(Deleting Nodes) LOCK TABLE nested_category WRITE;
SELECT@myLeft := lft, @myRight := rgt, @myWidth := rgt - lft +1FROM nested_category
WHERE name ='GAME CONSOLES';
DELETEFROM nested_category WHERE lft BETWEEN@myLeftAND@myRight;
UPDATE nested_category SET rgt = rgt -@myWidthWHERE rgt >@myRight;
UPDATE nested_category SET lft = lft -@myWidthWHERE lft >@myRight;
UNLOCK TABLES;