总结查询mysql下 的所有子节点。。。希望一起学习。。
在Oracle 里 Hierarchical Queries 通过CONNECT BY 可以查询所有当前节点下的所有子节点。但MySQL的目前版本中还没有对应的功能。
在MySQL中如果是有限的层次,比如我们事先如果可以确定这个树的最大深度是4, 那么所有节点为根的树的深度均不会超过4,则我们可以直接通过left join 来实现。
但很多时候我们无法控制树的深度。这时就需要在MySQL中用存储过程来实现或在你的程序中来实现这个递归。本文讨论一下几种实现的方法。
mysql>
create
table
treeNodes
-> (
-> id
int
primary
key
,
-> nodename
varchar
(20),
-> pid
int
-> );
Query OK, 0
rows
affected (0.09 sec)
mysql>
select
*
from
treenodes;
+
----+----------+------+
| id | nodename | pid |
+
----+----------+------+
| 1 | A | 0 |
| 2 | B | 1 |
| 3 | C | 1 |
| 4 | D | 2 |
| 5 | E | 2 |
| 6 | F | 3 |
| 7 | G | 6 |
| 8 | H | 0 |
| 9 | I | 8 |
| 10 | J | 8 |
| 11 | K | 8 |
| 12 | L | 9 |
| 13 | M | 9 |
| 14 | N | 12 |
| 15 | O | 12 |
| 16 | P | 15 |
| 17 | Q | 15 |
+
----+----------+------+
17
rows
in
set
(0.00 sec)
树形图
1:A
+-- 2:B
| +-- 4:D
| +-- 5:E
+-- 3:C
+-- 6:F
+-- 7:G
8:H
+-- 9:I
| +-- 12:L
| | +--14:N
| | +--15:O
| | +--16:P
| | +--17:Q
| +-- 13:M
+-- 10:J
+-- 11:K
方法一:利用函数来得到所有子节点号。
创建一个function getChildLst, 得到一个由所有子节点号组成的字符串.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
mysql> delimiter //
mysql>
mysql>
CREATE
FUNCTION
`getChildLst`(rootId
INT
)
->
RETURNS
varchar
(1000)
->
BEGIN
->
DECLARE
sTemp
VARCHAR
(1000);
->
DECLARE
sTempChd
VARCHAR
(1000);
->
->
SET
sTemp =
'$'
;
->
SET
sTempChd =
cast
(rootId
as
CHAR
);
->
-> WHILE sTempChd
is
not
null
DO
->
SET
sTemp = concat(sTemp,
','
,sTempChd);
->
SELECT
group_concat(id)
INTO
sTempChd
FROM
treeNodes
where
FIND_IN_SET(pid,sTempChd)>0;
->
END
WHILE;
->
RETURN
sTemp;
->
END
-> //
Query OK, 0
rows
affected (0.00 sec)
mysql>
mysql> delimiter ;
|
使用我们直接利用find_in_set函数配合这个getChildlst来查找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
mysql>
select
getChildLst(1);
+
-----------------+
| getChildLst(1) |
+
-----------------+
| $,1,2,3,4,5,6,7 |
+
-----------------+
1 row
in
set
(0.00 sec)
mysql>
select
*
from
treeNodes
->
where
FIND_IN_SET(id, getChildLst(1));
+
----+----------+------+
| id | nodename | pid |
+
----+----------+------+
| 1 | A | 0 |
| 2 | B | 1 |
| 3 | C | 1 |
| 4 | D | 2 |
| 5 | E | 2 |
| 6 | F | 3 |
| 7 | G | 6 |
+
----+----------+------+
7
rows
in
set
(0.01 sec)
mysql>
select
*
from
treeNodes
->
where
FIND_IN_SET(id, getChildLst(3));
+
----+----------+------+
| id | nodename | pid |
+
----+----------+------+
| 3 | C | 1 |
| 6 | F | 3 |
| 7 | G | 6 |
+
----+----------+------+
3
rows
in
set
(0.01 sec)
|
优点: 简单,方便,没有递归调用层次深度的限制 (max_sp_recursion_depth,最大255) ;
缺点:长度受限,虽然可以扩大 RETURNS varchar(1000),但总是有最大限制的。
MySQL目前版本( 5.1.33-community)中还不支持function 的递归调用。
方法二:利用临时表和过程递归
创建存储过程如下。createChildLst 为递归过程,showChildLst为调用入口过程,准备临时表及初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
mysql> delimiter //
mysql>
mysql> # 入口过程
mysql>
CREATE
PROCEDURE
showChildLst (
IN
rootId
INT
)
->
BEGIN
->
CREATE
TEMPORARY
TABLE
IF
NOT
EXISTS tmpLst
-> (sno
int
primary
key
auto_increment,id
int
,depth
int
);
->
DELETE
FROM
tmpLst;
->
-> CALL createChildLst(rootId,0);
->
->
select
tmpLst.*,treeNodes.*
from
tmpLst,treeNodes
where
tmpLst.id=treeNodes.id
order
by
tmpLst.sno;
->
END
;
-> //
Query OK, 0
rows
affected (0.00 sec)
mysql>
mysql> # 递归过程
mysql>
CREATE
PROCEDURE
createChildLst (
IN
rootId
INT
,
IN
nDepth
INT
)
->
BEGIN
->
DECLARE
done
INT
DEFAULT
0;
->
DECLARE
b
INT
;
->
DECLARE
cur1
CURSOR
FOR
SELECT
id
FROM
treeNodes
where
pid=rootId;
->
DECLARE
CONTINUE
HANDLER
FOR
NOT
FOUND
SET
done = 1;
->
->
insert
into
tmpLst
values
(
null
,rootId,nDepth);
->
->
OPEN
cur1;
->
->
FETCH
cur1
INTO
b;
-> WHILE done=0 DO
-> CALL createChildLst(b,nDepth+1);
->
FETCH
cur1
INTO
b;
->
END
WHILE;
->
->
CLOSE
cur1;
->
END
;
-> //
Query OK, 0
rows
affected (0.00 sec)
mysql> delimiter ;
|
调用时传入结点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
mysql> call showChildLst(1);
+
-----+------+-------+----+----------+------+
| sno | id | depth | id | nodename | pid |
+
-----+------+-------+----+----------+------+
| 4 | 1 | 0 | 1 | A | 0 |
| 5 | 2 | 1 | 2 | B | 1 |
| 6 | 4 | 2 | 4 | D | 2 |
| 7 | 5 | 2 | 5 | E | 2 |
| 8 | 3 | 1 | 3 | C | 1 |
| 9 | 6 | 2 | 6 | F | 3 |
| 10 | 7 | 3 | 7 | G | 6 |
+
-----+------+-------+----+----------+------+
7
rows
in
set
(0.13 sec)
Query OK, 0
rows
affected, 1 warning (0.14 sec)
mysql>
mysql> call showChildLst(3);
+
-----+------+-------+----+----------+------+
| sno | id | depth | id | nodename | pid |
+
-----+------+-------+----+----------+------+
| 1 | 3 | 0 | 3 | C | 1 |
| 2 | 6 | 1 | 6 | F | 3 |
| 3 | 7 | 2 | 7 | G | 6 |
+
-----+------+-------+----+----------+------+
3
rows
in
set
(0.11 sec)
Query OK, 0
rows
affected, 1 warning (0.11 sec)
|
depth 为深度,这样可以在程序进行一些显示上的格式化处理。类似于oracle中的 level 伪列。sno 仅供排序控制。这样你还可以通过临时表tmpLst与数据库中其它表进行联接查询。
MySQL中你可以利用系统参数 max_sp_recursion_depth 来控制递归调用的层数上限。如下例设为12.
1
2
|
mysql>
set
max_sp_recursion_depth=12;
Query OK, 0
rows
affected (0.00 sec)
|
优点 : 可以更灵活处理,及层数的显示。并且可以按照树的遍历顺序得到结果。
缺点 : 递归有255的限制。
方法三:利用中间表和过程
(本方法由yongyupost2000提供样子改编)
创建存储过程如下。由于MySQL中不允许在同一语句中对临时表多次引用,只以使用普通表tmpLst来实现了。当然你的程序中负责在用完后清除这个表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
delimiter //
drop
PROCEDURE
IF EXISTS showTreeNodes_yongyupost2000//
CREATE
PROCEDURE
showTreeNodes_yongyupost2000 (
IN
rootid
INT
)
BEGIN
DECLARE
Level
int
;
drop
TABLE
IF EXISTS tmpLst;
CREATE
TABLE
tmpLst (
id
int
,
nLevel
int
,
sCort
varchar
(8000)
);
Set
Level
=0 ;
INSERT
into
tmpLst
SELECT
id,
Level
,ID
FROM
treeNodes
WHERE
PID=rootid;
WHILE ROW_COUNT()>0 DO
SET
Level
=
Level
+1 ;
INSERT
into
tmpLst
SELECT
A.ID,
Level
,concat(B.sCort,A.ID)
FROM
treeNodes A,tmpLst B
WHERE
A.PID=B.ID
AND
B.nLevel=
Level
-1 ;
END
WHILE;
END
;
//
delimiter ;
CALL showTreeNodes_yongyupost2000(0);
|
执行完后会产生一个tmpLst表,nLevel 为节点深度,sCort 为排序字段。
使用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
SELECT
concat(
SPACE
(B.nLevel*2),
'+--'
,A.nodename)
FROM
treeNodes A,tmpLst B
WHERE
A.ID=B.ID
ORDER
BY
B.sCort;
+
--------------------------------------------+
| concat(
SPACE
(B.nLevel*2),
'+--'
,A.nodename) |
+
--------------------------------------------+
| +
--A |
| +
--B |
| +
--D |
| +
--E |
| +
--C |
| +
--F |
| +
--G |
| +
--H |
| +
--J |
| +
--K |
| +
--I |
| +
--L |
| +
--N |
| +
--O |
| +
--P |
| +
--Q |
| +
--M |
+
--------------------------------------------+
17
rows
in
set
(0.00 sec)
|
优点 : 层数的显示。并且可以按照树的遍历顺序得到结果。没有递归限制。
缺点 : MySQL中对临时表的限制,只能使用普通表,需做事后清理。
查询
1、根据子节点查询所有的父节点 创建getParentList函数
此时Mysql可能会报如下错误:
解决方法:
执行此语句:show VARIABLES like "log_bin_trust_function_creators";
发现log_bin_trust_function_creators的值为OFF(这是默认值)
那么,我们再执行语句:set global log_bin_trust_function_creators = 1;
现在再来查看log_bin_trust_function_creators的值已经变为ON了
最后,函数就可以创建成功了。
新建一张数据表province
执行查询语句:select * from province where FIND_IN_SET(id,getParentList(17))
查询结果:
2、根据父节点查找所有的子节点 创建函数getChildrenList
执行查询语句:select * from province where FIND_IN_SET(id,getChildrenList(1))
例子
执行
CREATE TABLE `treenodes` (
`id` int(11) NOT NULL,
`nodename` varchar(20) DEFAULT NULL,
`pid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `treenodes` VALUES ('1', 'A', '0');
INSERT INTO `treenodes` VALUES ('2', 'B', '1');
INSERT INTO `treenodes` VALUES ('3', 'C', '1');
INSERT INTO `treenodes` VALUES ('4', 'D', '2');
INSERT INTO `treenodes` VALUES ('5', 'E', '2');
INSERT INTO `treenodes` VALUES ('6', 'F', '3');
INSERT INTO `treenodes` VALUES ('7', 'G', '6');
INSERT INTO `treenodes` VALUES ('8', 'H', '0');
INSERT INTO `treenodes` VALUES ('9', 'I', '8');
INSERT INTO `treenodes` VALUES ('10', 'J', '8');
INSERT INTO `treenodes` VALUES ('11', 'K', '8');
INSERT INTO `treenodes` VALUES ('12', 'L', '9');
INSERT INTO `treenodes` VALUES ('13', 'M', '9');
INSERT INTO `treenodes` VALUES ('14', 'N', '12');
INSERT INTO `treenodes` VALUES ('15', 'O', '12');
INSERT INTO `treenodes` VALUES ('16', 'P', '15');
INSERT INTO `treenodes` VALUES ('17', 'Q', '15');
查询父节点的函数
delimiter //
CREATE FUNCTION `getParentList`(rootId INT)
RETURNS char(255)
BEGIN
declare fid int default 1;
declare str char(255) default rootId;
while rootId>0 do
set fid=(SELECT pid FROM hostmonitor.treenodes WHERE rootId=id);
IF fid > 0 THEN
SET str=concat(str,',',fid);
SET rootId=fid;
ELSE SET rootId=fid;
END IF;
END WHILE;
return str;
END //
调用select getParentList(7);
查询子节点
delimiter //
CREATE FUNCTION `getChildList`(rootId INT)
RETURNS char(255)
BEGIN
DECLARE str char(255) ;
DECLARE cid char(255) ;
SET str = '';
SET cid =cast(rootId as CHAR);
WHILE cid is not null DO
SET str= concat(str,',',cid);
SELECT group_concat(id) INTO cid FROM treeNodes where FIND_IN_SET(pid,cid)>0;
END WHILE;
RETURN str;
END //
select getChildLst(1);
在看一个例子
一、查父集合
Sql代码:
Sql代码
二、查子集合
Java代码:查询
Sql代码Mysql函数中并不支持动态sql,Dynamic SQL is not allowed in stored function or trigger
要想查多个表的,可以建多个函数,或用以下方法
-
DROP TABLE IF EXISTS `t_areainfo`;
CREATE TABLE `t_areainfo` (
`id` int(11) NOT '0' AUTO_INCREMENT,
`level` int(11) DEFAULT '0',
`name` varchar(255) DEFAULT '0',
`parentId` int(11) DEFAULT '0',
`status` int(11) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=65 DEFAULT CHARSET=utf8;
--初始数据
INSERT INTO `t_areainfo` VALUES ('1', '0', '中国', '0', '0');
INSERT INTO `t_areainfo` VALUES ('2', '0', '华北区', '1', '0');
INSERT INTO `t_areainfo` VALUES ('3', '0', '华南区', '1', '0');
INSERT INTO `t_areainfo` VALUES ('4', '0', '北京', '2', '0');
INSERT INTO `t_areainfo` VALUES ('5', '0', '海淀区', '4', '0');
INSERT INTO `t_areainfo` VALUES ('6', '0', '丰台区', '4', '0');
INSERT INTO `t_areainfo` VALUES ('7', '0', '朝阳区', '4', '0');
INSERT INTO `t_areainfo` VALUES ('8', '0', '北京XX区1', '4', '0');
INSERT INTO `t_areainfo` VALUES ('9', '0', '北京XX区2', '4', '0');
INSERT INTO `t_areainfo` VALUES ('10', '0', '北京XX区3', '4', '0');
INSERT INTO `t_areainfo` VALUES ('11', '0', '北京XX区4', '4', '0');
INSERT INTO `t_areainfo` VALUES ('12', '0', '北京XX区5', '4', '0');
INSERT INTO `t_areainfo` VALUES ('13', '0', '北京XX区6', '4', '0');
INSERT INTO `t_areainfo` VALUES ('14', '0', '北京XX区7', '4', '0');
INSERT INTO `t_areainfo` VALUES ('15', '0', '北京XX区8', '4', '0');
INSERT INTO `t_areainfo` VALUES ('16', '0', '北京XX区9', '4', '0');
INSERT INTO `t_areainfo` VALUES ('17', '0', '北京XX区10', '4', '0');
INSERT INTO `t_areainfo` VALUES ('18', '0', '北京XX区11', '4', '0');
INSERT INTO `t_areainfo` VALUES ('19', '0', '北京XX区12', '4', '0');
INSERT INTO `t_areainfo` VALUES ('20', '0', '北京XX区13', '4', '0');
INSERT INTO `t_areainfo` VALUES ('21', '0', '北京XX区14', '4', '0');
INSERT INTO `t_areainfo` VALUES ('22', '0', '北京XX区15', '4', '0');
INSERT INTO `t_areainfo` VALUES ('23', '0', '北京XX区16', '4', '0');
INSERT INTO `t_areainfo` VALUES ('24', '0', '北京XX区17', '4', '0');
INSERT INTO `t_areainfo` VALUES ('25', '0', '北京XX区18', '4', '0');
INSERT INTO `t_areainfo` VALUES ('26', '0', '北京XX区19', '4', '0');
INSERT INTO `t_areainfo` VALUES ('27', '0', '北京XX区1', '4', '0');
INSERT INTO `t_areainfo` VALUES ('28', '0', '北京XX区2', '4', '0');
INSERT INTO `t_areainfo` VALUES ('29', '0', '北京XX区3', '4', '0');
INSERT INTO `t_areainfo` VALUES ('30', '0', '北京XX区4', '4', '0');
INSERT INTO `t_areainfo` VALUES ('31', '0', '北京XX区5', '4', '0');
INSERT INTO `t_areainfo` VALUES ('32', '0', '北京XX区6', '4', '0');
INSERT INTO `t_areainfo` VALUES ('33', '0', '北京XX区7', '4', '0');
INSERT INTO `t_areainfo` VALUES ('34', '0', '北京XX区8', '4', '0');
INSERT INTO `t_areainfo` VALUES ('35', '0', '北京XX区9', '4', '0');
INSERT INTO `t_areainfo` VALUES ('36', '0', '北京XX区10', '4', '0');
INSERT INTO `t_areainfo` VALUES ('37', '0', '北京XX区11', '4', '0');
INSERT INTO `t_areainfo` VALUES ('38', '0', '北京XX区12', '4', '0');
INSERT INTO `t_areainfo` VALUES ('39', '0', '北京XX区13', '4', '0');
INSERT INTO `t_areainfo` VALUES ('40', '0', '北京XX区14', '4', '0');
INSERT INTO `t_areainfo` VALUES ('41', '0', '北京XX区15', '4', '0');
INSERT INTO `t_areainfo` VALUES ('42', '0', '北京XX区16', '4', '0');
INSERT INTO `t_areainfo` VALUES ('43', '0', '北京XX区17', '4', '0');
INSERT INTO `t_areainfo` VALUES ('44', '0', '北京XX区18', '4', '0');
INSERT INTO `t_areainfo` VALUES ('45', '0', '北京XX区19', '4', '0');
INSERT INTO `t_areainfo` VALUES ('46', '0', 'xx省1', '1', '0');
INSERT INTO `t_areainfo` VALUES ('47', '0', 'xx省2', '1', '0');
INSERT INTO `t_areainfo` VALUES ('48', '0', 'xx省3', '1', '0');
INSERT INTO `t_areainfo` VALUES ('49', '0', 'xx省4', '1', '0');
INSERT INTO `t_areainfo` VALUES ('50', '0', 'xx省5', '1', '0');
INSERT INTO `t_areainfo` VALUES ('51', '0', 'xx省6', '1', '0');
INSERT INTO `t_areainfo` VALUES ('52', '0', 'xx省7', '1', '0');
INSERT INTO `t_areainfo` VALUES ('53', '0', 'xx省8', '1', '0');
INSERT INTO `t_areainfo` VALUES ('54', '0', 'xx省9', '1', '0');
INSERT INTO `t_areainfo` VALUES ('55', '0', 'xx省10', '1', '0');
INSERT INTO `t_areainfo` VALUES ('56', '0', 'xx省11', '1', '0');
INSERT INTO `t_areainfo` VALUES ('57', '0', 'xx省12', '1', '0');
INSERT INTO `t_areainfo` VALUES ('58', '0', 'xx省13', '1', '0');
INSERT INTO `t_areainfo` VALUES ('59', '0', 'xx省14', '1', '0');
INSERT INTO `t_areainfo` VALUES ('60', '0', 'xx省15', '1', '0');
INSERT INTO `t_areainfo` VALUES ('61', '0', 'xx省16', '1', '0');
INSERT INTO `t_areainfo` VALUES ('62', '0', 'xx省17', '1', '0');
INSERT INTO `t_areainfo` VALUES ('63', '0', 'xx省18', '1', '0');
INSERT INTO `t_areainfo` VALUES ('64', '0', 'xx省19', '1', '0');
--查询传入areaId及其以下所有子节点
DROP FUNCTION IF EXISTS queryChildrenAreaInfo;
CREATE FUNCTION `queryChildrenAreaInfo` (areaId INT)
RETURNS VARCHAR(4000)
BEGIN
DECLARE sTemp VARCHAR(4000);
DECLARE sTempChd VARCHAR(4000);
SET sTemp = '$';
SET sTempChd = cast(areaId as char);
WHILE sTempChd is not NULL DO
SET sTemp = CONCAT(sTemp,',',sTempChd);
SELECT group_concat(id) INTO sTempChd FROM t_areainfo where FIND_IN_SET(parentId,sTempChd)>0;
END WHILE;
return sTemp;
END;
--调用方式
select queryChildrenAreaInfo(1);
select * from t_areainfo where FIND_IN_SET(id, queryChildrenAreaInfo(1));
-- 创建存储过程
drop PROCEDURE showChildList;
CREATE PROCEDURE showChildList (IN rootId INT)
BEGIN
CREATE TEMPORARY TABLE
IF NOT EXISTS tmpList (
sno INT PRIMARY KEY auto_increment,
id INT,
depth INT
);
DELETE FROM tmpList;
CALL createChildList (rootId, 0);
SELECT tmpList.*, t_areainfo.* FROM tmpList, t_areainfo
WHERE
tmpList.id = t_areainfo.id
ORDER BY
tmpList.sno;
END;
drop PROCEDURE createChildList;
CREATE PROCEDURE createChildList (IN rootId INT, IN nDepth INT)
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE b INT;
DECLARE cur1 CURSOR FOR SELECT id FROM t_areainfo WHERE parentId = rootId;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET done = 1;
INSERT INTO tmpList VALUES (NULL, rootId, nDepth);
OPEN cur1;
FETCH cur1 INTO b;
WHILE done = 0 DO
CALL createChildList (b, nDepth + 1);
FETCH cur1 INTO b;
END WHILE;
CLOSE cur1;
END;
-- 调用方式
call showChildList(1);
--简易程度
首先我们可以通过sql语句就可以看的出,方式二的代码量差不多是方式一的两倍,而且又是临时表又是游标的,极易出错。
--效率对比
可以通过图片可以看到,同样的查询结果,方式一仅仅需要0.044s既可以完成查询,而方式二则需要1.525s,效率远远低于方式一。
强烈推荐用方式一
执行方式二是系统出报错,错误原因是因为没有指定控制递归调用层数上线,可以通过利用系统参数 max_sp_recursion_depth 来控制递归调用的层数上限。