MySQL学习笔记(三):存储过程

一、存储过程概念

  • 什么是存储过程

  • 一组为了完成特定功能的SQL 语句集。
    更加直白的理解:存储过程可以说是一个记录集,它是由一些T-SQL语句组成的代码块,这些T-SQL语句代码像一个方法一样实现一些功能(对单表或多表的增删改查),然后再给这个代码块取一个名字,在用到这个功能的时候调用他就行了。

  • 优点

  • 存储过程是一个预编译的代码块,执行效率比较高

  • 一个存储过程替代大量T_SQL语句 ,可以降低网络通信量,提高通信速率

  • 可以一定程度上确保数据安全(可设定只有某些用户才具有对指定存储过程的使用权)

  • 缺点

  • 如果更改范围大到需要对输入存储过程的参数进行更改,或者要更改由其返回的数据,则您仍需要更新程序集中的代码以添加参数、更新 GetValue() 调用,等等,这时候估计比较繁琐了。

  • 可移植性差

  • 很多存储过程不支持面向对象的设计,无法采用面向对象的方式将业务逻辑进行封装,从而无法形成通用的可支持复用的业务逻辑框架。

  • 代码可读性差,相当难维护。

  • 要不要用存储过程
    个人觉得各组件应该各司其职。MySQL就是存储数据的仓库,逻辑实现还是应该放在业务层。
    建议看看别人说的。为什么要用存储过程


二、存储过程使用

存储过程的创建语法:
DELIMITER //
CREATE PROCEDURE 储存过程名([in|out|inout] 参数 datatype)
BEGIN
SQL语句代码快
END
//
DELIMITER ;

