mysql 网上摘要

mysql 存储过程递归
2011-08-31 16:47
 

在Oracle中可以使用CONNECT  BY子句可以轻松的实现递归查询,在MSSQLServer和DB2中则可以使用WITH子句来实现递归查询,MYSQL中即不支持CONNECT  BY子句也不支持WITH子句,所以要实现递归查询就必须使用其他的方式来变通实现, 而且实现方案也随需求的不同而有差异。

下为mysql 使用程序过程的 递归实例(互联网整理),

1.创建表

DROP TABLE IF EXISTS `item_category`;
CREATE TABLE `item_category` (
`id` int(11) NOT NULL auto_increment,
`catId` int(11) default NULL,
`parentId` int(11) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=REDUNDANT;

2,插入数据

INSERT INTO `item_category` VALUES (1,1,0);
INSERT INTO `item_category` VALUES (2,2,1);
INSERT INTO `item_category` VALUES (3,3,1);
INSERT INTO `item_category` VALUES (4,4,2);
INSERT INTO `item_category` VALUES (5,5,3);

3.创建存储过程

DELIMITER //
drop procedure if exists findLChild//
CREATE PROCEDURE findLChild(iid bigint(20),layer bigint(20))
BEGIN
create temporary table if not exists tmp_table (id bigint(20));
SET@@max_sp_recursion_depth=99;
call iterative(iid,layer);
select * from tmp_table;
drop temporary table if exists tmp_table;
END;//
DELIMITER ;
DELIMITER //
drop procedure if exists iterative//
CREATE PROCEDURE iterative(iid bigint(20),layer bigint(20))
BEGIN
declare tid bigint(20) default -1;
declare cur1 CURSOR FOR select catId from item_category where parentId=iid;
declare CONTINUE HANDLER FOR SQLSTATE '02000' SET tid = null;
if layer > 0 then
OPEN cur1;
FETCH cur1 INTO tid;
WHILE(tid is not null)
DO
insert into tmp_table values(tid);
call iterative(tid,layer-1);
FETCH cur1 INTO tid;
END WHILE;
end if;
END;//
DELIMITER ;

4.调用

call findLChild(1,50);

5 java 调用

用JAVA执行存储过程并取得结果集的代码如下:
Connection con = new Connection();----假设这里已经取得一个有效连接.
ResultSet rs=null;
CallableStatement cstm = con.prepareCall("{CALL testrs(?)}");
cstm.setInt(1,2);----将存储过程的第一个参数填充值2.
boolean bl = cstm.execute();-----执行存储过程
while(bl){----若存储过程成功执行了,并且有结果集返回了,那么bl就是true
rs=cstm.getResultSet()----取得一个结果集,
while(rs.next()){
System.out.println(rs.getInt(1));--- 随便输出一个值。
}
bl=cstm.getMoreResultSet();----若还能取得结果集,则bl=true,这样就回到循环首部,继续取得结果集进行处理,若不再有结果集了,则bl=false。结果循环。
这样,只要三次循环,这个存储过程的三个结果集就可以被分别渠道并处理了。

 

树形结构的表如下:DROP TABLE IF EXISTS `survey`.`tree_test`;CREATE TABLE `survey`.`tree_test` ( `form_id` varchar(32) CHARACTER SET utf8 NOT NULL, `parent_form_id` varchar(32) CHARACTER SET utf8 DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=latin1;INSERT INTO tree_test(form_id,parent_form_id) VALUES('001','000');INSERT INTO tree_test(form_id,parent_form_id) VALUES('002','001');INSERT INTO tree_test(form_id,parent_form_id) VALUES('003','001');INSERT INTO tree_test(form_id,parent_form_id) VALUES('004','003');INSERT INTO tree_test(form_id,parent_form_id) VALUES('005','002');INSERT INTO tree_test(form_id,parent_form_id) VALUES('006','002');保存中间结果的临时表如下:CREATE TEMPORARY TABLE IF NOT EXISTS treeRecurisiveTest_tmp( form_id VARCHAR(32), parent_form_id VARCHAR(32) ) ENGINE=INNODB DEFAULT CHARSET=utf8;存储过程如下:DELIMITER $$USE `survey`$$DROP PROCEDURE IF EXISTS `treeRecurisiveTest`$$CREATE DEFINER=`root`@`localhost` PROCEDURE `treeRecurisiveTest`(IN pid VARCHAR(32))BEGIN DECLARE v_done1 INT DEFAULT 0; DECLARE v_form_id VARCHAR(32); DECLARE v_parent_form_id VARCHAR(32); DECLARE cur1 CURSOR FOR SELECT t.form_id,t.parent_form_id FROM tree_test AS t WHERE t.parent_form_id = pid; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET v_done1 = 1; SET max_sp_recursion_depth = 12; OPEN cur1; FETCH cur1 INTO v_form_id,v_parent_form_id; WHILE v_done1 <> 1 DO INSERT INTO treeRecurisiveTest_tmp(form_id,parent_form_id) VALUES(v_form_id,v_parent_form_id); CALL `survey`.`treeRecurisiveTest`(v_form_id); FETCH cur1 INTO v_form_id,v_parent_form_id; END WHILE; CLOSE cur1; SELECT * FROM treeRecurisiveTest_tmp; END$$DELIMITER ;

1、调用存储过程,传入顶级节点的FORM_ID 

TRUNCATE TABLE treeRecurisiveTest_tmp;
CALL treeRecurisiveTest('001');

2、查看存储过程的返回结果
  form_id form_name
  002 001
  005 002

3、存储过程调用后,直接用SELECT * FROM treeRecurisiveTest_tmp; 返回的结果。
  form_id form_name
  002 001
  005 002
  006 002
  003 001
  004 003

请教大家:为什么第2步中,存储过程直接返回的结果只有2条记录呢?
谢谢帮忙!

当工作在很大的表上时,您可能偶尔需要运行很多查询获得一个大量数据的小的子集,不是对整个表运行这些查询,而是让MySQL每次找出所需的少数记录,将记录选择到一个临时表可能更快些,然后多这些表运行查询。

  创建临时表很容易,给正常的CREATE TABLE语句加上TEMPORARY关键字:

  CREATE TEMPORARY TABLE tmp_table (

  name VARCHAR(10) NOT NULL,

  value INTEGER NOT NULL

  )

  临时表将在您连接MySQL期间存在。当您断开时,MySQL将自动删除表并释放所用的空间。当然您能够在仍然连接的时候删除表并释放空间。

  DROP TABLE tmp_table

  假如在您创建名为tmp_table临时表时名为tmp_table的表在数据库中已存在,临时表将有必要屏蔽(隐藏)非临时表tmp_table。

  假如您声明临时表是个HEAP表,MySQL也允许您指定在内存中创建他:

  CREATE TEMPORARY TABLE tmp_table (

  name VARCHAR(10) NOT NULL,

  value INTEGER NOT NULL

  ) TYPE = HEAP

  因为HEAP表存储在内存中,您对他运行的查询可能比磁盘上的临时表快些。然而,HEAP表和一般的表有些不同,且有自身的限制。详见MySQL参考手册。

  正如前面的建议,您应该测试临时表看看他们是否真的比对大量数据库运行查询快。假如数据很好地索引,临时表可能一点不快。

delimiter ||
create procedure sp_test1(
IN pageno int, IN pagesize int,
OUT pagecount int
)
BEGIN
declare idlower bigint;
declare idupper bigint;
declare totalreccount int;

drop table if exists tmp_table21;
CREATE TEMPORARY TABLE tmp_table21(   
rowid bigint auto_increment primary key,
userid bigint
);


insert into tmp_table21 (userid) select ID from restcomments;
set idlower=(pageno-1)*pagesize+1;
set idupper=pageno*pagesize;
select * from tmp_table21;

/*计算总页数*/
select count(*) from tmp_table21 into totalreccount;
set pagecount=totalreccount;
end||
delimiter ;
注意:

引言:某客户新上线一个项目,利用存储过程处理用户登录相关事务。在存储过程中,需要对用户数据进行处理,于是他们采用临时表(temporary table)来做这个动作,先创建一个临时表,然后插入数据,处理;由于是采用连接池方式,担心临时表被复用,于是在最后删除该临时表。该客户采用16G的2950机器做mysql db server,利用loadrunner进行模拟登录测试,发现并发量达到2,30万之后,就再也上不去了,而且峰值不是很稳定的处于30多万的级别上。
一开始以为是机器性能达到了极限,经过询问各种状况后,认为应该还可以得到改进和优化。经过现场分析后,发现在测试达到峰值时,会有大量的 "waiting for table",以及大量的 create temporary table 和 drop table 的线程在等待。很明显,瓶颈在于频繁的创建和删除临时表,mysql需要频繁的处理打开和关闭表描述符,才会导致了上面的问题。还好他们采用了连接池,否则情况将会更糟糕。建议他们把最后的 drop table 改成 truncate table,把临时表清空了,也就不会担心下一次调用时临时表不为空了,省去了频繁的处理表文件描述符,并发用户数也稳定的保持在了40多万。

 

Sql代码 复制代码 收藏代码

  1. label_proc:   
  2. begin  
  3.     declare v_category_inner_code int;   
  4.     declare v_rootid int default null;   
  5.     declare v_tempid int default null;   
  6.     declare v_listid varchar(1000) default 'aaa';   
  7.     set v_category_inner_code = p_category_inner_code;   
  8.     while (v_category_inner_code is  not null)   
  9.     do   
  10.     begin  
  11.         if  exists (select *   from pu_productcategory where category_inner_code = v_category_inner_code) then  
  12.             select category_inner_code,up_inner_code into v_tempid,v_category_inner_code  from pu_productcategory where category_inner_code = v_category_inner_code;   
  13.         else  
  14.             select v_category_inner_code;   
  15.             set v_category_inner_code = null;   
  16.         end if;   
  17.   
  18.     end;   
  19.     end while;   
  20.     select * from pu_productcategory where category_inner_code = v_category_inner_code;   
  21. end label_proc  
label_proc:
begin
	declare v_category_inner_code int;
	declare v_rootid int default null;
	declare v_tempid int default null;
	declare v_listid varchar(1000) default 'aaa';
	set v_category_inner_code = p_category_inner_code;
	while (v_category_inner_code is  not null)
	do
	begin
		if  exists (select *   from pu_productcategory where category_inner_code = v_category_inner_code) then
			select category_inner_code,up_inner_code into v_tempid,v_category_inner_code  from pu_productcategory where category_inner_code = v_category_inner_code;
		else
			select v_category_inner_code;
			set v_category_inner_code = null;
		end if;

	end;
	end while;
	select * from pu_productcategory where category_inner_code = v_category_inner_code;
end label_proc
Sql代码 复制代码  收藏代码
  1. 如上代码:mySQL存储过程 需传入一个参数p_category_inner_code int,数据表结构主要就一个category_inner_code分类编号 和上up_inner_code上级分类编号,我想根据p_category_inner_code 传入的参数递归查询  
如上代码:mySQL存储过程 需传入一个参数p_category_inner_code int,数据表结构主要就一个category_inner_code分类编号 和上up_inner_code上级分类编号,我想根据p_category_inner_code 传入的参数递归查询
Sql代码 复制代码  收藏代码
  1. 出该分类的最根级分类,就一个while简单的循环不错那里了,就是没结果出来,好象变成死循环了,我感觉我做的没错啊,希望高手来解决,本人mysql新手。  
出该分类的最根级分类,就一个while简单的循环不错那里了,就是没结果出来,好象变成死循环了,我感觉我做的没错啊,希望高手来解决,本人mysql新手。

 

 呵呵,自己解决了,错在这步:select category_inner_code,up_inner_code into v_tempid,v_category_inner_code  from pu_productcategory where category_inner_code = v_category_inner_code;原本是想根据v_category_inner_code来取记录并把up_inner_code 给v_category_inner_code赋值一步完成,下次递归循环查询的就是ID为这条记录的上级分类,但是MYSQL这里却不能很好的处理这个v_category_inner_code作为条件又给它赋值的操作,语法是没错误的,但是结果就是不出来,运行时还提示的结果超过多行错误!

 

Mysql存储过程优化——使用临时表代替游标

Mysql游标在操作小数据量时比较方便,效率可观,但操作大数据量,速度比较慢,甚至直接产生系统错误。

 

一般说来,当操作的数据超过1万条时,就避免用游标吧。

 

为了测试游标性能,写了下面一个游标对IDC_Gather_Info表中数据进行遍历

Sql代码 复制代码  收藏代码
  1. CREATE DEFINER=`root`@`%` PROCEDURE `debug`(IN `beginTime` intIN `checkTime` int)   
  2. BEGIN  
  3.     DECLARE t_id VARCHAR(64) DEFAULT '';   
  4.     DECLARE t_item TINYINT DEFAULT 0;   
  5.     DECLARE t_result VARCHAR(8192) DEFAULT '';   
  6.   
  7.     DECLARE cursorDone INT DEFAULT 0;   
  8.     DECLARE cur CURSOR FOR SELECT Asset_Id, Check_Item, Check_Result from IDC_Gather_Info WHERE Check_Time > beginTime AND Check_Time <= checkTime;   
  9.     DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET cursorDone = 1;   
  10.   
  11.     OPEN cur;   
  12.     cursorLoop:LOOP   
  13.         FETCH cur INTO t_id, t_item, t_result;   
  14.         IF cursorDone = 1 THEN  
  15.             LEAVE cursorLoop;   
  16.         END IF;   
  17.     END LOOP;   
  18.     CLOSE cur;   
  19. END  
CREATE DEFINER=`root`@`%` PROCEDURE `debug`(IN `beginTime` int, IN `checkTime` int)
BEGIN
	DECLARE t_id VARCHAR(64) DEFAULT '';
	DECLARE t_item TINYINT DEFAULT 0;
	DECLARE t_result VARCHAR(8192) DEFAULT '';

	DECLARE cursorDone INT DEFAULT 0;
	DECLARE cur CURSOR FOR SELECT Asset_Id, Check_Item, Check_Result from IDC_Gather_Info WHERE Check_Time > beginTime AND Check_Time <= checkTime;
	DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET cursorDone = 1;

	OPEN cur;
	cursorLoop:LOOP
		FETCH cur INTO t_id, t_item, t_result;
		IF cursorDone = 1 THEN
			LEAVE cursorLoop;
		END IF;
	END LOOP;
	CLOSE cur;
END

下面是当表中数据分别为15万、5万、1万时游标的表现:

 

1.数据量15万,存储过程执行失败,提示错误:Incorrect key file for table '/tmp/#sql_3044_0.MYI';try to repair it

2.数据量5万,执行成功,耗时31.051s

3.数据量1万,执行成功,耗时1.371s

 

下面使用临时表替换游标:

Sql代码 复制代码  收藏代码
  1. CREATE DEFINER=`root`@`%` PROCEDURE `debug`(IN `beginTime` intIN `checkTime` int)   
  2. BEGIN  
  3.     DECLARE t_id VARCHAR(64) DEFAULT '';   
  4.     DECLARE t_item TINYINT DEFAULT 0;   
  5.     DECLARE t_result VARCHAR(8192) DEFAULT '';   
  6.        
  7.     DECLARE maxCnt INT DEFAULT 0;   
  8.     DECLARE i INT DEFAULT 0;   
  9.   
  10.     DROP TABLE IF EXISTS Gather_Data_Tmp;   
  11.     CREATE TEMPORARY TABLE Gather_Data_Tmp(   
  12.         `Tmp_Id` INT UNSIGNED NOT NULL AUTO_INCREMENT,   
  13.         `Asset_Id` VARCHAR(16) NOT NULL,   
  14.         `Check_Item` TINYINT(1) NOT NULL,   
  15.         `Check_Result` VARCHAR(8192) NOT NULL,   
  16.         PRIMARY KEY (`Tmp_Id`)   
  17.     )ENGINE=MyISAM DEFAULT CHARSET=utf8;   
  18.   
  19.     SET @tSql = CONCAT('INSERT INTO Gather_Data_Tmp (`Asset_Id`, `Check_Item`, `Check_Result`)    
  20.                                             SELECT Asset_Id, Check_Item, Check_Result    
  21.                                             FROM IDC_Gather_Info    
  22.                                             WHERE Check_Time > ',beginTime,' AND Check_Time <= ',checkTime);   
  23.     PREPARE gatherData FROM @tSql;   
  24.     EXECUTE gatherData;   
  25.   
  26.     SELECT MIN(`Tmp_Id`) INTO i FROM Gather_Data_Tmp;   
  27.     SELECT MAX(`Tmp_Id`) INTO maxCnt FROM Gather_Data_Tmp;   
  28.   
  29.     WHILE i <= maxCnt DO   
  30.         SELECT Asset_Id, Check_Item, Check_Result INTO t_id, t_item, t_result FROM Gather_Data_Tmp WHERE Tmp_Id = i;   
  31.         SET i = i + 1;   
  32.     END WHILE;   
  33. END  
CREATE DEFINER=`root`@`%` PROCEDURE `debug`(IN `beginTime` int, IN `checkTime` int)
BEGIN
	DECLARE t_id VARCHAR(64) DEFAULT '';
	DECLARE t_item TINYINT DEFAULT 0;
	DECLARE t_result VARCHAR(8192) DEFAULT '';
	
	DECLARE maxCnt INT DEFAULT 0;
	DECLARE i INT DEFAULT 0;

	DROP TABLE IF EXISTS Gather_Data_Tmp;
	CREATE TEMPORARY TABLE Gather_Data_Tmp(
		`Tmp_Id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
		`Asset_Id` VARCHAR(16) NOT NULL,
		`Check_Item` TINYINT(1) NOT NULL,
		`Check_Result` VARCHAR(8192) NOT NULL,
		PRIMARY KEY (`Tmp_Id`)
	)ENGINE=MyISAM DEFAULT CHARSET=utf8;

	SET @tSql = CONCAT('INSERT INTO Gather_Data_Tmp (`Asset_Id`, `Check_Item`, `Check_Result`) 
											SELECT Asset_Id, Check_Item, Check_Result 
											FROM IDC_Gather_Info 
											WHERE Check_Time > ',beginTime,' AND Check_Time <= ',checkTime);
	PREPARE gatherData FROM @tSql;
	EXECUTE gatherData;

	SELECT MIN(`Tmp_Id`) INTO i FROM Gather_Data_Tmp;
	SELECT MAX(`Tmp_Id`) INTO maxCnt FROM Gather_Data_Tmp;

	WHILE i <= maxCnt DO
		SELECT Asset_Id, Check_Item, Check_Result INTO t_id, t_item, t_result FROM Gather_Data_Tmp WHERE Tmp_Id = i;
		SET i = i + 1;
	END WHILE;
END
 

1.数据量15万,执行成功,耗时8.928s

2.数据量5万,执行成功,耗时2.994s

3.数据量1万,执行成功,耗时0.634s

 

可以看到Mysql的游标在处理大一点的数据量时还是比较乏力的,仅适合用于操作几百上千的小数据量。

 

MySQL使用递归存储过程实现树状结构 

  • 感谢作者:佚名 来源:http://www.zxbc.cn/ 日期:2008-12-22 影响数:332 好评: 0  差评:0
  • 分类:MySQL      关键字:
     
    1 创建一个数据表
    查看复制到剪切板打印
    CREATE TABLE `tree` (
    `id` int(10) NOT NULL,
    `fid` int(10) NOT NULL
    );

    id 是编号
    fid 是上级编号, 顶层是0或者你自己定义

    2 创建临时表
    create temporary table if not exists tmp_table(id bigint(20),fid bigint(20),lvl int)//

    3 编写存储过程
    查看复制到剪切板打印
    DELIMITER //
    drop procedure if exists useCursor //
    -- 建立存储过程
    -- JAVA世纪网 www.java2000.net 老紫竹
    CREATE PROCEDURE useCursor(iid bigint(20),lvl int)
    BEGIN
    -- 局部变量定义
    declare tid bigint(20) default -1 ;
    declare tfid bigint(20) default -1 ;

    -- 游标定义
    declare cur1 CURSOR FOR select id,fid from tree where fid=iid ;

    -- 游标介绍定义
    declare CONTINUE HANDLER FOR SQLSTATE '02000' SET tid = null,tfid=null;

    SET @@max_sp_recursion_depth = 10;

    -- 开游标
    OPEN cur1;
    FETCH cur1 INTO tid,tfid;

    WHILE ( tid is not null )
    DO
    insert into tmp_table values(tid,tfid,lvl);
    -- 树形结构数据递归收集到建立的临时表中
    call useCursor(tid,lvl+1);
    FETCH cur1 INTO tid,tfid ;
    END WHILE;
    END;//
    DELIMITER ;



    4 调用方式,并删除临时表

    call useCursor(2,0);
    select * from tmp_table ;

    5 删除临时表
    drop temporary table if exists tmp_table ;

     

 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值