mysql 实现树形的遍历
前言:
关于多级别菜单栏或者权限系统中部门上下级的树形遍历,oracle中有connect by来实现,mysql没有这样的便捷途径,所以MySQL遍历数据表是我们经常会遇到的头痛问题,下面通过存储过程来实现。
1、建立测试表和数据:
DROP TABLE IF EXISTS test.channel; CREATE TABLE test.channel ( id INT(11) NOT NULL AUTO_INCREMENT, cname VARCHAR(200) DEFAULT NULL, parent_id INT(11) DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=INNODB DEFAULT CHARSET=utf8; INSERT INTO channel(id,cname,parent_id) VALUES (13,'首页',-1), (14,'TV580',-1), (15,'生活580',-1), (16,'左上幻灯片',13), (17,'帮忙',14), (18,'栏目简介',17);
2、用临时表和递归过程实现树的遍历(mysql的UDF不能递归调用):
2.1、递归过程输出某节点id路径,类似Oracle SYS_CONNECT_BY_PATH的功能
-- 递归输出某节点id路径 DELIMITER // DROP PROCEDURE IF EXISTS pro_cre_pathlist; CREATE PROCEDURE pro_cre_pathlist(IN nid INT,IN delimit VARCHAR(10), INOUT pathstr VARCHAR(1000)) BEGIN DECLARE done INT DEFAULT 0; DECLARE parentid INT DEFAULT 0; DECLARE cur1 CURSOR FOR SELECT t.parent_id,CONCAT(CAST(t.parent_id AS CHAR),delimit,pathstr) from channel AS t WHERE t.id = nid; -- 下面这行表示若没有数据返回,程序继续,并将变量done设为1 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; -- mysql中可以利用系统参数 max_sp_recursion_depth 来控制递归调用的层数上限。 SET max_sp_recursion_depth=12; OPEN cur1; -- 游标向下走一步 FETCH cur1 INTO parentid,pathstr; WHILE done=0 DO CALL pro_cre_pathlist(parentid,delimit,pathstr); -- 游标向下走一步 FETCH cur1 INTO parentid,pathstr; END WHILE; CLOSE cur1; END // DELIMITER ;
测试:
SET @str='16'; CALL pro_cre_pathlist(16,'/',@str); SELECT @str;
测试结果:
2.2、递归过程输出某节点name路径
-- 递归输出某节点name路径 DELIMITER // DROP PROCEDURE IF EXISTS pro_cre_pnlist; CREATE PROCEDURE pro_cre_pnlist(IN nid INT,IN delimit VARCHAR(10), INOUT pathstr VARCHAR(1000)) BEGIN DECLARE done INT DEFAULT 0; DECLARE parentid INT DEFAULT 0; DECLARE cur1 CURSOR FOR SELECT t.parent_id,CONCAT(t.cname,delimit,pathstr) from channel AS t WHERE t.id = nid; -- 下面这行表示若没有数据返回,程序继续,并将变量done设为1 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; -- mysql中可以利用系统参数 max_sp_recursion_depth 来控制递归调用的层数上限。 SET max_sp_recursion_depth=12; OPEN cur1; -- 游标向下走一步 FETCH cur1 INTO parentid,pathstr; WHILE done=0 DO CALL pro_cre_pnlist(parentid,delimit,pathstr); -- 游标向下走一步 FETCH cur1 INTO parentid,pathstr; END WHILE; CLOSE cur1; END // DELIMITER ;
测试:
SET @str=''; CALL pro_cre_pnlist(16,'/',@str); SELECT @str;
测试结果:
2.3、调用函数输出id路径
-- 调用函数输出id路径 DELIMITER // DROP FUNCTION IF EXISTS fn_tree_path; CREATE FUNCTION fn_tree_path(nid INT,delimit VARCHAR(10)) RETURNS VARCHAR(2000) CHARSET utf8 BEGIN DECLARE pathid VARCHAR(1000); SET pathid = CAST(nid AS CHAR); CALL pro_cre_pathlist(nid,delimit,pathid); RETURN pathid; END // DELIMITER ;
测试:
SELECT fn_tree_path(16,'/') AS id;
测试结果:
2.4、调用函数输出name路径
-- 调用函数输出name路径 DELIMITER // DROP FUNCTION IF EXISTS fn_tree_pathname; CREATE FUNCTION fn_tree_pathname(nid INT,delimit VARCHAR(10)) RETURNS VARCHAR(2000) CHARSET utf8 BEGIN DECLARE pathid VARCHAR(1000); SET pathid=''; CALL pro_cre_pnlist(nid,delimit,pathid); RETURN pathid; END // DELIMITER ;
测试:
SELECT fn_tree_pathname(16,'/') AS name;
测试结果:
2.5、调用过程输出子节点
-- 调用过程输出子节点 DELIMITER // DROP PROCEDURE IF EXISTS pro_show_childlist; CREATE PROCEDURE pro_show_childlist(IN rootId INT) BEGIN DROP TEMPORARY TABLE IF EXISTS tmpList; CREATE TEMPORARY TABLE IF NOT EXISTS tmpList( sno INT PRIMARY KEY AUTO_INCREMENT, id INT, depth INT); CALL pro_cre_childlist(rootId,0); SELECT channel.id,CONCAT(SPACE(tmpList.depth*2),'--',channel.cname)NAME, channel.parent_id,tmpList.depth,fn_tree_path(channel.id,'/')path, fn_tree_pathname(channel.id,'/')pathname FROM tmpList,channel WHERE tmpList.id=channel.id ORDER BY tmpList.sno; END // DELIMITER ;
2.6、从某节点向下遍历子节点,递归生成临时表数据
DELIMITER // DROP PROCEDURE IF EXISTS pro_cre_childlist; CREATE PROCEDURE pro_cre_childlist(IN rootId INT,IN nDepth INT) BEGIN DECLARE done INT DEFAULT 0; DECLARE b INT; DECLARE cur1 CURSOR FOR SELECT id FROM channel WHERE parent_id=rootId; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; SET max_sp_recursion_depth=12; INSERT INTO tmpList VALUES(NULL,rootId,nDepth); OPEN cur1; FETCH cur1 INTO b; WHILE done=0 DO CALL pro_cre_childlist(b,nDepth+1); FETCH cur1 INTO b; END WHILE CLOSE cur1; END // DELIMITER ;
2.7、调用过程输出父节点
-- 调用过程输出父节点 DELIMITER // DROP PROCEDURE IF EXISTS pro_show_parentlist; CREATE PROCEDURE pro_show_parentlist(IN rootId INT) BEGIN DROP TEMPORARY TABLE IF EXISTS tmpList; CREATE TEMPORARY TABLE IF NOT EXISTS tmpList( sno INT PRIMARY KEY AUTO_INCREMENT, id INT, depth INT); CALL pro_cre_parentlist(rootId,0); SELECT channel.id,CONCAT(SPACE(tmpList.depth*2),'--',channel.cname)NAME, channel.parent_id,tmpList.depth,fn_tree_path(channel.id,'/')path, fn_tree_pathname(channel.id,'/')pathname FROM tmpList,channel WHERE tmpList.id=channel.id ORDER BY tmpList.sno; END // DELIMITER ;
2.8、从某节点向上追溯根节点,递归生成临时表数据
DELIMITER // DROP PROCEDURE IF EXISTS pro_cre_parentlist; CREATE PROCEDURE pro_cre_parentlist(IN rootId INT,IN nDepth INT) BEGIN DECLARE done INT DEFAULT 0; DECLARE b INT; DECLARE cur1 CURSOR FOR SELECT parent_id FROM channel WHERE id=rootId; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; SET max_sp_recursion_depth=12; INSERT INTO tmpList VALUES(NULL,rootId,nDepth); OPEN cur1; FETCH cur1 INTO b; WHILE done=0 DO CALL pro_cre_parentlist(b,nDepth+1); FETCH cur1 INTO b; END WHILE; CLOSE cur1; END // DELIMITER ;
3、开始测试
3.1、从根节点开始显示,显示子节点集合:
CALL pro_show_childlist(-1);
测试结果:
3.2、显示首页下面的子节点
CALL pro_show_childlist(13);
测试结果:
3.3、显示TV580下面的所有子节点
CALL pro_show_childlist(14);
测试结果:
3.4、“帮忙”节点有一个子节点,显示出来:
CALL pro_show_childlist(17);
测试结果:
3.5、“栏目简介”没有子节点,所以只显示最终节点:
3.6、显示“首页”的父节点
CALL pro_show_parentlist(13);
测试结果:
3.7、显示“TV580”的父节点,parent_id为-1
CALL pro_show_parentlist(14);
测试结果:
3.8、显示“帮忙”节点的父节点
CALL pro_show_parentlist(17);
测试结果:
3.9、显示最低层节点“栏目简介”的父节点
CALL pro_show_parentlist(18);
测试结果: