同樣介紹嵌套集合模型的文章:http://www.nirvanastudio.org/php/hierarchical-data-database.html
本文作者:Yimin http://liyimin.net/blog
加深閱讀Celko Joe - Trees and Hierarchies in SQL for Smarties
MYSQL中分層資料的管理
By Mike Hillyer
引言
大多數使用者都曾在資料庫中處理過分層資料(hierarchical data),認為分層資料的管理不是關聯式資料庫的目的。之所以這麼認為,是因為關聯式資料庫中的表沒有層次關係,只是簡單的平面化的列表;而分層資料具有父 -子關係,顯然關聯式資料庫中的表不能自然地表現出其分層的特性。
我們認為,分層資料是每項只有一個父項和零個或多個子項(根項除外,根項沒有父項)的資料集合。分層資料存在於許多基於資料庫的應用程式中,包括論壇和郵件清單中的分類、商業組織圖表、內容管理系統的分類、產品分類。我們打算使用下面一個虛構的電子商店的產品分類:
當我們為樹狀的結構編號時,我們從左至右,一次一層,為節點賦右值前先從左至右遍曆其
子節點給其子節點賦左右值。這種方法被稱作改進的先序遍曆演算法。
檢索整樹 我們可以通過自串連把父節點串連到子節點上來檢索整樹,是因為子節點的lft值總是在其
父節點的lft值和rgt值之間:
SELECT node.name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND parent.name = 'ELECTRONICS'
ORDER BY node.lft;
+---------------------+
| name |
+---------------------+
| ELECTRONICS |
| TELEVISIONS |
| TUBE |
| LCD |
| PLASMA |
| PORTABLE ELECTRONICS|
| MP3 PLAYERS |
| FLASH |
| CD PLAYERS |
| 2 WAY RADIOS |
+---------------------+
不像先前鄰接表模型的例子,這個查詢語句不管樹的層次有多深都能很好的工作。在BETWEEN的子句中我們沒有去關心node的rgt值,是因為使用node的rgt值得出的父節點總是和使用lft值得出的是相同的。
檢索所有葉子節點 檢索出所有的葉子節點,使用嵌套集合模型的方法比鄰接表模型的LEFT JOIN方法簡單多了。如果你仔細得看了nested_category表,你可能已經注意到葉子節點的左右值是連續的。要檢索出葉子節點,我們只要尋找滿足rgt=lft+1的節點:
SELECT name
FROM nested_category
WHERE rgt = lft + 1;
+---------------+
| name |
+---------------+
| TUBE |
| LCD |
| PLASMA |
| FLASH |
| CD PLAYERS |
| 2 WAY RADIOS |
+---------------+
檢索單一路徑 在嵌套集合模型中,我們可以不用多個自串連就可以檢索出單一路徑:
SELECT parent.name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.name = 'FLASH'
ORDER BY node.lft;
+------------------------+
| name |
+------------------------+
| ELECTRONICS |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS |
| FLASH |
+------------------------+
檢索節點的深度 我們已經知道怎樣去呈現一棵整樹,但是為了更好的標識出節點在樹中所處層次,我們怎樣才能檢索出節點在樹中的深度呢。我們可以在先前的查詢語句上增加COUNT函數和GROUP BY子句來實現:
SELECT node.name, (COUNT(parent.name) 1)
AS depth
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;
+----------------------+--------+
| name | depth |
+----------------------+---+
| ELECTRONICS | 0 |
| TELEVISIONS | 1 |
| TUBE | 2 |
| LCD | 2 |
| PLASMA | 2 |
| PORTABLE ELECTRONICS | 1 |
| MP3 PLAYERS | 2 |
| FLASH | 3 |
| CD PLAYERS | 2 |
| 2 WAY RADIOS | 2 |
+----------------------+----+
我們可以根據depth值來縮排分類名字,使用CONCAT和REPEAT字串函數1:
SELECT CONCAT( REPEAT(' ', COUNT(parent.name) 1),
node.name) AS name
FROM nested_category AS node,
nested_category AS parent
1 [譯註] 縮排在phpMyAdmin下顯示會有出入,建議在MySQL命令列下執行查詢語句。
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;
+---------------------------+
| name |
+---------------------------+
| ELECTRONICS |
| TELEVISIONS |
| TUBE |
| LCD |
| PLASMA |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS |
| FLASH |
| CD PLAYERS |
| 2 WAY RADIOS |
+---------------------------+
當然,在用戶端應用程式中你可能會用depth值來直接展示資料的層次。Web開發人員會遍曆該樹,隨著depth值的增加和減少來添加<li></li>和<ul></ul>標籤。
檢索子樹的深度當我們需要子樹的深度資訊時,我們不能限制自串連中的node或parent,因為這麼做會打亂資料集的順序。因此,我們添加了第三個自串連作為子查詢,來得出子樹新起點的深度值:
SELECT node.name, (COUNT(parent.name) (
sub_tree.depth + 1)) AS depth
FROM nested_category AS node,
nested_category AS parent,
nested_category AS sub_parent,
(
SELECT node.name, (COUNT(parent.name) 1)
AS depth
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.name = 'PORTABLE ELECTRONICS'
GROUP BY node.name
ORDER BY node.lft
)AS 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
GROUP BY node.name
ORDER BY node.lft;
+----------------------+-------+
| name | depth |
+----------------------+------+
| PORTABLE ELECTRONICS | 0 |
| MP3 PLAYERS | 1 |
| FLASH | 2 |
| CD PLAYERS | 1 |
| 2 WAY RADIOS | 1 |
+----------------------+---+
這個查詢語句可以檢索出任一節點子樹的深度值,包括根節點。這裡的深度值跟你指定的節點有關。
檢索節點的直接子節點 可以想象一下,你在零售網站上呈現電子產品的分類。當使用者點擊分類後,你將要呈現該分類下的產品,同時也需列出該分類下的直接子分類,而不是該分類下的全 部分類。為此,我們只呈現該節點及其直接子節點,不再呈現更深層次的節點。例如,當呈現PORTABLEELECTRONICS分類時,我們同時只呈現 MP3 PLAYERS、CD PLAYERS和2 WAY RADIOS分類,而不呈現FLASH分類。要實現它非常的簡單,在先前的查詢語句上添加HAVING子句:
SELECT node.name, (COUNT(parent.name) (
sub_tree.depth + 1)) AS depth
FROM nested_category AS node,
nested_category AS parent,
nested_category AS sub_parent,
(
SELECT node.name, (COUNT(parent.name) 1)
AS depth
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.name = 'PORTABLE ELECTRONICS'
GROUP BY node.name
ORDER BY node.lft
)AS 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
GROUP BY node.name
HAVING depth <= 1
ORDER BY node.lft;
+----------------------+-------+
| name | depth |
+----------------------+-------+
| PORTABLE ELECTRONICS | 0 |
| MP3 PLAYERS | 1 |
| CD PLAYERS | 1 |
| 2 WAY RADIOS | 1 |
+----------------------+----+
如果你不希望呈現父節點,你可以更改HAVING depth <= 1為HAVING depth = 1。