网站建设知识
实现树形的遍历(关于多级菜单栏以及多级上下部门的查询问题)
2025-07-22 09:55  点击:0
前言: 关于多级别菜单栏或者权限系统中部门上下级的树形遍历,oracle中有connect by来实现,mysql没有这样的便捷途径,所以MySQL遍历数据表是我们经常会遇到的头痛问题,下面通过存储过程来实现。1,建立测试表和数据:
DROP TABLE IF EXISTS csdn.channel;   CREATE TABLE csdn.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);  DROP TABLE IF EXISTS channel;
2,利用临时表和递归过程实现树的遍历(mysql的UDF不能递归调用):2.1,从某节点向下遍历子节点,递归生成临时表数据
-- pro_cre_childlistDELIMITER $$     DROP PROCEDURE IF EXISTS csdn.pro_cre_childlist$$   CREATE PROCEDURE csdn.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 tmpLst 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$$   
2.2,从某节点向上追溯根节点,递归生成临时表数据
-- pro_cre_parentlistDELIMITER $$DROP PROCEDURE IF EXISTS csdn.pro_cre_parentlist$$   CREATE PROCEDURE csdn.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 tmpLst 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$$   
2.3,实现类似Oracle SYS_CONNECT_BY_PATH的功能,递归过程输出某节点id路径
-- pro_cre_pathlistDELIMITER $$USE csdn$$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;                    DECLARE ConTINUE HANDLER FOR NOT FOUND SET done = 1;         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 ;
2.4,递归过程输出某节点name路径
-- pro_cre_pnlistDELIMITER $$USE csdn$$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;                    DECLARE ConTINUE HANDLER FOR NOT FOUND SET done = 1;         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 ;
2.5,调用函数输出id路径
-- fn_tree_pathDELIMITER $$ DROP FUNCTION IF EXISTS csdn.fn_tree_path$$   CREATE FUNCTION csdn.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$$       
2.6,调用函数输出name路径
-- fn_tree_pathname-- 调用函数输出name路径   DELIMITER $$ DROP FUNCTION IF EXISTS csdn.fn_tree_pathname$$   CREATE FUNCTION csdn.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 ;   
2.7,调用过程输出子节点
-- pro_show_childLst  DELIMITER $$-- 调用过程输出子节点   DROP PROCEDURE IF EXISTS pro_show_childLst$$   CREATE PROCEDURE pro_show_childLst(IN rootId INT)   BEGIN         DROP TEMPORARY TABLE IF EXISTS tmpLst;         CREATE TEMPORARY TABLE IF NOT EXISTS tmpLst           (sno INT PRIMARY KEY AUTO_INCREMENT,id INT,depth INT);                      CALL pro_cre_childlist(rootId,0);                SELECT channel.id,ConCAT(SPACE(tmpLst.depth*2),‘--‘,channel.cname) NAME,channel.parent_id,tmpLst.depth,fn_tree_path(channel.id,‘/‘) path,fn_tree_pathname(channel.id,‘/‘) pathname         FROM tmpLst,channel WHERE tmpLst.id=channel.id ORDER BY tmpLst.sno;        END$$   
2.8,调用过程输出父节点
-- pro_show_parentLstDELIMITER $$-- 调用过程输出父节点   DROP PROCEDURE IF EXISTS `pro_show_parentLst`$$   CREATE PROCEDURE `pro_show_parentLst`(IN rootId INT)   BEGIN         DROP TEMPORARY TABLE IF EXISTS tmpLst;         CREATE TEMPORARY TABLE IF NOT EXISTS tmpLst           (sno INT PRIMARY KEY AUTO_INCREMENT,id INT,depth INT);                      CALL pro_cre_parentlist(rootId,0);         SELECT channel.id,ConCAT(SPACE(tmpLst.depth*2),‘--‘,channel.cname) NAME,channel.parent_id,tmpLst.depth,fn_tree_path(channel.id,‘/‘) path,fn_tree_pathname(channel.id,‘/‘) pathname         FROM tmpLst,channel WHERE tmpLst.id=channel.id ORDER BY tmpLst.sno;        END$$   
3,开始测试:3.1,从根节点开始显示,显示子节点集合:
mysql> CALL pro_show_childLst(-1); +----+-----------------------+-----------+-------+-------------+----------------------------+| id | NAME                  | parent_id | depth | path        | pathname                   |+----+-----------------------+-----------+-------+-------------+----------------------------+| 13 |   --首页              |        -1 |     1 | -1/13       | 首页/                      || 16 |     --左上幻灯片      |        13 |     2 | -1/13/16    | 首页/左上幻灯片/           || 14 |   --TV580             |        -1 |     1 | -1/14       | TV580/                     || 17 |     --帮忙            |        14 |     2 | -1/14/17    | TV580/帮忙/                || 18 |       --栏目简介      |        17 |     3 | -1/14/17/18 | TV580/帮忙/栏目简介/       || 15 |   --生活580           |        -1 |     1 | -1/15       | 生活580/                   |+----+-----------------------+-----------+-------+-------------+----------------------------+6 rows in set (0.05 sec)Query OK, 0 rows affected (0.05 sec)
3.2,显示首页下面的子节点
CALL pro_show_childLst(13);  mysql> CALL pro_show_childLst(13);   +----+---------------------+-----------+-------+----------+-------------------------+| id | NAME                | parent_id | depth | path     | pathname                |+----+---------------------+-----------+-------+----------+-------------------------+| 13 | --首页              |        -1 |     0 | -1/13    | 首页/                   || 16 |   --左上幻灯片      |        13 |     1 | -1/13/16 | 首页/左上幻灯片/        |+----+---------------------+-----------+-------+----------+-------------------------+2 rows in set (0.02 sec)Query OK, 0 rows affected (0.02 sec)mysql> 
3.3,显示TV580下面的所有子节点
CALL pro_show_childLst(14);   mysql> CALL pro_show_childLst(14);  +----+--------------------+-----------+-------+-------------+----------------------------+| id | NAME               | parent_id | depth | path        | pathname                   |+----+--------------------+-----------+-------+-------------+----------------------------+| 14 | --TV580            |        -1 |     0 | -1/14       | TV580/                     || 17 |   --帮忙           |        14 |     1 | -1/14/17    | TV580/帮忙/                || 18 |     --栏目简介     |        17 |     2 | -1/14/17/18 | TV580/帮忙/栏目简介/       |+----+--------------------+-----------+-------+-------------+----------------------------+3 rows in set (0.02 sec)Query OK, 0 rows affected (0.02 sec)mysql> 
3.4,“帮忙”节点有一个子节点,显示出来:
CALL pro_show_childLst(17);   mysql> CALL pro_show_childLst(17); +----+------------------+-----------+-------+-------------+----------------------------+| id | NAME             | parent_id | depth | path        | pathname                   |+----+------------------+-----------+-------+-------------+----------------------------+| 17 | --帮忙           |        14 |     0 | -1/14/17    | TV580/帮忙/                || 18 |   --栏目简介     |        17 |     1 | -1/14/17/18 | TV580/帮忙/栏目简介/       |+----+------------------+-----------+-------+-------------+----------------------------+2 rows in set (0.03 sec)Query OK, 0 rows affected (0.03 sec)mysql> 
3.5,“栏目简介”没有子节点,所以只显示最终节点:
mysql> CALL pro_show_childLst(18);   +----+----------------+-----------+-------+-------------+----------------------------+| id | NAME           | parent_id | depth | path        | pathname                   |+----+----------------+-----------+-------+-------------+----------------------------+| 18 | --栏目简介     |        17 |     0 | -1/14/17/18 | TV580/帮忙/栏目简介/       |+----+----------------+-----------+-------+-------------+----------------------------+1 row in set (0.36 sec)Query OK, 0 rows affected (0.36 sec)mysql> 
3.6,显示根节点的父节点
CALL pro_show_parentLst(-1);   mysql> CALL pro_show_parentLst(-1);Empty set (0.01 sec)Query OK, 0 rows affected (0.01 sec)mysql>
3.7,显示“首页”的父节点
CALL pro_show_parentLst(13);   mysql> CALL pro_show_parentLst(13);   +----+----------+-----------+-------+-------+----------+| id | NAME     | parent_id | depth | path  | pathname |+----+----------+-----------+-------+-------+----------+| 13 | --首页   |        -1 |     0 | -1/13 | 首页/    |+----+----------+-----------+-------+-------+----------+1 row in set (0.02 sec)Query OK, 0 rows affected (0.02 sec)mysql> 
3.8,显示“TV580”的父节点,parent_id为-1
CALL pro_show_parentLst(14);   mysql> CALL pro_show_parentLst(14);   +----+---------+-----------+-------+-------+----------+| id | NAME    | parent_id | depth | path  | pathname |+----+---------+-----------+-------+-------+----------+| 14 | --TV580 |        -1 |     0 | -1/14 | TV580/   |+----+---------+-----------+-------+-------+----------+1 row in set (0.02 sec)Query OK, 0 rows affected (0.02 sec)
3.9,显示“帮忙”节点的父节点
mysql>CALL pro_show_parentLst(17);   mysql> CALL pro_show_parentLst(17);   +----+-----------+-----------+-------+----------+---------------+| id | NAME      | parent_id | depth | path     | pathname      |+----+-----------+-----------+-------+----------+---------------+| 17 | --帮忙    |        14 |     0 | -1/14/17 | TV580/帮忙/   || 14 |   --TV580 |        -1 |     1 | -1/14    | TV580/        |+----+-----------+-----------+-------+----------+---------------+2 rows in set (0.02 sec)Query OK, 0 rows affected (0.02 sec)mysql>
3.10,显示最低层节点“栏目简介”的父节点
CALL pro_show_parentLst(18);  mysql> CALL pro_show_parentLst(18);  +----+----------------+-----------+-------+-------------+----------------------------+| id | NAME           | parent_id | depth | path        | pathname                   |+----+----------------+-----------+-------+-------------+----------------------------+| 18 | --栏目简介     |        17 |     0 | -1/14/17/18 | TV580/帮忙/栏目简介/       || 17 |   --帮忙       |        14 |     1 | -1/14/17    | TV580/帮忙/                || 14 |     --TV580    |        -1 |     2 | -1/14       | TV580/                     |+----+----------------+-----------+-------+-------------+----------------------------+3 rows in set (0.02 sec)Query OK, 0 rows affected (0.02 sec)mysql>