mysql tree_MySQL树形遍历(二)

转载自:

http://blog.csdn.net/dreamer0924/article/details/7580278

英文原文:

http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/

预排序遍历树算法:modified preorder tree traversal algorithm

这个算法有如下几个数据结构

1 lft 代表左 left

2 rgt 代表右 right

3 lvl 代表所在的层次 level

下面这个图是一个典型的结构

1337324704_1024.png

我们先看一些使用方法

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节点

1337324669_3248.png

我们要做的工作就是

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

这样就完成了一个新节点的增加操作。

另一篇详细解释:

译文:Yimin

引言

大多数用户都曾在数据库中处理过分层数据(hierarchical data),认为分层数据的管理不是关系数据库的目的。之所以这么认为,是因为关系数据库中的表没有层次关系,只是简单的平面化的列表;而分层数据具有父-子关系,显然关系数据库中的表不能自然地表现出其分层的特性。

我们认为,分层数据是每项只有一个父项和零个或多个子项(根项除外,根项没有父项)的数据集合。分层数据存在于许多基于数据库的应用程序中,包括论坛和邮件列表中的分类、商业组织图表、内容管理系统的分类、产品分类。我们打算使用下面一个虚构的电子商店的产品分类:

268693e87e4b339ba8d016f0e43df4c5.png

