MySQL 左右值無限分類 預排序遍歷樹算法

左右值無限分類 預排序遍歷樹算法:modified preorder tree traversal algorithm


這個算法有以下幾個數據結構 php

1 lft 表明左 left html

2 rgt 表明右 right node

3 lvl 表明所在的層次 level mysql

下面這個圖是一個典型的結構  算法

 

咱們先看一些使用方法 sql

1     查看整個樹(A)有多少節點(包含本身) 數據庫

直接看根節點就好了 (right-left+1)/2 = (20-1+1)/2 = 10 編程

這個數有10個節點 網絡

2     查看從節點A到E的路徑 數據結構

select * from tree where lft between 1 and 6 and rgt between 7 and 20 order by lft

獲得的結果是A,B,D,E 這4個節點的數據,且按照訪問路徑的順序

若是2個節點之間不是上下級的關係,則查詢沒有結果

反向也是同樣的,能夠拿到底部一個節點,到上級節點的路徑

select * from tree where lft between 1 and 6 and rgt between 7 and 20 order by lft desc

惟一的區別就是排序是反向的就好了。

3     獲得某個節點下面的全部節點,且按照樹狀結構返回

咱們用B作例子

select * from tree where lft>2 and right<11 order by lft

拿到的結果是 C,D,E,F,並且順序也是正確的。

4     拿到全部下2級的子節點

咱們A作例子,此次加上了lvl的參數,由於A的level是1,因此咱們查詢level不大於3的。

select * from tree where lft>2 and right<11 and lvl<=3 order by lft

下面看咱們新增長一個節點的方法。

咱們在根節點的下面,G節點的右側增長一個X節點

 

咱們要作的工做就是

1 G節點的右參數爲13

2 變動全部的受影響的節點,給新節點騰出空位子

全部左節點比G節點大的,都增長2

update tree set lft=lft+2 where lft>12

全部右節點比G節點大的,都增長2

update tree set rgt=rgt+2 where rgt>13

3 新節點放在空位子上,lft=14,rgt=15

這樣就完成了一個新節點的增長操做。

本文來自編程入門網:http://www.bianceng.cn/Programming/sjjg/201109/30086_2.htm

  另外一篇詳細解釋:

Managing Hierarchical Data in MySQL(推薦)

原文在:http://dev.mysql.com/tech-resources/articles/hierarchical-data.html

 譯文:Yimin

引言

大多數用戶都曾在數據庫中處理過度層數據(hierarchical data),認爲分層數據的管理不是關係數據庫的目的。之因此這麼認爲,是由於關係數據庫中的表沒有層次關係,只是簡單的平面化的列表;而分層數據具備父-子關係,顯然關係數據庫中的表不能天然地表現出其分層的特性。

咱們認爲,分層數據是每項只有一個父項和零個或多個子項(根項除外,根項沒有父項)的數據集合。分層數據存在於許多基於數據庫的應用程序中,包括論壇和郵件列表中的分類、商業組織圖表、內容管理系統的分類、產品分類。咱們打算使用下面一個虛構的電子商店的產品分類:

image

這些分類層次與上面提到的一些例子中的分類層次是相相似的。在本文中咱們將從傳統的鄰接表(adjacency list)模型出發,闡述2種在MySQL中處理分層數據的模型。

鄰接表模型

上述例子的分類數據將被存儲在下面的數據表中(我給出了所有的數據表建立、數據插入的代碼,你能夠跟着作):

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)

在鄰接表模型中,數據表中的每項包含了指向其父項的指示器。在此例中,最上層項的父項爲空值(NULL)。鄰接表模型的優點在於它很簡單,能夠很容易地看出FLASH是MP3 PLAYERS的子項,哪一個是portable electronics的子項,哪一個是electronics的子項。雖然,在客戶端編碼中鄰接表模型處理起來也至關的簡單,可是若是是純SQL編碼的話,該模型會有不少問題。

檢索整樹

一般在處理分層數據時首要的任務是,以某種縮進形式來呈現一棵完整的樹。爲此,在純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)

 

檢索全部葉子節點

咱們能夠用左鏈接(LEFT JOIN)來檢索出樹中全部葉子節點(沒有孩子節點的節點):

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)

這種方法的主要侷限是你須要爲每層數據添加一個自鏈接,隨着層次的增長,自鏈接變得愈來愈複雜,檢索的性能天然而然的也就降低了。

 

鄰接表模型的侷限性

用純SQL編碼實現鄰接表模型有必定的難度。在咱們檢索某分類的路徑以前,咱們須要知道該分類所在的層次。另外,咱們在刪除節點的時候要特別當心,由於潛在的可能會孤立一棵子樹(當刪除portable electronics分類時,全部他的子分類都成了孤兒)。部分侷限性能夠經過使用客戶端代碼或者存儲過程來解決,咱們能夠從樹的底部開始向上迭代來得到一顆樹或者單一路徑,咱們也能夠在刪除節點的時候使其子節點指向一個新的父節點,來防止孤立子樹的產生。

嵌套集合(Nested Set)模型

我想在這篇文章中重點闡述一種不一樣的方法,俗稱爲嵌套集合模型。在嵌套集合模型中,咱們將以一種新的方式來看待咱們的分層數據,再也不是線與點了,而是嵌套容器。我試着以嵌套容器的方式畫出了electronics分類圖:

image

 

