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

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 功能,应该会比较优化。

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

(0)

相关推荐

  • MySQL创建用户与授权方法

    注:我的运行环境是widnows xp professional + MySQL5.0 一, 创建用户: 命令:CREATE USER 'username'@'host' IDENTIFIED BY 'password'; 说明:username - 你将创建的用户名, host - 指定该用户在哪个主机上可以登陆,如果是本地用户可用localhost, 如果想让该用户可以从任意远程主机登陆,可以使用通配符%. password - 该用户的登陆密码,密码可以为空,如果为空则该用户可以不需要密码

  • 设置MySQL自动增长从某个指定的数开始方法

    自增字段,一定要设置为primary key. 以指定从1000开始为例. 1 创建表的时候就设置: CREATE TABLE `Test` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `NAME` varchar(50) NOT NULL, `SEX` varchar(2) NOT NULL, PRIMARY KEY (`ID`) ) ENGINE=MEMORY AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 CHECKSU

  • mySQL中replace的用法

    mysql replace实例说明: UPDATE tb1 SET f1=REPLACE(f1, 'abc', 'def'); REPLACE(str,from_str,to_str) 在字符串 str 中所有出现的字符串 from_str 均被 to_str替换,然后返回这个字符串 这个函数用来批量替换数据中的非法关键字是很有用的!如下例子: 例1:UPDATE BBSTopic SET tcontents = replace(replace(tcontents,'共产党','') ,'找死'

  • mysql默认编码为UTF-8 通过修改my.ini实现方法

    mysql汉字乱码的原因 mysql默认的编码是Latin1是I-8859-1的别名,但Latin1是不支持汉字的,所以要将其改为UTF-8或GBK 1.关闭mysql服务器,这个很重要. 2.通过my.ini设置mysql数据库的编码 在mysql数据库的安装根目录下找到my.ini,例:C:\Program Files\MySQL\MySQL Server 5.5 将其复制到桌面,双击打开, 搜索"default-character-set"将其改为utf8, 搜索"ch

  • 如何查看MySQL连接的root密码

    本文给大家分享的是查看MySQL连接的root密码的方法,下面话不多说来来看正文: 1.首先我们进到MySQL的bin目录下 ➜ cd /usr/local/mysql/bin 2.切换成root身份 ➜ bin sudo su 3.跨过权限的验证 sh-3.2# ./mysqld_safe --skip-grant-tables & [1] 9451 sh-3.2# 2017-01-03T15:40:10.6NZ mysqld_safe Logging to '/usr/local/mysql

  • 简单谈谈MySQL5.7 JSON格式检索

    MySQL5.7版本开始支持JSON格式,在创建表时,可以指定列表的数据类型为JSON,但是如何在JSON格式上创建索引呢?? 本人做了一个简单测试. 第一步:建立一个包含JSON类型的表: CREATE TABLE json_test` ( id` int (8) NOT NULL AUTO_INCREMENT, content` json NOT NULL , PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 第二步:初始化数据

  • MySQL日期数据类型、时间类型使用总结

    MySQL 日期类型:日期格式.所占存储空间.日期范围 比较. 日期类型        存储空间       日期格式                 日期范围 ------------ ---------   --------------------- ----------------------------------------- datetime       8 bytes   YYYY-MM-DD HH:MM:SS   1000-01-01 00:00:00 ~ 9999-12-31

  • mysql update语句的用法详解

    首先,单表的UPDATE语句: UPDATE [LOW_PRIORITY] [IGNORE] tbl_name SET col_name1=expr1 [, col_name2=expr2 ...] [WHERE where_definition] [ORDER BY ...] [LIMIT row_count] 其次,多表的UPDATE语句: UPDATE [LOW_PRIORITY] [IGNORE] table_references SET col_name1=expr1 [, col_n

  • Mysql命令行导入sql数据

    我的个人实践是:phpmyadmin 导出 utf-8 的 insert 模式的 abc.sql ftp abc.sql 到服务器 ssh 到服务器 mysql -u abc -p use KKK(数据库名,如果没有就 create database KKK) set names 'utf8' source abc.sql 注意:我看到 set character set utf8; 的说法,那样不行,中文乱码. 1.首先在命令行控制台中打开mysql 或许命令的如下: mysql -u roo

  • Mysql字符串截取函数SUBSTRING的用法说明

    感觉上MySQL的字符串函数截取字符,比用程序截取(如PHP或JAVA)来得强大,所以在这里做一个记录,希望对大家有用. 函数: 1.从左开始截取字符串 left(str, length) 说明:left(被截取字段,截取长度) 例:select left(content,200) as abstract from my_content_t 2.从右开始截取字符串 right(str, length) 说明:right(被截取字段,截取长度) 例:select right(content,200

  • Can't connect to MySQL server on localhost (10061)解决方法

    首先检查MySQL 服务没有启动>如果没有启动,则要启动这个服务. 昨天,重起服务器后出现MySQL 'localhost' (10061)错误,开始以为是因为数据库链接打开过多,数据库资源耗尽的缘故,但是重启服务器以后,仍旧出现问题,于是在网上查找解决方法.大体如下: 解决办法: 第一步 删除c:\windows\下面的my.ini 第二步 打开c:\mysql\bin\winmysqladmin.exe 输入用户名 和密码 第三步 在dos下 输入 mysqld-nt -remove 删除服

  • windows10安装mysql5.7.17教程

    本教程为大家分享了mysql5.7.17安装配置方法,供大家参考,具体内容如下 操作 全允许 精彩专题分享:mysql不同版本安装教程 mysql5.7各版本安装教程 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们.

随机推荐