这些分类层次与上面提到的一些例子中的分类层次是相类似的。在本文中我们将从传统的邻接表(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分类图:

a8c4cd84c43d4806df730e3b77fa72d5.png

从上图可以看出我们依旧保持了数据的层次,父分类包围了其子分类。在数据表中,我们通过使用表示节点的嵌套关系的左值(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 |

+-------------+----------------------+-----+-----+

我们使用了lft和rgt来代替left和right,是因为在MySQL中left和right是保留字。http://dev.mysql.com/doc/mysql/en/reserved-words.html,有一份详细的MySQL保留字清单。

那么,我们怎样决定左值和右值呢?我们从外层节点的最左侧开始,从左到右编号:

edaab3b93095639cd32cc5f308bf1c01.png

这样的编号方式也同样适用于典型的树状结构:

3a0e01a9924facc58dfd8deda8b4a3e8.png

当我们为树状的结构编号时,我们从左到右,一次一层,为节点赋右值前先从左到右遍历其子节点给其子节点赋左右值。这种方法被称作改进的先序遍历算法。

检索整树

我们可以通过自连接把父节点连接到子节点上来检索整树,是因为子节点的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值的增加和减少来添加

标签。

检索子树的深度

当我们需要子树的深度信息时,我们不能限制自连接中的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。

嵌套集合模型中集合函数的应用

让我们添加一个产品表,我们可以使用它来示例集合函数的应用:

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表和一个自连接。

新增节点

到现在,我们已经知道了如何去查询我们的树,是时候去关注一下如何增加一个新节点来更新我们的树了。让我们再一次观察一下我们的嵌套集合图:

d12d0b0972c9b73c0ce43d033c8ae160.png

当我们想要在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 文中下方列出的一些资源链接中能找到一些样例代码,可以去试验一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/527427.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql 常用数据库连接池_常见的数据库连接池

欢迎进入Java社区论坛&#xff0c;与200万技术人员互动交流 >>进入 2.C3P0 在Hibernate和Spring中默认支持该数据库连接池 需要引入&#xff1a;c3p0-0.9.1.2.jar包&#xff0c;如果报错再引入mchange-commons-0.2.jar 1. 在类路径下编写一个c3p0-config.xml文件 c3p0-co…

win32_bios 的对象编辑器无法保存对象_怎样创建Femap对象

创建Femap对象主要有两种方式&#xff0c;一是直接在Femap内置的API程序窗体中创建&#xff0c;二是在API程序窗口以外的开发环境中创建。一、使用FEMAP集成的API程序窗口开始使用FEMAP API的最快方法是打开API编程窗口。它提供了一个完整的编辑、调试和运行的环境&#xff0c;…

未定义变量: data_三、变量声明

三、变量声明var声明主要特点&#xff1a; - var是函数作用域&#xff0c;只针对函数声明 - 可以多次声明同一个变量不会报错 - 捕获变量怪异之处function fnVar(flag: boolean) {if(flag) {var x 10;}return x; } fnVar(true); // 10 fnVar(false); // undefinedvar isDone: …

表单的默认提交方式_对于PHP表单提交有哪集中方式讲解

PHP 做网页后端还是很优秀的&#xff0c;PHP 表单提交&#xff0c;不外乎两种方法&#xff0c;即 GET 和 POST 方法&#xff1b;PHP后台使用全局变量$_POST;$_GET;来获取提交数据。代码&#xff1a;<!DOCTYPE HTML> <html> <head><meta charset"utf-…

spring中怎么让事物提交_Spring怎么在一个事务中开启另一个事务

点击上方“Java知音”&#xff0c;选择“置顶公众号”技术文章第一时间送达&#xff01;作者&#xff1a;Mazinmy.oschina.net/u/3441184/blog/893628Spring项目&#xff0c;需要在一个事务中开启另一个事务。上面提到的情景可能不常见&#xff0c;但是还是会有的&#xff0c;一…

通过对象指针的方式强行指定到子类_C++中的虚指针与虚函数表

​ 最近在逛B站的时候发现有候捷老师的课程&#xff0c;如获至宝。因此&#xff0c;跟随他的讲解又复习了一遍关于C的内容&#xff0c;收获也非常的大&#xff0c;对于某些模糊的概念及遗忘的内容又有了更深的认识。以下内容是关于虚函数表、虚函数指针&#xff0c;而C中的动态…

如何手动输入给数组赋值_你是否真的了解VBA数组呢?让我带你认识一下真正的数组...

大家好&#xff0c;我们今日继续讲解VBA代码解决方案的第110讲内容&#xff1a;VBA数组讲解&#xff0c;什么是数组&#xff0c;如何定义数组&#xff0c;如何创建数组一、什么是数组 就是数组共享一个名字&#xff0c;有着多个元素按顺序排列的变量。在数组中&#xff0c;元素…

路由器装上去没有网络_5G网络那么快,今后路由器没有存在的价值了?

从一些宣传来看&#xff0c;即将商用的5G网速确实很快&#xff0c;号称10Gbps&#xff0c;比有线宽带还要快。既然5G网速那么快了&#xff0c;还有必要装宽带和路由器吗?先来纠正一个误区&#xff0c;5G网络的理论速度与实际速度是有很大差别的!!目前很多宣传中所说的5G网速最…

python绘制散点图的步骤_python如何绘制散点图?

饼图&#xff1a;饼图显示静态数字以及类别如何表示整体构成的一部分。饼图以百分比表示数字&#xff0c;所有段的总和需要等于100&#xff05;。plt.pie(df[Age], labels {"A", "B", "C","D", "E", "F","G…

python问卷调查系统设计案例_从设计到施工,全面剖析超级玻璃屋面系统案例!...

【设计师的材料清单】戳图即可购买《设计师的材料清单》实体书或获取高清电子书转载整理自&#xff1a;弗思特(ID&#xff1a;FFTCCL)各位设计师大家好&#xff01;随着人们对建筑外观及内部环境要求的不断提高具有大面积采光且轻盈的玻璃屋面系统在大型公共建筑设计中被广泛使…

二叉树的建立与遍历完整代码_腾讯面试官这样问我二叉树,我刚好都会

前记上周我投递出了简历&#xff0c;岗位是后端开发工程师。这周腾讯面试官给我进行了视频面试。面试过程中他问了二叉树的问题。二叉树相关算法题&#xff0c;在面试中出现的次数非常非常多&#xff0c;所以我面试之前也有所准备。今天结合面试问题详细讲一讲二叉树&#xff0…

创建mysql数据库图解_mysql数据库怎么创建外键?(图文+视频)

本篇文章主要给大家介绍mysql数据库怎么创建外键。关于mysql数据库外键的基础介绍&#xff0c;我们在这篇文章【Mysql外键是什么&#xff1f;有哪些用处&#xff1f;】中&#xff0c;已经给大家介绍过了&#xff0c;需要的朋友可以选择参考。了解了外键的基础定义&#xff0c;那…

redis 管理工具_Redis桌面管理工具Redis Desktop Manager 2019.2发布

简介Redis桌面管理器(又名RDM) - 是适用于Windows&#xff0c;Linux和MacOS的快速开源Redis数据库管理应用程序。该工具为您提供了一个易于使用的GUI&#xff0c;可以访问您的Redis数据库并执行一些基本操作&#xff1a;将键视为树&#xff0c;CRUD键&#xff0c;通过shell执行…

sql in里面可以放多少参数_如何从文本文件读入 SQL 参数

有时我们希望把参数列表分行存储在文本文件里&#xff0c;执行SQL时再拼到in函数里&#xff0c;从而查询出符合条件的记录。但SQL不能解析文本文件&#xff0c;直接拼到in函数里有困难&#xff0c;所以很多人先把文件导入数据库临时表&#xff0c;再用join语句做关联&#xff0…

谷歌浏览器32位安装包_谷歌浏览器发布紧急安全更新修复Blink内核中的任意代码执行漏洞...

上月底谷歌浏览器推送紧急安全更新对浏览器漏洞进行修复&#xff0c;当时谷歌浏览器博客并未公布漏洞的具体细节信息。蓝点网当时也在文章中称通常这种不公布漏洞的更新&#xff0c;都是比较严重的问题因此只有等多数用户修复后才会公开。现在多数能够自动更新的用户已经升级到…

java的serversocket_Java ServerSocket 实现聊天室功能(简易版)

大体实现功能可以选择房间&#xff0c;可以与房间中的用户进行实时的交流说在前面的话:serverSocket.accept() 会形成阻塞scanner.hasNextLine() 和 scanner.nextLine() 也会形成阻塞windows 下需要打开telnet服务。请自行百度实现思路:用户连接上服务器后选择房间号&#xff0…

ensp删除所有命令_HCIA学习笔记——eNSP配置NAT技术

今天用华为的eNSP做了关于NAT的实验&#xff0c;我把它发了出来&#xff0c;有需要的小伙伴可以收藏一下。分别配置了静态的NAT&#xff0c;NAT Outbound配置&#xff0c;Easy IP配置&#xff0c;NAT server配置。接口配置如图所示。在网关路由器上配置访问外网的默认路由。配置…

java程序员用代码写的情书_用代码写的三行情诗,你懂得程序员的浪漫吗?

原标题&#xff1a;用代码写的三行情诗&#xff0c;你懂得程序员的浪漫吗&#xff1f;在一般人眼里&#xff0c;程序员是一群古板&#xff0c;技术宅&#xff0c;不懂得浪漫的屌丝&#xff0c;但其实这些大神们浪漫起来也是相当可以的&#xff01;下面让我们来看一下程序员们用…

对比四大企业级linux版本_Linux家族族谱

大体上来讲&#xff0c;Linux分为两个生态体系&#xff0c;红帽和debian。商业版本以Redhat为代表&#xff0c;开源社区版本则以debian为代表。红帽家族redhat &#xff1a;红帽自家服务器centos &#xff1a;基于红帽重新封装的去掉版权信息的免费版本fedora &#xff1a;基于…

mysql vim 命令_Vim基本命令必知必会

光标移动定位方式hjkl (强例推荐使用其移动光标&#xff0c;但不必需) →你也可以使用光标键 (←↓↑→). 注: j 就像下箭头0 → 数字零&#xff0c;到行首^ → 到本行第一个不是blank字符的位置(所谓blank字符就是空格&#xff0c;tab&#xff0c;换行&#xff0c;回车等)$ →…