從上圖能夠看出咱們依舊保持了數據的層次,父分類包圍了其子分類。在數據表中,咱們經過使用表示節點的嵌套關係的左值(left value)和右值(right value)來表現嵌套集合模型中數據的分層特性:

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 | +-------------+----------------------+-----+-----+

 

咱們使用了lftrgt來代替left和right,是由於在MySQL中left和right是保留字。http://dev.mysql.com/doc/mysql/en/reserved-words.html,有一份詳細的MySQL保留字清單。

那麼,咱們怎樣決定左值和右值呢?咱們從外層節點的最左側開始,從左到右編號:

image

這樣的編號方式也一樣適用於典型的樹狀結構:

image

 

當咱們爲樹狀的結構編號時,咱們從左到右,一次一層,爲節點賦右值前先從左到右遍歷其子節點給其子節點賦左右值。這種方法被稱做改進的先序遍歷算法

 

檢索整樹

咱們能夠經過自鏈接把父節點鏈接到子節點上來檢索整樹,是由於子節點的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 parent.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字符串函數:

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 | +-----------------------+

固然,在客戶端應用程序中你可能會用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 <= 1HAVING depth = 1

嵌套集合模型中集合函數的應用

讓咱們添加一個產品表,咱們可使用它來示例集合函數的應用:

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 | +------------+-------------------+-------------+

如今,讓咱們寫一個查詢語句,在檢索分類樹的同時,計算出各分類下的產品數量:

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表和一個自鏈接。

 

新增節點

到如今,咱們已經知道了如何去查詢咱們的樹,是時候去關注一下如何增長一個新節點來更新咱們的樹了。讓咱們再一次觀察一下咱們的嵌套集合圖:

image

當咱們想要在TELEVISIONS和PORTABLE ELECTRONICS節點之間新增一個節點,新節點的lft和rgt 的 值爲10和11,全部該節點的右邊節點的lft和rgt值都將加2,以後咱們再添加新節點並賦相應的lft和rgt值。在MySQL 5中可使用存儲過程來完成,我假設當前大部分讀者使用的是MySQL 4.1版本,由於這是最新的穩定版本。因此,我使用了鎖表(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; 咱們能夠檢驗一下新節點插入的正確性: 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 | +-----------------------+

若是咱們想要在葉子節點下增長節點,咱們得稍微修改一下查詢語句。讓咱們在2 WAYRADIOS葉子節點下添加FRS節點吧:

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;

在這個例子中,咱們擴大了新產生的父節點(2 WAY RADIOS節點)的右值及其全部它的右邊節點的左右值,以後置新增節點於新父節點之下。正如你所看到的,咱們新增的節點已經徹底融入了嵌套集合中:

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 | +-----------------------+

刪除節點

最後還有個基礎任務,刪除節點。刪除節點的處理過程跟節點在分層數據中所處的位置有關,刪除一個葉子節點比刪除一個子節點要簡單得多,由於刪除子節點的時候,咱們須要去處理孤立節點。

刪除一個葉子節點的過程正好是新增一個葉子節點的逆過程,咱們在刪除節點的同時該節點右邊全部節點的左右值和該父節點的右值都會減去該節點的寬度值:

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;

咱們再一次檢驗一下節點已經成功刪除,並且沒有打亂數據的層次:

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 | +-----------------------+

這個方法能夠完美地刪除節點及其子節點:

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;

再次驗證咱們已經成功的刪除了一棵子樹:

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 | +-----------------------+

有時,咱們只刪除該節點,而不刪除該節點的子節點。在一些狀況下,你但願改變其名字爲佔位符,直到替代名字的出現,好比你開除了一個主管(須要更換主管)。在另一些狀況下,你但願子節點掛到該刪除節點的父節點下:

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;

在這個例子中,咱們對該節點全部右邊節點的左右值都減去了2(由於不考慮其子節點,該節點的寬度爲2),對該節點的子節點的左右值都減去了1(彌補因爲失去父節點的左值形成的裂縫)。咱們再一次確認,那些節點是否都晉升了:

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 | +---------------+

有時,當刪除節點的時候,把該節點的一個子節點掛載到該節點的父節點下,而其餘節點掛到該節點父節點的兄弟節點下,考慮到篇幅這種狀況不在這裏解說了。

 

最後的思考

我但願這篇文章對你有所幫助,SQL中的嵌套集合的觀念大約有十年的歷史了,在網上和一些書中都能找到許多相關信息。在我看來,講述分層數據的管理最全面的,是來自一本名叫《Joe Celko's Trees and Hierarchies in SQL for Smarties》的書,此書的做者是在高級SQL領域倍受尊敬的Joe Celko。Joe Celko被認爲是嵌套集合模型的創造者,更是該領域內的多產做家。我把Celko的書看成無價之寶,並極力地推薦它。在這本書中涵蓋了在此文中沒有說起的一些高級話題,也提到了其餘一些關於鄰接表和嵌套集合模型下管理分層數據的方法。

在隨後的參考書目章節中,我列出了一些網絡資源,也許對你研究分層數據的管理會有所幫助,其中包括一些PHP相關的資源(處理嵌套集合的PHP庫)。若是你還在使用鄰接表模型,你該去試試嵌套集合模型了,在Storing Hierarchical Data in a Database 文中下方列出的一些資源連接中能找到一些樣例代碼,能夠去試驗一下。

相關文章
相關標籤/搜索