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

引言

大多數(shù)用戶都曾在數(shù)據(jù)庫中處理過分層數(shù)據(jù)(hierarchical data)姑宽,認(rèn)為分層數(shù)據(jù)的管理不是關(guān)系數(shù)據(jù)庫的目的。之所以這么認(rèn)為刊殉,是因為關(guān)系數(shù)據(jù)庫中的表沒有層次關(guān)系殉摔,只是簡單的平面化的列表;而分層數(shù)據(jù)具有父-子關(guān)系记焊,顯然關(guān)系數(shù)據(jù)庫中的表不能自然地表現(xiàn)出其分層的特性逸月。

我們認(rè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ù)插入的代碼醒第,你可以跟著做):

CREATETABLEcategory( category_idINTAUTO_INCREMENT PRIMARYKEY,nameVARCHAR(20)NOTNULL,parentINTDEFAULTNULL);INSERTINTOcategoryVALUES(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*FROMcategoryORDERBYcategory_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ù)暮唵危侨绻羌僑QL編碼的話榔至,該模型會有很多問題抵赢。

檢索整樹

通常在處理分層數(shù)據(jù)時首要的任務(wù)是,以某種縮進(jìn)形式來呈現(xiàn)一棵完整的樹唧取。為此铅鲤,在純SQL編碼中通常的做法是使用自連接(self-join):

SELECTt1.nameASlev1, t2.nameaslev2, t3.nameaslev3, t4.nameaslev4FROMcategoryASt1LEFTJOINcategoryASt2ONt2.parent = t1.category_idLEFTJOINcategoryASt3ONt3.parent = t2.category_idLEFTJOINcategoryASt4ONt4.parent = t3.category_idWHEREt1.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é)點):

SELECTt1.nameFROMcategoryASt1LEFTJOINcategoryast2ONt1.category_id = t2.parentWHEREt2.category_idISNULL; +--------------+ | name | +--------------+ | TUBE | | LCD | | PLASMA | | FLASH | | CD PLAYERS | | 2 WAY RADIOS | +--------------+

檢索單一路徑

通過自連接,我們也可以檢索出單一路徑:

SELECTt1.nameASlev1, t2.nameaslev2, t3.nameaslev3, t4.nameaslev4FROMcategoryASt1LEFTJOINcategoryASt2ONt2.parent = t1.category_idLEFTJOINcategoryASt3ONt3.parent = t2.category_idLEFTJOINcategoryASt4ONt4.parent = t3.category_idWHEREt1.name ='ELECTRONICS'ANDt4.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 |2WAY RADIOS| 17 |18| +-------------+----------------------+-----+-----+

我們使用了lftrgt來代替left和right蛾坯,是因為在MySQL中l(wèi)eft和right是保留字光酣。http://dev.mysql.com/doc/mysql/en/reserved-words.html,有一份詳細(xì)的MySQL保留字清單脉课。

那么救军,我們怎樣決定左值和右值呢?我們從外層節(jié)點的最左側(cè)開始倘零,從左到右編號:


這樣的編號方式也同樣適用于典型的樹狀結(jié)構(gòu):


當(dāng)我們?yōu)闃錉畹慕Y(jié)構(gòu)編號時唱遭,我們從左到右,一次一層呈驶,為節(jié)點賦右值前先從左到右遍歷其子節(jié)點給其子節(jié)點賦左右值拷泽。這種方法被稱作改進(jìn)的先序遍歷算法

檢索整樹

我們可以通過自連接把父節(jié)點連接到子節(jié)點上來檢索整樹袖瞻,是因為子節(jié)點的lft值總是在其父節(jié)點的lft值和rgt值之間:

SELECTnode.nameFROMnested_categoryASnode, nested_categoryASparentWHEREnode.lftBETWEENparent.lftANDparent.rgtANDparent.name ='ELECTRONICS'ORDERBYnode.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方法簡單多了霉晕。如果你仔細(xì)得看了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 |+--------------+

