mysql 实现树形的遍历分析

访客 阅读:270 2020-02-17 20:54:37 评论:0

前言:
关于多级别菜单栏或者权限系统中部门上下级的树形遍历,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);

测试结果:

声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

发表评论
搜索
排行榜
关注我们

扫一扫关注我们,了解最新精彩内容