几点说明:

  • 存储过程名字后面的“()”是必须的,即使没有一个参数,也需要“()”
  • begin end 可以在只有一条sql语句的时候省略。
  • 每条语句的末尾,都要加上分号 “;”
  • 不能在存储过程中使用 “return” 关键字。
  • DELIMITER 的意思是,告诉mysql,下面的语句中,语句定界符不再是分号(;),而是双斜线(//)。这里的双斜线可以任意指定,比如指定为|或者///,都是可以的(但不要指定为sql语句中经常出现的逗号或等号等,不然就结束了),如:
mysql> delimiter // 
mysql> create procedure simpleproc (out param1 int) 
    -> begin 
    -> select count(*) into param1 from t; 
    -> end 
    -> //
Query OK, 0 rows affected

mysql> delimiter |
mysql> create procedure simpleproc1 (out param1 int) 
    -> begin 
    -> select count(*) into param1 from t;
    -> end 
    -> |
Query OK, 0 rows affected

mysql> delimiter ///
mysql> create procedure simpleproc2 (out param1 int)
    -> begin 
    -> select count(*) into param1 from t;
    -> end
    -> ///
Query OK, 0 rows affected

还要注意,定义完存储过程之后,要重新将分号(;)指定为语义分隔符。即调用DELIMITER ;

  • 关于参数

  • in表示向存储过程传递参数,out表示从存储过程返回参数,而inout表示传递参数和返回参数;如果不显式指定in、out、inout,则默认为in。习惯上,对于是in的参数,我们都不会显式指定;

  • 参数只能指定参数类型,不能指定长度;

  • 参数不能指定默认值。

  • 关于注释

/*   
这是   
多行 
注释
*/
declare a int; -- 这是单行 MySQL 注释 (注意 -- 后至少要有一个空格)   
if a is null then set a = 0; # 这也是个单行 MySQL 注释   
查看已经创建的存储过程:

show procedure status where Db='数据库名';或者:先use 数据库名,再show procedure status

mysql> show procedure status where Db='cpgl';
+------+--------+-----------+----------------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
| Db   | Name   | Type      | Definer        | Modified            | Created             | Security_type | Comment | character_set_client | collation_connection | Database Collation |
+------+--------+-----------+----------------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
| cpgl | hi     | PROCEDURE | root@localhost | 2017-05-30 11:39:44 | 2017-05-30 11:39:44 | DEFINER       |         | utf8                 | utf8_general_ci      | utf8_general_ci    |
| cpgl | pr_add | PROCEDURE | root@localhost | 2017-05-30 11:41:42 | 2017-05-30 11:41:42 | DEFINER       |         | utf8                 | utf8_general_ci      | utf8_general_ci    |
+------+--------+-----------+----------------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
2 rows in set
删除存储过程

drop procedure '存储过程名字';

mysql> drop procedure hi;
Query OK, 0 rows affected
实例

从网上找了几个例子,真正用到的时候,可以参考:包含了事务,参数,嵌套调用,游标,循环等使用

例子1

drop procedure if exists pro_rep_shadow_rs;   
delimiter |   
----------------------------------   
-- rep_shadow_rs   
-- 用来处理信息的增加,更新和删除   
-- 每次只更新上次以来没有做过的数据   
-- 根据不同的标志位   
-- 需要一个输出的参数,   
-- 如果返回为0,则调用失败,事务回滚   
-- 如果返回为1,调用成功,事务提交   
--   
-- 测试方法   
-- call pro_rep_shadow_rs(@rtn);   
-- select @rtn;   
----------------------------------   
create procedure pro_rep_shadow_rs(out rtn int)   
begin   
    -- 声明变量,所有的声明必须在非声明的语句前面   
    declare iLast_rep_sync_id int default -1;   
    declare iMax_rep_sync_id int default -1;   
    -- 如果出现异常,或自动处理并rollback,但不再通知调用方了   
    -- 如果希望应用获得异常,需要将下面这一句,以及启动事务和提交事务的语句全部去掉   
    declare exit handler for sqlexception rollback;   
    -- 查找上一次的   
    select eid into iLast_rep_sync_id from rep_de_proc_log where tbl='rep_shadow_rs';   
    -- 如果不存在,则增加一行   
    if iLast_rep_sync_id=-1 then   
      insert into rep_de_proc_log(rid,eid,tbl) values(0,0,'rep_shadow_rs');   
      set iLast_rep_sync_id = 0;   
    end if;   
       
    -- 下一个数字   
    set iLast_rep_sync_id=iLast_rep_sync_id+1;   
    -- 设置默认的返回值为0:失败   
    set rtn=0;   
       
    -- 启动事务   
    start transaction;   
    -- 查找最大编号   
    select max(rep_sync_id) into iMax_rep_sync_id from rep_shadow_rs;   
    -- 有新数据   
    if iMax_rep_sync_id>=iLast_rep_sync_id then   
        -- 调用   
        call pro_rep_shadow_rs_do(iLast_rep_sync_id,iMax_rep_sync_id);   
        -- 更新日志   
        update rep_de_proc_log set rid=iLast_rep_sync_id,eid=iMax_rep_sync_id where tbl='rep_shadow_rs';   
    end if;   
       
    -- 运行没有异常,提交事务   
    commit;   
    -- 设置返回值为1  
    set rtn=1;   
end;   
|   
delimiter ;   

例子2

drop procedure if exists pro_rep_shadow_rs_do;   
delimiter |   
---------------------------------   
-- 处理指定编号范围内的数据   
-- 需要输入2个参数   
-- last_rep_sync_id 是编号的最小值   
-- max_rep_sync_id 是编号的最大值   
-- 无返回值   
---------------------------------   
create procedure pro_rep_shadow_rs_do(last_rep_sync_id int, max_rep_sync_id int)   
begin   
    declare iRep_operationtype varchar(1);   
    declare iRep_status varchar(1);   
    declare iRep_Sync_id int;   
    declare iId int;   
    -- 这个用于处理游标到达最后一行的情况   
    declare stop int default 0;   
    -- 声明游标   
    declare cur cursor for select id,Rep_operationtype,iRep_status,rep_sync_id from rep_shadow_rs where rep_sync_id between last_rep_sync_id and max_rep_sync_id;   
    -- 声明游标的异常处理,设置一个终止标记   
    declare CONTINUE HANDLER FOR SQLSTATE '02000' SET stop=1;   
       
    -- 打开游标   
    open cur;   
       
    -- 读取一行数据到变量   
    fetch cur into iId,iRep_operationtype,iRep_status,iRep_Sync_id;   
    -- 这个就是判断是否游标已经到达了最后   
    while stop <> 1 do  
        -- 各种判断   
        if iRep_operationtype='I' then   
            insert into rs0811 (id,fnbm) select id,fnbm from rep_shadow_rs where rep_sync_id=iRep_sync_id;   
        elseif iRep_operationtype='U' then   
        begin   
            if iRep_status='A' then   
                insert into rs0811 (id,fnbm) select id,fnbm from rep_shadow_rs where rep_sync_id=iRep_sync_id;   
            elseif iRep_status='B' then   
                delete from rs0811 where id=iId;   
            end if;   
        end;   
        elseif iRep_operationtype='D' then   
            delete from rs0811 where id=iId;   
        end if;    
           
        -- 读取下一行的数据    
        fetch cur into iId,iRep_operationtype,iRep_status,iRep_Sync_id;   
    end while;  -- 循环结束   
    close cur; -- 关闭游标   
 end;   
|  
delimiter ;

关于游标定义和使用,可以参考:MySql存储过程—游标(Cursor)

  • 游标循环次数不对的问题
    在使用游标的时候,最容易出现的问题就是循环次数与实际记录数不一:第一种就是循环次数比实际记录数多一次;第二种情况就是循环次数远小于记录数。
    第一种情况的发生,是由于对while do结构(或者有时候使用repeat)以及cursor的性质认识不够导致的,cursor会在找不到记录时(一般是循环完了最后一行,也有可能是循环时的select into 赋值语句结果为空导致)发生NOT FOUND,可以根据这一点来判断循环结束。而while do 结构类似于java中的do while 结构,无论怎样都会先do再判断。上面例子2中的写法可以很好的解决这个问题:在每次循环结尾,执行赋值操作,相当于让cursor的判断提前一步。
    第二种情况的发生,一般是select into 赋值语句结果为空(有待研究)。

  • 存储过程如何调试
    我使用的有两种:
    第一种是在call调用存储过程之前,定义全局变量,如SET @test=0;在存储过程的函数体中的某个地方,为该变量赋值,如SET @test=1;在调用完存储过程之后,使用select @test的方式查看之前定义的@test的值。如果存储过程写的没问题,执行到了你写SET @test=1;的地方,则值为1,否则还是初始值0。
    第二种则是,在函数体中,使用select var1,var2...这种方式:var1,var2...是你在其中定义的变量,你想在某处查看它们的值是否与你期望的一样,就在该处写上select var1,var2...语句。这样,在控制台调用存储过程时,就会打印出这样变量的值。
    下面的存储过程可供参考:

CREATE PROCEDURE copy_nfi_item(nfi_id_from varchar(100), nfi_id_to varchar(100))
    COMMENT '复制nfi考核,与业务无关;如果两个部门的nfi考核项相同,可以通过该存储过程快捷复制。'
BEGIN
        
    DECLARE v_item_id varchar(50);
    DECLARE v_percent float(11,0);
    DECLARE v_first_level_duty varchar(255);
    DECLARE v_first_level_duty_weight float(11,0);
    DECLARE v_title varchar(255);
    DECLARE v_task text;
    DECLARE v_task_weight float(11,0);
    DECLARE v_plan_begin_date date;
    DECLARE v_plan_end_date date;
    DECLARE v_complete_date date;
    DECLARE v_results text;
    DECLARE v_scoring_standard text;
    DECLARE v_results_desc text;
    DECLARE v_provide_dept varchar(255);
    DECLARE v_check_dept varchar(255);
    DECLARE v_self_marks float(11,0);
    DECLARE v_self_score float(11,2);
    DECLARE v_dept_marks float(11,0);
    DECLARE v_dept_score float(11,2);
    DECLARE v_nonfinancial_score float(11,2);
    DECLARE v_president_score float(11,2);
    DECLARE v_review_marks float(11,2);
    DECLARE v_review_score float(11,2);
    DECLARE v_advice text;
    DECLARE v_nfi_id varchar(50);
    DECLARE v_executor_id varchar(50);
    DECLARE v_status int(11);
    DECLARE v_step int(11);
    DECLARE v_dr bit(1);
    DECLARE v_ts datetime;
    DECLARE v_improvements text;
    DECLARE v_update_ts varchar(50);

    DECLARE temp_dept_id_from VARCHAR(200);
    DECLARE temp_dept_id_to VARCHAR(200);
    DECLARE v_count int(10) DEFAULT 0;


    -- 这个用于处理游标到达最后一行的情况
    DECLARE stopFlag int default 0;
     
    -- 声明游标:指向数据源  
    DECLARE cur CURSOR FOR SELECT item.item_id,item.percent,item.first_level_duty,item.first_level_duty_weight,item.title,item.task,item.task_weight,item.plan_begin_date,item.plan_end_date,
                item.complete_date,item.results,item.scoring_standard,item.results_desc,item.provide_dept,item.check_dept,item.self_marks,item.self_score,item.dept_marks,item.dept_score,item.review_marks,item.review_score,
                item.advice,item.nfi_id,item.status,item.step,item.dr,item.ts,item.executor_id,item.nonfinancial_score,item.president_score,item.improvements FROM nfi_item item WHERE item.nfi_id = nfi_id_from;

    -- 声明游标的异常处理,设置一个终止标记 
    -- 另一种写法:DECLARE CONTINUE HANDLER FOR NOT FOUND SET stop=1;   
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET stopFlag=1;  


    -- 打开游标   
    OPEN cur;


    /**

    使用游标的一个常见问题就是循环次数不对。这样可以正确。
    mysql的while与java里的类似,会先进入其中,do之后,再判断。
    在循环体的最后使用fetch,是为了让游标提前更进一步,使得stopFlag=1。

    */
-- 读取一行数据到变量   
        FETCH cur INTO v_item_id,v_percent,v_first_level_duty,v_first_level_duty_weight,v_title,v_task,v_task_weight,v_plan_begin_date,v_plan_end_date,v_complete_date,v_results,
                v_scoring_standard,v_results_desc,v_provide_dept,v_check_dept,v_self_marks,v_self_score,v_dept_marks,v_dept_score,v_review_marks,v_review_score,v_advice,v_nfi_id,v_status,v_step,
                v_dr,v_ts,v_executor_id,v_nonfinancial_score,v_president_score,v_improvements;   

    -- 判断是否游标已经到达了最后
    WHILE stopFlag <> 1 DO
            
            SET v_count = v_count + 1;

            -- 测试用,这样的语法会在控制台直接输出变量的值
            SELECT v_item_id,v_percent, temp_dept_id_from, temp_dept_id_to, v_count;

            -- 几个特殊的变量重新赋值:使用SELECT 表中字段名 INTO 变量名的方式 
            SELECT n.dept_id INTO temp_dept_id_from FROM nfi n WHERE n.nfi_id = nfi_id_from;
            SELECT n.dept_id INTO temp_dept_id_to FROM nfi n WHERE n.nfi_id = nfi_id_to;

            IF temp_dept_id_from = v_provide_dept THEN SET v_provide_dept = temp_dept_id_to;
            END IF;

            SET v_item_id = CONCAT(SUBSTR(DATE_FORMAT(NOW(), '%Y%m%d%H%i%s'),3),SUBSTR(UUID(),1,8));
            SET v_nfi_id = nfi_id_to;

            SELECT n.psn_code INTO v_executor_id FROM nfi n WHERE n.nfi_id = nfi_id_to;

            -- 执行插入
            INSERT INTO nfi_item
                    (item_id,percent,first_level_duty,first_level_duty_weight,title,task,task_weight,plan_begin_date,plan_end_date,complete_date,
                    results,scoring_standard,results_desc,provide_dept,check_dept,self_marks,self_score,dept_marks,dept_score,review_marks,review_score,
                    advice,nfi_id,status,step,dr,ts,executor_id,nonfinancial_score,president_score,improvements)
            VALUES(
                    v_item_id,v_percent,v_first_level_duty,v_first_level_duty_weight,v_title,v_task,v_task_weight,v_plan_begin_date,v_plan_end_date,v_complete_date,v_results,
                    v_scoring_standard,v_results_desc,v_provide_dept,v_check_dept,v_self_marks,v_self_score,v_dept_marks,v_dept_score,v_review_marks,v_review_score,v_advice,v_nfi_id,v_status,v_step,
                    v_dr,v_ts,v_executor_id,v_nonfinancial_score,v_president_score,v_improvements);

       -- 读取一行数据到变量   
         FETCH cur INTO v_item_id,v_percent,v_first_level_duty,v_first_level_duty_weight,v_title,v_task,v_task_weight,v_plan_begin_date,v_plan_end_date,v_complete_date,v_results,
                    v_scoring_standard,v_results_desc,v_provide_dept,v_check_dept,v_self_marks,v_self_score,v_dept_marks,v_dept_score,v_review_marks,v_review_score,v_advice,v_nfi_id,v_status,v_step,
                    v_dr,v_ts,v_executor_id,v_nonfinancial_score,v_president_score,v_improvements;
    END WHILE;  -- 循环结束   
    CLOSE cur; -- 关闭游标   
END
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,585评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,283评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,760评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,461评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,280评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,268评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,656评论 3 385
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,322评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,629评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,691评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,445评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,299评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,694评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,982评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,244评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,642评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,829评论 2 335

推荐阅读更多精彩内容