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 文中下方列出的一些资源连接中能找到一些样例代码,能够去试验一下。

相关文章
相关标签/搜索