檢索單一路徑

在嵌套集合模型中佩微,我們可以不用多個自連接就可以檢索出單一路徑:

SELECTparent.nameFROMnested_categoryASnode, nested_categoryASparentWHEREnode.lftBETWEENparent.lftANDparent.rgtANDnode.name ='FLASH'ORDERBYparent.lft; +----------------------+ | name | +----------------------+ | ELECTRONICS | | PORTABLE ELECTRONICS | | MP3 PLAYERS | | FLASH | +----------------------+

檢索節(jié)點的深度

我們已經(jīng)知道怎樣去呈現(xiàn)一棵整樹,但是為了更好的標(biāo)識出節(jié)點在樹中所處層次萌焰,我們怎樣才能檢索出節(jié)點在樹中的深度呢哺眯?我們可以在先前的查詢語句上增加COUNT函數(shù)和GROUP BY子句來實現(xiàn):

SELECTnode.name, (COUNT(parent.name) -1)ASdepthFROMnested_categoryASnode, nested_categoryASparentWHEREnode.lftBETWEENparent.lftANDparent.rgtGROUPBYnode.nameORDERBYnode.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值來縮進(jìn)分類名字,使用CONCAT和REPEAT字符串函數(shù):

SELECTCONCAT(REPEAT(' ',COUNT(parent.name) -1), node.name)ASnameFROMnested_categoryASnode, nested_categoryASparentWHEREnode.lftBETWEENparent.lftANDparent.rgtGROUPBYnode.nameORDERBYnode.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ù)集的順序。因此盏浙,我們添加了第三個自連接作為子查詢眉睹,來得出子樹新起點的深度值:

      SELECTnode.name, (COUNT(parent.name) - (sub_tree.depth +1))ASdepthFROMnested_categoryASnode, nested_categoryASparent, nested_categoryASsub_parent, (SELECTnode.name, (COUNT(parent.name) -1)ASdepthFROMnested_categoryASnode, nested_categoryASparentWHEREnode.lftBETWEENparent.lftANDparent.rgtANDnode.name ='PORTABLE ELECTRONICS'GROUPBYnode.nameORDERBYnode.lft )ASsub_treeWHEREnode.lftBETWEENparent.lftANDparent.rgtANDnode.lftBETWEENsub_parent.lftANDsub_parent.rgtANDsub_parent.name = sub_tree.nameGROUPBYnode.nameORDERBYnode.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子句:

      SELECTnode.name, (COUNT(parent.name) - (sub_tree.depth +1))ASdepthFROMnested_categoryASnode, nested_categoryASparent, nested_categoryASsub_parent, (SELECTnode.name, (COUNT(parent.name) -1)ASdepthFROMnested_categoryASnode, nested_categoryASparentWHEREnode.lftBETWEENparent.lftANDparent.rgtANDnode.name ='PORTABLE ELECTRONICS'GROUPBYnode.nameORDERBYnode.lft )ASsub_treeWHEREnode.lftBETWEENparent.lftANDparent.rgtANDnode.lftBETWEENsub_parent.lftANDsub_parent.rgtANDsub_parent.name = sub_tree.nameGROUPBYnode.nameHAVINGdepth<=1ORDERBYnode.lft; +----------------------+-------+ | name | depth | +----------------------+-------+ | PORTABLE ELECTRONICS | 0 | | MP3 PLAYERS | 1 | | CD PLAYERS | 1 | | 2 WAY RADIOS | 1 | +----------------------+-------+

      如果你不希望呈現(xiàn)父節(jié)點,你可以更改HAVING depth <= 1HAVING depth = 1诽凌。

      嵌套集合模型中集合函數(shù)的應(yīng)用

      讓我們添加一個產(chǎn)品表毡熏,我們可以使用它來示例集合函數(shù)的應(yīng)用:

      CREATETABLEproduct( product_idINTAUTO_INCREMENT PRIMARYKEY,nameVARCHAR(40), category_idINTNOTNULL);INSERTINTOproduct(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*FROMproduct; +------------+-------------------+-------------+ | 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)語句來隔離查詢:

      LOCKTABLEnested_category WRITE;SELECT@myRight := rgtFROMnested_categoryWHEREname='TELEVISIONS';UPDATEnested_categorySETrgt = rgt +2WHERErgt > @myRight;UPDATEnested_categorySETlft = lft +2WHERElft > @myRight;INSERTINTOnested_category(name, lft, rgt)VALUES('GAME CONSOLES', @myRight +1, @myRight +2);UNLOCKTABLES; 我們可以檢驗一下新節(jié)點插入的正確性:SELECTCONCAT(REPEAT(' ', (COUNT(parent.name) -1) ), node.name)ASnameFROMnested_categoryASnode, nested_categoryASparentWHEREnode.lftBETWEENparent.lftANDparent.rgtGROUPBYnode.nameORDERBYnode.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é)點吧:

      LOCKTABLEnested_category WRITE;SELECT@myLeft := lftFROMnested_categoryWHEREname='2 WAY RADIOS';UPDATEnested_categorySETrgt = rgt +2WHERErgt > @myLeft;UPDATEnested_categorySETlft = lft +2WHERElft > @myLeft;INSERTINTOnested_category(name, lft, rgt)VALUES('FRS', @myLeft +1, @myLeft +2);UNLOCKTABLES;

      在這個例子中毁习,我們擴(kuò)大了新產(chǎn)生的父節(jié)點(2 WAY RADIOS節(jié)點)的右值及其所有它的右邊節(jié)點的左右值,之后置新增節(jié)點于新父節(jié)點之下卖丸。正如你所看到的纺且,我們新增的節(jié)點已經(jīng)完全融入了嵌套集合中:

      SELECTCONCAT(REPEAT(' ', (COUNT(parent.name) -1) ), node.name)ASnameFROMnested_categoryASnode, nested_categoryASparentWHEREnode.lftBETWEENparent.lftANDparent.rgtGROUPBYnode.nameORDERBYnode.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é)點的寬度值:

      LOCKTABLEnested_category WRITE;SELECT@myLeft := lft, @myRight := rgt, @myWidth := rgt - lft +1FROMnested_categoryWHEREname='GAME CONSOLES';DELETEFROMnested_categoryWHERElftBETWEEN@myLeftAND@myRight;UPDATEnested_categorySETrgt = rgt - @myWidthWHERErgt > @myRight;UPDATEnested_categorySETlft = lft - @myWidthWHERElft > @myRight;UNLOCKTABLES;

      我們再一次檢驗一下節(jié)點已經(jīng)成功刪除,而且沒有打亂數(shù)據(jù)的層次:

      SELECTCONCAT(REPEAT(' ', (COUNT(parent.name) -1) ), node.name)ASnameFROMnested_categoryASnode, nested_categoryASparentWHEREnode.lftBETWEENparent.lftANDparent.rgtGROUPBYnode.nameORDERBYnode.lft; +-----------------------+ | name | +-----------------------+ | ELECTRONICS | | TELEVISIONS | | TUBE | | LCD | | PLASMA | | PORTABLE ELECTRONICS | | MP3 PLAYERS | | FLASH | | CD PLAYERS | | 2 WAY RADIOS | | FRS | +-----------------------+

      這個方法可以完美地刪除節(jié)點及其子節(jié)點:

      LOCKTABLEnested_category WRITE;SELECT@myLeft := lft, @myRight := rgt, @myWidth := rgt - lft +1FROMnested_categoryWHEREname='MP3 PLAYERS';DELETEFROMnested_categoryWHERElftBETWEEN@myLeftAND@myRight;UPDATEnested_categorySETrgt = rgt - @myWidthWHERErgt > @myRight;UPDATEnested_categorySETlft = lft - @myWidthWHERElft > @myRight;UNLOCKTABLES;

      再次驗證我們已經(jīng)成功的刪除了一棵子樹:

      SELECTCONCAT(REPEAT(' ', (COUNT(parent.name) -1) ), node.name)ASnameFROMnested_categoryASnode, nested_categoryASparentWHEREnode.lftBETWEENparent.lftANDparent.rgtGROUPBYnode.nameORDERBYnode.lft; +-----------------------+ | name | +-----------------------+ | ELECTRONICS | | TELEVISIONS | | TUBE | | LCD | | PLASMA | | PORTABLE ELECTRONICS | | CD PLAYERS | | 2 WAY RADIOS | | FRS | +-----------------------+

      有時为鳄,我們只刪除該節(jié)點,而不刪除該節(jié)點的子節(jié)點腕让。在一些情況下孤钦,你希望改變其名字為占位符,直到替代名字的出現(xiàn)纯丸,比如你開除了一個主管(需要更換主管)偏形。在另外一些情況下,你希望子節(jié)點掛到該刪除節(jié)點的父節(jié)點下:

      LOCKTABLEnested_category WRITE;SELECT@myLeft := lft, @myRight := rgt, @myWidth := rgt - lft +1FROMnested_categoryWHEREname='PORTABLE ELECTRONICS';DELETEFROMnested_categoryWHERElft = @myLeft;UPDATEnested_categorySETrgt = rgt -1, lft = lft -1WHERElftBETWEEN@myLeftAND@myRight;UPDATEnested_categorySETrgt = rgt -2WHERErgt > @myRight;UPDATEnested_categorySETlft = lft -2WHERElft > @myRight;UNLOCKTABLES;

      在這個例子中觉鼻,我們對該節(jié)點所有右邊節(jié)點的左右值都減去了2(因為不考慮其子節(jié)點俊扭,該節(jié)點的寬度為2),對該節(jié)點的子節(jié)點的左右值都減去了1(彌補(bǔ)由于失去父節(jié)點的左值造成的裂縫)坠陈。我們再一次確認(rèn)萨惑,那些節(jié)點是否都晉升了:

      SELECTCONCAT(REPEAT(' ', (COUNT(parent.name) -1) ), node.name)ASnameFROMnested_categoryASnode, nested_categoryASparentWHEREnode.lftBETWEENparent.lftANDparent.rgtGROUPBYnode.nameORDERBYnode.lft; +---------------+ | name | +---------------+ | ELECTRONICS | | TELEVISIONS | | TUBE | | LCD | | PLASMA | | CD PLAYERS | | 2 WAY RADIOS | | FRS | +---------------+

      有時,當(dāng)刪除節(jié)點的時候仇矾,把該節(jié)點的一個子節(jié)點掛載到該節(jié)點的父節(jié)點下庸蔼,而其他節(jié)點掛到該節(jié)點父節(jié)點的兄弟節(jié)點下,考慮到篇幅這種情況不在這里解說了贮匕。

    • 最后編輯于
      ?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
      • 序言:七十年代末姐仅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌萍嬉,老刑警劉巖乌昔,帶你破解...
        沈念sama閱讀 211,376評論 6 491
      • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡侣颂,警方通過查閱死者的電腦和手機(jī)损拢,發(fā)現(xiàn)死者居然都...
        沈念sama閱讀 90,126評論 2 385
      • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溺蕉,“玉大人,你說我怎么就攤上這事悼做》杼兀” “怎么了?”我有些...
        開封第一講書人閱讀 156,966評論 0 347
      • 文/不壞的土叔 我叫張陵肛走,是天一觀的道長漓雅。 經(jīng)常有香客問我,道長朽色,這世上最難降的妖魔是什么邻吞? 我笑而不...
        開封第一講書人閱讀 56,432評論 1 283
      • 正文 為了忘掉前任,我火速辦了婚禮葫男,結(jié)果婚禮上抱冷,老公的妹妹穿的比我還像新娘。我一直安慰自己梢褐,他們只是感情好旺遮,可當(dāng)我...
        茶點故事閱讀 65,519評論 6 385
      • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盈咳,像睡著了一般耿眉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鱼响,一...
        開封第一講書人閱讀 49,792評論 1 290
      • 那天跷敬,我揣著相機(jī)與錄音,去河邊找鬼热押。 笑死西傀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的桶癣。 我是一名探鬼主播拥褂,決...
        沈念sama閱讀 38,933評論 3 406
      • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼牙寞!你這毒婦竟也來了饺鹃?” 一聲冷哼從身側(cè)響起莫秆,我...
        開封第一講書人閱讀 37,701評論 0 266
      • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎悔详,沒想到半個月后镊屎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
        沈念sama閱讀 44,143評論 1 303
      • 正文 獨居荒郊野嶺守林人離奇死亡茄螃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
        茶點故事閱讀 36,488評論 2 327
      • 正文 我和宋清朗相戀三年缝驳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片归苍。...
        茶點故事閱讀 38,626評論 1 340
      • 序言:一個原本活蹦亂跳的男人離奇死亡用狱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拼弃,到底是詐尸還是另有隱情夏伊,我是刑警寧澤,帶...
        沈念sama閱讀 34,292評論 4 329
      • 正文 年R本政府宣布吻氧,位于F島的核電站溺忧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏盯孙。R本人自食惡果不足惜鲁森,卻給世界環(huán)境...
        茶點故事閱讀 39,896評論 3 313
      • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望镀梭。 院中可真熱鬧刀森,春花似錦踱启、人聲如沸报账。這莊子的主人今日做“春日...
        開封第一講書人閱讀 30,742評論 0 21
      • 文/蒼蘭香墨 我抬頭看了看天上的太陽透罢。三九已至,卻和暖如春冠蒋,著一層夾襖步出監(jiān)牢的瞬間羽圃,已是汗流浹背。 一陣腳步聲響...
        開封第一講書人閱讀 31,977評論 1 265
      • 我被黑心中介騙來泰國打工抖剿, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留朽寞,地道東北人。 一個月前我還...
        沈念sama閱讀 46,324評論 2 360
      • 正文 我出身青樓斩郎,卻偏偏與公主長得像脑融,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缩宜,可洞房花燭夜當(dāng)晚...
        茶點故事閱讀 43,494評論 2 348

      推薦閱讀更多精彩內(nèi)容

      • 表結(jié)構(gòu) 新增:通過我們剛才新增數(shù)據(jù)得到這個結(jié)構(gòu)的操作肘迎,我們發(fā)現(xiàn)新增分兩種情況甥温。第一種如下圖所示:1:變更所有受影響...
        李小賤AA閱讀 555評論 0 2
      • 單例模式 適用場景:可能會在場景中使用到對象,但只有一個實例妓布,加載時并不主動創(chuàng)建姻蚓,需要時才創(chuàng)建 最常見的單例模式,...
        Obeing閱讀 2,058評論 1 10
      • 前幾天在項目開發(fā)中遇到了前輩們所設(shè)計的結(jié)構(gòu)(用來實現(xiàn)商品分類)匣沼,所設(shè)計的結(jié)構(gòu)便是利用了預(yù)排序遍歷樹算法狰挡。故特...
        AduGEN閱讀 4,867評論 6 13
      • 有個大家都很熟悉的成語叫“眼見為實”圆兵,小時候?qū)W習(xí)這個成語的時候,我是帶著權(quán)威崇拜的心態(tài)學(xué)習(xí)的枢贿。因為它是成語詞典上的...
        根號四等于二閱讀 3,975評論 7 51
      • 向?qū)莻€老驢殉农,他深知無人區(qū)的艱苦,清楚人在這樣極端的條件下會是怎樣的反應(yīng)局荚。自私也好超凳,貪婪也罷,在我看來不過是本能的...
        alexischi閱讀 246評論 4 2