mysql tree_MySQL树形遍历(二)
轉(zhuǎn)載自:
http://blog.csdn.net/dreamer0924/article/details/7580278
英文原文:
http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
預(yù)排序遍歷樹算法:modified preorder tree traversal algorithm
這個算法有如下幾個數(shù)據(jù)結(jié)構(gòu)
1 lft 代表左 left
2 rgt 代表右 right
3 lvl 代表所在的層次 level
下面這個圖是一個典型的結(jié)構(gòu)
我們先看一些使用方法
1???? 查看整個樹(A)有多少節(jié)點(包含自己)
直接看根節(jié)點就行了 (right-left+1)/2 = (20-1+1)/2 = 10
這個數(shù)有10個節(jié)點
2???? 查看從節(jié)點A到E的路徑
select * from tree where lft between 1 and 6 and rgt between 7 and 20 order by lft
得到的結(jié)果是A,B,D,E 這4個節(jié)點的數(shù)據(jù),且按照訪問路徑的順序
如果2個節(jié)點之間不是上下級的關(guān)系,則查詢沒有結(jié)果
反向也是一樣的,可以拿到底部一個節(jié)點,到上級節(jié)點的路徑
select * from tree where lft between 1 and 6 and rgt between 7 and 20 order by lft desc
唯一的區(qū)別就是排序是反向的就行了。
3??? ?得到某個節(jié)點下面的所有節(jié)點,且按照樹狀結(jié)構(gòu)返回
我們用B做例子
select * from tree where lft>2 and right<11 order by lft
拿到的結(jié)果是 C,D,E,F,而且順序也是正確的。
4??? ?拿到所有下2級的子節(jié)點
我們A做例子,這次加上了lvl的參數(shù),因為A的level是1,所以我們查詢level不大于3的。
select * from tree where lft>2 and right<11 and lvl<=3 order by lft
下面看我們新增加一個節(jié)點的方法。
我們在根節(jié)點的下面,G節(jié)點的右側(cè)增加一個X節(jié)點
我們要做的工作就是
1 G節(jié)點的右參數(shù)為13
2 變更所有的受影響的節(jié)點,給新節(jié)點騰出空位子
所有左節(jié)點比G節(jié)點大的,都增加2
update tree set lft=lft+2 where lft>12
所有右節(jié)點比G節(jié)點大的,都增加2
update tree set rgt=rgt+2 where rgt>13
3 新節(jié)點放在空位子上,lft=14,rgt=15
這樣就完成了一個新節(jié)點的增加操作。
另一篇詳細解釋:
譯文:Yimin
引言
大多數(shù)用戶都曾在數(shù)據(jù)庫中處理過分層數(shù)據(jù)(hierarchical data),認為分層數(shù)據(jù)的管理不是關(guān)系數(shù)據(jù)庫的目的。之所以這么認為,是因為關(guān)系數(shù)據(jù)庫中的表沒有層次關(guān)系,只是簡單的平面化的列表;而分層數(shù)據(jù)具有父-子關(guān)系,顯然關(guān)系數(shù)據(jù)庫中的表不能自然地表現(xiàn)出其分層的特性。
我們認為,分層數(shù)據(jù)是每項只有一個父項和零個或多個子項(根項除外,根項沒有父項)的數(shù)據(jù)集合。分層數(shù)據(jù)存在于許多基于數(shù)據(jù)庫的應(yīng)用程序中,包括論壇和郵件列表中的分類、商業(yè)組織圖表、內(nèi)容管理系統(tǒng)的分類、產(chǎn)品分類。我們打算使用下面一個虛構(gòu)的電子商店的產(chǎn)品分類:
這些分類層次與上面提到的一些例子中的分類層次是相類似的。在本文中我們將從傳統(tǒng)的鄰接表(adjacency list)模型出發(fā),闡述2種在MySQL中處理分層數(shù)據(jù)的模型。
鄰接表模型
上述例子的分類數(shù)據(jù)將被存儲在下面的數(shù)據(jù)表中(我給出了全部的數(shù)據(jù)表創(chuàng)建、數(shù)據(jù)插入的代碼,你可以跟著做):
CREATE TABLE category(
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
parent INT DEFAULT NULL);
INSERT INTO category
VALUES(1,'ELECTRONICS',NULL),(2,'TELEVISIONS',1),(3,'TUBE',2),
(4,'LCD',2),(5,'PLASMA',2),(6,'PORTABLE ELECTRONICS',1),
(7,'MP3 PLAYERS',6),(8,'FLASH',7),
(9,'CD PLAYERS',6),(10,'2 WAY RADIOS',6);
SELECT * FROM category ORDER BY category_id;
+-------------+----------------------+--------+
| category_id | name | parent |
+-------------+----------------------+--------+
| 1 | ELECTRONICS | NULL |
| 2 | TELEVISIONS | 1 |
| 3 | TUBE | 2 |
| 4 | LCD | 2 |
| 5 | PLASMA | 2 |
| 6 | PORTABLE ELECTRONICS | 1 |
| 7 | MP3 PLAYERS | 6 |
| 8 | FLASH | 7 |
| 9 | CD PLAYERS | 6 |
| 10 | 2 WAY RADIOS | 6 |
+-------------+----------------------+--------+
10 rows in set (0.00 sec)
在鄰接表模型中,數(shù)據(jù)表中的每項包含了指向其父項的指示器。在此例中,最上層項的父項為空值(NULL)。鄰接表模型的優(yōu)勢在于它很簡單,可以很容易地看出FLASH是MP3 PLAYERS的子項,哪個是portable electronics的子項,哪個是electronics的子項。雖然,在客戶端編碼中鄰接表模型處理起來也相當(dāng)?shù)暮唵?#xff0c;但是如果是純SQL編碼的話,該模型會有很多問題。
檢索整樹
通常在處理分層數(shù)據(jù)時首要的任務(wù)是,以某種縮進形式來呈現(xiàn)一棵完整的樹。為此,在純SQL編碼中通常的做法是使用自連接(self-join):
SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';
+-------------+----------------------+--------------+-------+
| lev1 | lev2 | lev3 | lev4 |
+-------------+----------------------+--------------+-------+
| ELECTRONICS | TELEVISIONS | TUBE | NULL |
| ELECTRONICS | TELEVISIONS | LCD | NULL |
| ELECTRONICS | TELEVISIONS | PLASMA | NULL |
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS | FLASH |
| ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS | NULL |
| ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL |
+-------------+----------------------+--------------+-------+
6 rows in set (0.00 sec)
檢索所有葉子節(jié)點
我們可以用左連接(LEFT JOIN)來檢索出樹中所有葉子節(jié)點(沒有孩子節(jié)點的節(jié)點):
SELECT t1.name FROM
category AS t1 LEFT JOIN category as t2
ON t1.category_id = t2.parent
WHERE t2.category_id IS NULL;
+--------------+
| name |
+--------------+
| TUBE |
| LCD |
| PLASMA |
| FLASH |
| CD PLAYERS |
| 2 WAY RADIOS |
+--------------+
檢索單一路徑
通過自連接,我們也可以檢索出單一路徑:
SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS' AND t4.name = 'FLASH';
+-------------+----------------------+-------------+-------+
| lev1 | lev2 | lev3 | lev4 |
+-------------+----------------------+-------------+-------+
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS | FLASH |
+-------------+----------------------+-------------+-------+
1 row in set (0.01 sec)
這種方法的主要局限是你需要為每層數(shù)據(jù)添加一個自連接,隨著層次的增加,自連接變得越來越復(fù)雜,檢索的性能自然而然的也就下降了。
鄰接表模型的局限性
用純SQL編碼實現(xiàn)鄰接表模型有一定的難度。在我們檢索某分類的路徑之前,我們需要知道該分類所在的層次。另外,我們在刪除節(jié)點的時候要特別小心,因為潛在的可能會孤立一棵子樹(當(dāng)刪除portable electronics分類時,所有他的子分類都成了孤兒)。部分局限性可以通過使用客戶端代碼或者存儲過程來解決,我們可以從樹的底部開始向上迭代來獲得一顆樹或者單一路徑,我們也可以在刪除節(jié)點的時候使其子節(jié)點指向一個新的父節(jié)點,來防止孤立子樹的產(chǎn)生。
嵌套集合(Nested Set)模型
我想在這篇文章中重點闡述一種不同的方法,俗稱為嵌套集合模型。在嵌套集合模型中,我們將以一種新的方式來看待我們的分層數(shù)據(jù),不再是線與點了,而是嵌套容器。我試著以嵌套容器的方式畫出了electronics分類圖:
從上圖可以看出我們依舊保持了數(shù)據(jù)的層次,父分類包圍了其子分類。在數(shù)據(jù)表中,我們通過使用表示節(jié)點的嵌套關(guān)系的左值(left value)和右值(right value)來表現(xiàn)嵌套集合模型中數(shù)據(jù)的分層特性:
CREATE TABLE nested_category (
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
lft INT NOT NULL,
rgt INT NOT NULL
);
INSERT INTO nested_category
VALUES(1,'ELECTRONICS',1,20),(2,'TELEVISIONS',2,9),(3,'TUBE',3,4),
(4,'LCD',5,6),(5,'PLASMA',7,8),(6,'PORTABLE ELECTRONICS',10,19),
(7,'MP3 PLAYERS',11,14),(8,'FLASH',12,13),
(9,'CD PLAYERS',15,16),(10,'2 WAY RADIOS',17,18);
SELECT * FROM nested_category ORDER BY category_id;
+-------------+----------------------+-----+-----+
| category_id | name | lft | rgt |
+-------------+----------------------+-----+-----+
| 1 | ELECTRONICS | 1 | 20 |
| 2 | TELEVISIONS | 2 | 9 |
| 3 | TUBE | 3 | 4 |
| 4 | LCD | 5 | 6 |
| 5 | PLASMA | 7 | 8 |
| 6 | PORTABLE ELECTRONICS | 10 | 19 |
| 7 | MP3 PLAYERS | 11 | 14 |
| 8 | FLASH | 12 | 13 |
| 9 | CD PLAYERS | 15 | 16 |
| 10 | 2 WAY RADIOS | 17 | 18 |
+-------------+----------------------+-----+-----+
我們使用了lft和rgt來代替left和right,是因為在MySQL中l(wèi)eft和right是保留字。http://dev.mysql.com/doc/mysql/en/reserved-words.html,有一份詳細的MySQL保留字清單。
那么,我們怎樣決定左值和右值呢?我們從外層節(jié)點的最左側(cè)開始,從左到右編號:
這樣的編號方式也同樣適用于典型的樹狀結(jié)構(gòu):
當(dāng)我們?yōu)闃錉畹慕Y(jié)構(gòu)編號時,我們從左到右,一次一層,為節(jié)點賦右值前先從左到右遍歷其子節(jié)點給其子節(jié)點賦左右值。這種方法被稱作改進的先序遍歷算法。
檢索整樹
我們可以通過自連接把父節(jié)點連接到子節(jié)點上來檢索整樹,是因為子節(jié)點的lft值總是在其父節(jié)點的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的子句中我們沒有去關(guān)心node的rgt值,是因為使用node的rgt值得出的父節(jié)點總是和使用lft值得出的是相同的。
檢索所有葉子節(jié)點
檢索出所有的葉子節(jié)點,使用嵌套集合模型的方法比鄰接表模型的LEFT JOIN方法簡單多了。如果你仔細得看了nested_category表,你可能已經(jīng)注意到葉子節(jié)點的左右值是連續(xù)的。要檢索出葉子節(jié)點,我們只要查找滿足rgt=lft+1的節(jié)點:
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 parent.lft;
+----------------------+
| name |
+----------------------+
| ELECTRONICS |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS |
| FLASH |
+----------------------+
檢索節(jié)點的深度
我們已經(jīng)知道怎樣去呈現(xiàn)一棵整樹,但是為了更好的標(biāo)識出節(jié)點在樹中所處層次,我們怎樣才能檢索出節(jié)點在樹中的深度呢?我們可以在先前的查詢語句上增加COUNT函數(shù)和GROUP BY子句來實現(xiàn):
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 |
+----------------------+-------+
我們可以根據(jù)depth值來縮進分類名字,使用CONCAT和REPEAT字符串函數(shù):
SELECT CONCAT( REPEAT(' ', COUNT(parent.name) - 1), node.name) AS name
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 |
+-----------------------+
| ELECTRONICS |
| TELEVISIONS |
| TUBE |
| LCD |
| PLASMA |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS |
| FLASH |
| CD PLAYERS |
| 2 WAY RADIOS |
+-----------------------+
當(dāng)然,在客戶端應(yīng)用程序中你可能會用depth值來直接展示數(shù)據(jù)的層次。Web開發(fā)者會遍歷該樹,隨著depth值的增加和減少來添加
和
標(biāo)簽。
檢索子樹的深度
當(dāng)我們需要子樹的深度信息時,我們不能限制自連接中的node或parent,因為這么做會打亂數(shù)據(jù)集的順序。因此,我們添加了第三個自連接作為子查詢,來得出子樹新起點的深度值:
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 |
+----------------------+-------+
這個查詢語句可以檢索出任一節(jié)點子樹的深度值,包括根節(jié)點。這里的深度值跟你指定的節(jié)點有關(guān)。
檢索節(jié)點的直接子節(jié)點
可以想象一下,你在零售網(wǎng)站上呈現(xiàn)電子產(chǎn)品的分類。當(dāng)用戶點擊分類后,你將要呈現(xiàn)該分類下的產(chǎn)品,同時也需列出該分類下的直接子分類,而不是該分類下的全部分類。為此,我們只呈現(xiàn)該節(jié)點及其直接子節(jié)點,不再呈現(xiàn)更深層次的節(jié)點。例如,當(dāng)呈現(xiàn)PORTABLEELECTRONICS分類時,我們同時只呈現(xiàn)MP3 PLAYERS、CD PLAYERS和2 WAY RADIOS分類,而不呈現(xiàn)FLASH分類。
要實現(xiàn)它非常的簡單,在先前的查詢語句上添加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 |
+----------------------+-------+
如果你不希望呈現(xiàn)父節(jié)點,你可以更改HAVING depth <= 1為HAVING depth = 1。
嵌套集合模型中集合函數(shù)的應(yīng)用
讓我們添加一個產(chǎn)品表,我們可以使用它來示例集合函數(shù)的應(yīng)用:
CREATE TABLE product(
product_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(40),
category_id INT NOT NULL
);
INSERT INTO product(name, category_id) VALUES('20" TV',3),('36" TV',3),
('Super-LCD 42"',4),('Ultra-Plasma 62"',5),('Value Plasma 38"',5),
('Power-MP3 5gb',7),('Super-Player 1gb',8),('Porta CD',9),('CD To go!',9),
('Family Talk 360',10);
SELECT * FROM product;
+------------+-------------------+-------------+
| product_id | name | category_id |
+------------+-------------------+-------------+
| 1 | 20" TV | 3 |
| 2 | 36" TV | 3 |
| 3 | Super-LCD 42" | 4 |
| 4 | Ultra-Plasma 62" | 5 |
| 5 | Value Plasma 38" | 5 |
| 6 | Power-MP3 128mb | 7 |
| 7 | Super-Shuffle 1gb | 8 |
| 8 | Porta CD | 9 |
| 9 | CD To go! | 9 |
| 10 | Family Talk 360 | 10 |
+------------+-------------------+-------------+
現(xiàn)在,讓我們寫一個查詢語句,在檢索分類樹的同時,計算出各分類下的產(chǎn)品數(shù)量:
SELECT parent.name, COUNT(product.name)
FROM nested_category AS node ,
nested_category AS parent,
product
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.category_id = product.category_id
GROUP BY parent.name
ORDER BY node.lft;
+----------------------+---------------------+
| name | COUNT(product.name) |
+----------------------+---------------------+
| ELECTRONICS | 10 |
| TELEVISIONS | 5 |
| TUBE | 2 |
| LCD | 1 |
| PLASMA | 2 |
| PORTABLE ELECTRONICS | 5 |
| MP3 PLAYERS | 2 |
| FLASH | 1 |
| CD PLAYERS | 2 |
| 2 WAY RADIOS | 1 |
+----------------------+---------------------+
這條查詢語句在檢索整樹的查詢語句上增加了COUNT和GROUP BY子句,同時在WHERE子句中引用了product表和一個自連接。
新增節(jié)點
到現(xiàn)在,我們已經(jīng)知道了如何去查詢我們的樹,是時候去關(guān)注一下如何增加一個新節(jié)點來更新我們的樹了。讓我們再一次觀察一下我們的嵌套集合圖:
當(dāng)我們想要在TELEVISIONS和PORTABLE ELECTRONICS節(jié)點之間新增一個節(jié)點,新節(jié)點的lft和rgt 的 值為10和11,所有該節(jié)點的右邊節(jié)點的lft和rgt值都將加2,之后我們再添加新節(jié)點并賦相應(yīng)的lft和rgt值。在MySQL 5中可以使用存儲過程來完成,我假設(shè)當(dāng)前大部分讀者使用的是MySQL 4.1版本,因為這是最新的穩(wěn)定版本。所以,我使用了鎖表(LOCK TABLES)語句來隔離查詢:
LOCK TABLE nested_category WRITE;
SELECT @myRight := rgt FROM nested_category
WHERE name = 'TELEVISIONS';
UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft + 2 WHERE lft > @myRight;
INSERT INTO nested_category(name, lft, rgt) VALUES('GAME CONSOLES', @myRight + 1, @myRight + 2);
UNLOCK TABLES;
我們可以檢驗一下新節(jié)點插入的正確性:
SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
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 |
+-----------------------+
| ELECTRONICS |
| TELEVISIONS |
| TUBE |
| LCD |
| PLASMA |
| GAME CONSOLES |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS |
| FLASH |
| CD PLAYERS |
| 2 WAY RADIOS |
+-----------------------+
如果我們想要在葉子節(jié)點下增加節(jié)點,我們得稍微修改一下查詢語句。讓我們在2 WAYRADIOS葉子節(jié)點下添加FRS節(jié)點吧:
LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft FROM nested_category
WHERE name = '2 WAY RADIOS';
UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myLeft;
UPDATE nested_category SET lft = lft + 2 WHERE lft > @myLeft;
INSERT INTO nested_category(name, lft, rgt) VALUES('FRS', @myLeft + 1, @myLeft + 2);
UNLOCK TABLES;
在這個例子中,我們擴大了新產(chǎn)生的父節(jié)點(2 WAY RADIOS節(jié)點)的右值及其所有它的右邊節(jié)點的左右值,之后置新增節(jié)點于新父節(jié)點之下。正如你所看到的,我們新增的節(jié)點已經(jīng)完全融入了嵌套集合中:
SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
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 |
+-----------------------+
| ELECTRONICS |
| TELEVISIONS |
| TUBE |
| LCD |
| PLASMA |
| GAME CONSOLES |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS |
| FLASH |
| CD PLAYERS |
| 2 WAY RADIOS |
| FRS |
+-----------------------+
刪除節(jié)點
最后還有個基礎(chǔ)任務(wù),刪除節(jié)點。刪除節(jié)點的處理過程跟節(jié)點在分層數(shù)據(jù)中所處的位置有關(guān),刪除一個葉子節(jié)點比刪除一個子節(jié)點要簡單得多,因為刪除子節(jié)點的時候,我們需要去處理孤立節(jié)點。
刪除一個葉子節(jié)點的過程正好是新增一個葉子節(jié)點的逆過程,我們在刪除節(jié)點的同時該節(jié)點右邊所有節(jié)點的左右值和該父節(jié)點的右值都會減去該節(jié)點的寬度值:
LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM nested_category
WHERE name = 'GAME CONSOLES';
DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;
UNLOCK TABLES;
我們再一次檢驗一下節(jié)點已經(jīng)成功刪除,而且沒有打亂數(shù)據(jù)的層次:
SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
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 |
+-----------------------+
| ELECTRONICS |
| TELEVISIONS |
| TUBE |
| LCD |
| PLASMA |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS |
| FLASH |
| CD PLAYERS |
| 2 WAY RADIOS |
| FRS |
+-----------------------+
這個方法可以完美地刪除節(jié)點及其子節(jié)點:
LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM nested_category
WHERE name = 'MP3 PLAYERS';
DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;
UNLOCK TABLES;
再次驗證我們已經(jīng)成功的刪除了一棵子樹:
SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
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 |
+-----------------------+
| ELECTRONICS |
| TELEVISIONS |
| TUBE |
| LCD |
| PLASMA |
| PORTABLE ELECTRONICS |
| CD PLAYERS |
| 2 WAY RADIOS |
| FRS |
+-----------------------+
有時,我們只刪除該節(jié)點,而不刪除該節(jié)點的子節(jié)點。在一些情況下,你希望改變其名字為占位符,直到替代名字的出現(xiàn),比如你開除了一個主管(需要更換主管)。在另外一些情況下,你希望子節(jié)點掛到該刪除節(jié)點的父節(jié)點下:
LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM nested_category
WHERE name = 'PORTABLE ELECTRONICS';
DELETE FROM nested_category WHERE lft = @myLeft;
UPDATE nested_category SET rgt = rgt - 1, lft = lft - 1 WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - 2 WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - 2 WHERE lft > @myRight;
UNLOCK TABLES;
在這個例子中,我們對該節(jié)點所有右邊節(jié)點的左右值都減去了2(因為不考慮其子節(jié)點,該節(jié)點的寬度為2),對該節(jié)點的子節(jié)點的左右值都減去了1(彌補由于失去父節(jié)點的左值造成的裂縫)。我們再一次確認,那些節(jié)點是否都晉升了:
SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
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 |
+---------------+
| ELECTRONICS |
| TELEVISIONS |
| TUBE |
| LCD |
| PLASMA |
| CD PLAYERS |
| 2 WAY RADIOS |
| FRS |
+---------------+
有時,當(dāng)刪除節(jié)點的時候,把該節(jié)點的一個子節(jié)點掛載到該節(jié)點的父節(jié)點下,而其他節(jié)點掛到該節(jié)點父節(jié)點的兄弟節(jié)點下,考慮到篇幅這種情況不在這里解說了。
最后的思考
我希望這篇文章對你有所幫助,SQL中的嵌套集合的觀念大約有十年的歷史了,在網(wǎng)上和一些書中都能找到許多相關(guān)信息。在我看來,講述分層數(shù)據(jù)的管理最全面的,是來自一本名叫《Joe Celko's Trees and Hierarchies in SQL for Smarties》的書,此書的作者是在高級SQL領(lǐng)域倍受尊敬的Joe Celko。Joe Celko被認為是嵌套集合模型的創(chuàng)造者,更是該領(lǐng)域內(nèi)的多產(chǎn)作家。我把Celko的書當(dāng)作無價之寶,并極力地推薦它。在這本書中涵蓋了在此文中沒有提及的一些高級話題,也提到了其他一些關(guān)于鄰接表和嵌套集合模型下管理分層數(shù)據(jù)的方法。
在隨后的參考書目章節(jié)中,我列出了一些網(wǎng)絡(luò)資源,也許對你研究分層數(shù)據(jù)的管理會有所幫助,其中包括一些PHP相關(guān)的資源(處理嵌套集合的PHP庫)。如果你還在使用鄰接表模型,你該去試試嵌套集合模型了,在Storing Hierarchical Data in a Database?文中下方列出的一些資源鏈接中能找到一些樣例代碼,可以去試驗一下。
總結(jié)
以上是生活随笔為你收集整理的mysql tree_MySQL树形遍历(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python pandas合并多个exc
- 下一篇: mysql varchar(20)_My