侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

MySQL 实现树的遍历详解及简单实现示例

数据库  /  管理员 发布于 6年前   157

MySQL 实现树的遍历

经常在一个表中有父子关系的两个字段,比如empno与manager,这种结构中需要用到树的遍历。在Oracle 中可以使用connect by简单解决问题,但MySQL 5.1中还不支持(据说已纳入to do中),要自己写过程或函数来实现。

一、建立测试表和数据:

DROP TABLE IF EXISTS `channel`;  CREATE TABLE `channel` (  `id` int(11) NOT NULL AUTO_INCREMENT,   `cname` varchar(200) DEFAULT NULL,  `parent_id` int(11) DEFAULT NULL,  PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;  /*Data for the table `channel` */  insert into `channel`(`id`,`cname`,`parent_id`)  values (13,'首页',-1),     (14,'TV580',-1),     (15,'生活580',-1),     (16,'左上幻灯片',13),     (17,'帮忙',14),     (18,'栏目简介',17); 

 二、利用临时表和递归过程实现树的遍历(MySQL的UDF不能递归调用):

DELIMITER $$  USE `db1`$$   -- 从某节点向下遍历子节点 -- 递归生成临时表数据 DROP PROCEDURE IF EXISTS `createChildLst`$$  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 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 createChildLst(b,nDepth+1);        FETCH cur1 INTO b;    END WHILE;       CLOSE cur1;    END$$   -- 从某节点向上追溯根节点 -- 递归生成临时表数据 DROP PROCEDURE IF EXISTS `createParentLst`$$  CREATE PROCEDURE `createParentLst`(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 createParentLst(b,nDepth+1);        FETCH cur1 INTO b;    END WHILE;       CLOSE cur1;    END$$   -- 实现类似Oracle SYS_CONNECT_BY_PATH的功能 -- 递归过程输出某节点id路径 DROP PROCEDURE IF EXISTS `createPathLst`$$  CREATE PROCEDURE `createPathLst`(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 createPathLst(parentid,delimit,pathstr);        FETCH cur1 INTO parentid,pathstr;    END WHILE;          CLOSE cur1;     END$$   -- 递归过程输出某节点name路径 DROP PROCEDURE IF EXISTS `createPathnameLst`$$  CREATE PROCEDURE `createPathnameLst`(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 createPathnameLst(parentid,delimit,pathstr);        FETCH cur1 INTO parentid,pathstr;    END WHILE;          CLOSE cur1;     END$$   -- 调用函数输出id路径 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 createPathLst(nid,delimit,@pathid);    RETURN @pathid; END$$   -- 调用函数输出name路径 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 createPathnameLst(nid,delimit,@pathid);    RETURN @pathid; END$$   -- 调用过程输出子节点 DROP PROCEDURE IF EXISTS `showChildLst`$$  CREATE PROCEDURE `showChildLst`(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 createChildLst(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$$  -- 调用过程输出父节点 DROP PROCEDURE IF EXISTS `showParentLst`$$  CREATE PROCEDURE `showParentLst`(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 createParentLst(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$$   DELIMITER ; 

三、测试

CALL showChildLst(-1); CALL showChildLst(13); CALL showChildLst(14); CALL showChildLst(17); CALL showChildLst(18);  CALL showParentLst(-1); CALL showParentLst(13); CALL showParentLst(14); CALL showParentLst(17); CALL showParentLst(18); 

四、遗留问题

1. 因为mysql对动态游标的支持不够,所以要想做成通用的过程或函数比较困难,可以利用两个临时表来转换(同时去掉了递归调用)是个相对通用的实现。

2. 目前来看无论哪种实现,效率都不太好,希望mysql自己能实现Oracle 的connect by 功能,应该会比较优化。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!


  • 上一条:
    MySQL 5.7.16 修改密码提示 ERROR 1054 (42S22): Unknown column ''password'' in ''field list''的原因
    下一条:
    MySQL远程连接不上的解决方法
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 分库分表的目的、优缺点及具体实现方式介绍(0个评论)
    • DevDB - 在 VS 代码中直接访问数据库(0个评论)
    • 在ubuntu系统中实现mysql数据存储目录迁移流程步骤(0个评论)
    • 在mysql中使用存储过程批量新增测试数据流程步骤(0个评论)
    • php+mysql数据库批量根据条件快速更新、连表更新sql实现(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2017-06
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-01
    • 2018-05
    • 2018-10
    • 2018-11
    • 2020-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2021-02
    • 2021-04
    • 2021-07
    • 2021-08
    • 2021-11
    • 2021-12
    • 2022-02
    • 2022-03
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-07
    • 2023-08
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-03
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客