日期:2014-05-16  浏览次数:20812 次

MySQL中进行树状所有子节点的查询

?
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, 得到一个由所有子节点号组成的字符串.?


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来查找


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为调用入口过程,准备临时表及初始化。

?

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 creat