MySQL进阶篇之存储过程(procedure)
作者:mmseoamin日期:2024-01-19

04、视图/存储过程/触发器

4.1、视图(view)

4.2、存储过程(procedure)

4.2.1、介绍

1、介绍

存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。

存储过程思想上很简单,就是数据库SQL语言层面的代码封装与重用。

2、特点

  • 封装、复用
  • 可以接收参数,也可以返回数据
  • 减少网络交互,效率提升
    4.2.2、基本语法

    1、基本语法

    • 创建

      CREATE PROCEDURE 存储过程名称([参数列表])
      BEGIN
         -- SQL语句
      END;
      
    • 调用

      CALL 存储过程名称([参数]);
      
    • 查看

      -- 查询指定数据库的存储过程及状态信息
      SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = '数据库名称';
      -- 查询整个存储过程的定义
      SHOW CREATE PROCEDURE 存储过程名称;
      
    • 删除

      DROP PROCEDURE [IF EXISTS] 存储过程名称;
      

      2、演示:

      -- 存储过程基本语法
      -- 创建
      create procedure p1()
      begin
        select count(*) from student;
      end$$
      -- 调用
      call p1();
      -- 查看
      select * from information_schema.ROUTINES where ROUTINE_SCHEMA = 'itcast';
      show create procedure p1;
      -- 删除
      drop procedure if exists p1;
      

      注意:

      在命令行中,执行创建存储过程的SQL时,需要通过关键字delimiter指定SQL语句的结束符。

      MySQL进阶篇之存储过程(procedure),在这里插入图片描述,第1张

      4.2.3、变量
      4.2.3.1、系统变量

      1、介绍

      系统变量时MySQL服务器提供,不是用户定义的,属于服务器层面。

      可分为:全局变量(GLOBAL)、会话变量(SESSION)。

      全局变量在所有会话中有效,会话变量仅在当前会话中有效。

      2、语法:

      • 查看系统变量

        -- 查看所有系统变量
        SHOW [SESSION | GLOBAL] VARIABLES;
        -- 可以通过like模糊匹配方式查找变量
        SHOW [SESSION | GLOBAL] VARIABLES LIKE '...';
        -- 查看指定变量的值
        SELECT @@[SESSION. | GLOBAL.] 系统变量名;
        
      • 设置系统变量

        SET [SESSION | GLOBAL] 系统变量名 = 值;
        SET @@[SESSION. | GLOBAL.]系统变量名 = 值;
        

        3、演示:

        -- 变量:系统变量
        -- 查看系统变量
        show session variables;
        show session variables like 'auto%';
        show global variables like 'auto%';
        select @@global.autocommit;
        -- 设置系统变量
        set session autocommit = 1;
        set global autocommit = 1;
        set @@global.autocommit = 1;
        

        注意:

        • 如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量。
        • mysql服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在/etc/my.cnf中设置。
        4.2.3.2、用户定义变量

        1、介绍

        用户定义变量是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用“@变量名”使用就可以。其作用域为当前连接(会话)。

        2、语法:

        • 赋值

          SET @var_name = expr[, @var_name = expr] ...;
          SET @var_name := expr[, @var_name := expr] ...;
          SELECT @var_name := expr[, @var_name := expr] ...;
          SELECT 字段名 INTO @var_name FROM 表名;
          
        • 使用

          SELECT @var_name;
          

          3、演示:

          -- 变量:用户定义变量
          -- 赋值
          set @myname = 'itcast';
          set @myage := 10;
          set @mygender := '女', @myhobby := 'mysql';
          select @mycolor := 'red';
          select count(*) into @mycount from tb_user;
          -- 使用
          select @myname, @myage, @mygender;
          select @mycolor, @mycount;
          

          注意:

          用户定义的变量无需对其进行声明或初始化,只不过获取的值为NULL。

          MySQL进阶篇之存储过程(procedure),在这里插入图片描述,第2张

          4.2.3.3、局部变量

          1、介绍

          局部变量是根据需要定义的在局部生效的变量,访问之前,需要DECLARE声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的BEGIN ... END块。

          2、语法

          • 声明

            DECLARE 变量名 变量类型 [DEFAULT ...];
            

            变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCAHR、DATE、TIME等。

          • 赋值

            SET 变量名 = 值;
            SET 变量名 := 值;
            SELECT 字段名 INTO 变量名 FROM 表名 ...;
            

            3、演示:

            -- 变量:局部变量
            create procedure p2()
            begin
              -- 声明 -- declare
              declare stu_count int default 0;
            	
            	-- 赋值
            	-- set stu_count := 100;
            	select count(*) into stu_count from student;
            	
            	select stu_count;
            end;
            call p2();
            
            4.2.4、if判断

            1、语法:

            IF 条件1 THEN
              ...
            ELSEIF 条件2 THEN    -- 可选
              ...
            ELSE                -- 可选
              ...
            END IF;
            

            2、定义存储过程,完成如下需求:

            根据定义的分数score变量,判断当前分数对应的分数等级。

            • score >= 85分,等级为优秀;
            • score >= 60分,且score < 85分,等级为及格;
            • score < 60分,等级为不及格。
              -- 创建存储过程
              create procedure grade_score() 
              begin
                declare score int default 58;
                declare grade varchar(10);
                
                if score >= 85 then
                   set grade := '优秀';
                elseif score >= 60 then
                   set grade := '及格';
                else
                   set grade := '不及格';
                end if;
                
                select grade;
              end;
              -- 调用
              call grade_score();
              
              4.2.5、参数(IN/OUT/INOUT)

              1、参数

              类型含义备注
              IN该类参数作为输入,也就是需要调用时传入值默认
              OUT该类参数作为输出,也就是该参数可以作为返回值
              INOUT既可以作为输入参数,也可以作为输出参数

              2、语法

              CREATE PROCEDURE 存储过程名称([IN/OUT/INOUT 参数名 参数类型])
              BEGIN
                -- SQL语句
              END;
              

              3、定义存储过程,完成如下需求:

              ① 根据传入参数score,判断当前分数对应的分数等级,并返回。

              • score >= 85分,等级为优秀;
              • score >= 60分,且score < 85分,等级为及格;
              • score < 60分,等级为不及格。
                -- 创建存储过程
                create procedure grade_core_2(in score int, out grade varchar(10))
                begin
                  if score >= 85 then
                     set grade := '优秀';
                  elseif score >= 60 then
                     set grade := '及格';
                  else
                     set grade := '不及格';
                  end if;
                  
                  select grade;
                end;
                -- 调用 
                call grade_core_2(18, @result);
                select @result; 
                

                ② 将传入的200分制的分数,换算成百分制,然后返回分数。

                -- 创建存储过程
                create procedure p3(inout score double) 
                begin
                  set score := score / 2;
                end;
                -- 调用
                set @score := 78;  -- 先给变量赋值
                call p3(@score);   -- 调用存储过程,将78传入,并将结果输出赋值给score
                select @score;     -- 查询展示结果
                
                4.2.6、case

                1、语法

                • 语法一:

                  CASE case_value
                       WHEN when_value1 THEN statement_list1
                       [WHEN when_value2 THEN statement_list2]...
                       [ELSE statement_list]
                  END CASE;
                  
                • 语法二:

                  CASE
                       WHEN search_condition1 WHEN statement_list1
                       [WHEN search_condition2 WHEN statement_list2]...
                       [ELSE statement_list]
                  END CASE;
                  

                  2、定义存储过程,完成如下需求:

                  根据传入的月份,判断月份所属的季度(要求采用case结构)

                  • 1-3月份,为第一季度
                  • 4-6月份,为第二季度
                  • 7-9月份,为第三季度
                  • 10-12月份,为第四季度
                    -- 创建存储过程
                    create procedure p6(in month int)
                    begin
                      declare result varchar(10);
                      
                      case
                        when month >= 1 and month <= 3 then
                          set result := '第一季度';
                        when month >= 4 and month <= 6 then
                          set result := '第二季度';
                        when month >= 7 and month <= 9 then
                          set result := '第三季度';
                        when month >= 10 and month <= 12 then
                          set result := '第四季度';
                        else 
                          set result := '非法传输';
                      end case;
                      
                      select concat('您输入的月份为:',month,",所属的季度为:",result);
                    end;
                    -- 调用
                    call p6(8);
                    
                    4.2.7、循环
                    4.2.7.1、while

                    1、while循环是有条件的循环控制语句。满足条件后,再执行循环体中的SQL语句。具体语法为:

                    # 先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
                    WHILE 条件 DO
                       SQL逻辑...
                    END WHILE;
                    

                    2、定义存储过程,完成如下需求

                    计算从1累加到n的值,n为传入的参数值。

                    -- A.定义局部变量,记录累加之后的值
                    -- B.每循环一次,就会对n进行减1,如果n减到0,则退出循环
                    create procedure p7(in n int) 
                    begin
                      declare total int default 0;
                      
                      while n > 0 do 
                         set total := total + n;
                         set n := n - 1;
                      end while;
                    	
                    	select total;
                    end;
                    -- 调用
                    call p7(100);
                    
                    4.2.7.2、repeat

                    1、repeat是有条件的循环控制语句,当满足条件的时候退出循环。具体语法为:

                    # 先执行一次逻辑,然后判定逻辑是否满足,如果满足,则退出。如果不满足,则继续下一次循环
                    REPEAT 
                       SQL逻辑...
                    UNTIL 条件
                    END REPEAT;
                    

                    2、定义存储过程,完成如下需求

                    计算从1累加到n的值,n为传入的参数值。

                    -- A.定义局部变量,记录累加之后的值
                    -- B.每循环一次,就会对n进行减1,如果n减到0,则退出循环
                    create procedure p8(in n int) 
                    begin
                      declare total int default 0;
                      
                      repeat 
                         set total := total + n;
                         set n := n - 1;
                      until n <= 0
                      end repeat;
                    	
                    	select total;
                    end;
                    -- 调用
                    call p8(100);
                    
                    4.2.7.3、loop

                    1、LOOP实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。

                    LOOP可以配合以下两个语句使用:

                    • LEAVE:配合循环使用,退出循环。
                    • ITERATE:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。

                      2、语法:

                      [begin_label:] LOOP
                         SQL逻辑...
                      END LOOP [end_label];
                      
                      -- 退出指定标记的循环体
                      LEAVE label;
                      -- 直接进入下一次循环
                      ITERATE label;
                      

                      3、定义存储过程,完成如下需求

                      ① 计算从1累加到n的值,n为传入的参数值。

                      -- loop
                      -- A.定义局部变量,记录累加之后的值
                      -- B.每循环一次,就会对n进行减1,如果n减到0,则退出循环
                      create procedure p9(in n int)
                      begin
                         declare total int default 0;
                        
                         sum:loop
                      	   if n <= 0 then 
                      		    leave sum;
                      	   end if;
                      		 
                      	   set total := total + n;
                             set n := n - 1;
                      	end loop sum;
                      	
                      	select total;
                      end;
                      -- 调用
                      call p9(100);
                      

                      ② 计算从1到n之间的偶数累加的值,n为传入的参数值。

                      -- A. 定义局部变量, 记录累加之后的值;
                      -- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环 ----> leave xx
                      -- C. 如果当次累加的数据是奇数, 则直接进入下一次循环. --------> iterate xx
                       
                      create procedure p10(in n int)
                      begin
                          declare total int default 0;
                       
                          sum:loop
                              if n<=0 then
                                  leave sum;
                              end if;
                       
                              if n%2 = 1 then
                                  set n := n - 1;
                                  iterate sum;
                              end if;
                       
                              set total := total + n;
                              set n := n - 1;
                          end loop sum;
                       
                          select total;
                      end;
                       
                      -- 调用
                      call p10(100);
                      
                      4.2.8、游标

                      1、介绍

                      游标(cursor)是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。

                      2、游标的使用包括游标的声明、OPEN、FETCH和CLOSE,其语法分别如下:

                      • 声明游标

                        DECLARE 游标名称 CURSOR FOR 查询语句;
                        
                      • 打开游标

                        OPEN 游标名称;
                        
                      • 获取游标记录

                        FETCH 游标名称 INTO 变量[,变量];
                        
                      • 关闭游标

                        CLOSE 游标名称;
                        

                        3、定义存储过程,完成如下需求

                        根据传入的参数uage,来查询用户表tb_user中,所有用户年龄小于等于uage的用户姓名(name)和专业(profession),并将用户的姓名和专业插入到所创建的一张新表(id,name,profession)中。

                        -- 逻辑实现
                        -- A.声明游标,存储查询结果集
                        -- B.准备:创建表结构
                        -- C.开启游标
                        -- D.循环获取游标中的记录
                        -- E.插入数据到新表中
                        -- F.关闭游标
                        create procedure p11(in uage int)
                        begin
                          declare uname varchar(100);
                        	declare uprofession varchar(100);
                        	declare u_cursor cursor for select name,profession from tb_user where age <= uage;
                        	
                        	drop table if exists tb_user_pro;
                        	create table if not exists tb_user_pro(
                        			id int primary key auto_increment,
                        			name varchar(100),
                        			profession varchar(100)
                        	);
                        	
                        	open u_cursor;
                        	
                        	while true do
                        			fetch u_cursor into uname, uprofession;
                        			insert into tb_user_pro values(null, uname, uprofession);
                        	end while;
                        	
                        	close u_cursor;
                        end;
                        -- 调用
                        call p11(40);
                        

                        注意:声明变量在声明游标之前

                        调用存储过程p11(),报错:

                        MySQL进阶篇之存储过程(procedure),在这里插入图片描述,第3张

                        上述的功能,虽然我们实现了,但是逻辑并不完善,而且程序执行完毕,获取不到数据,数据库还报错。 接下来,我们就需要来完成这个存储过程,并且解决这个问题。

                        要想解决这个问题,就需要通过MySQL中提供的条件处理程序Handler来解决。

                        4.2.9、条件处理程序(handler)

                        1、介绍

                        条件处理程序(Handler)可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。

                        2、语法:

                        DECLARE handler_action HANDLER FOR condition_value[,condition_value]... statement;
                        
                        • handler_action
                          • CONTINUE:继续执行当前程序
                          • EXIT:终止执行当前程序
                          • condition_value
                            • SQLSTATE sqlstate_value:状态码,如02000
                              • SQLWARNING:所有以01开头的SQLSTATE代码的简写
                              • NOT FOUND:所有以02开头的SQLSTATE代码的简写
                              • SQLEXCEPTION:所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE代码的简写

                                状态码可查看:https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html

                                3、案例

                                解决上一节的问题:

                                create procedure p11(in uage int)
                                begin
                                  declare uname varchar(100);
                                	declare uprofession varchar(100);
                                	declare u_cursor cursor for select name,profession from tb_user where age <= uage;
                                	-- declare exit handler for SQLSTATE '02000' close u_cursor;
                                	declare exit handler for not found close u_cursor;
                                	
                                	drop table if exists tb_user_pro;
                                	create table if not exists tb_user_pro(
                                			id int primary key auto_increment,
                                			name varchar(100),
                                			profession varchar(100)
                                	);
                                	
                                	open u_cursor;
                                	
                                	while true do
                                			fetch u_cursor into uname, uprofession;
                                			insert into tb_user_pro values(null, uname, uprofession);
                                	end while;
                                	
                                	close u_cursor;
                                end;
                                call p11(40);
                                
                                4.2.10、存储函数

                                1、介绍

                                存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的。

                                2、语法:

                                CREATE FUNCTION 存储函数名称([参数列表])
                                RETURNS type [characteristics ...]
                                BEGIN
                                	-- SQL语句
                                	RETURN ...;
                                END;
                                

                                characteristics的说明:

                                • DETERMINISTIC:相同的输入参数总是产生相同的结果
                                • NO SQL:不包含SQL语句
                                • READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句。

                                3、定义存储函数,完成如下需求

                                计算从1累加到n的值,n为传入的参数值。

                                -- 存储函数
                                -- 从1到n的累加
                                create function fun1(n int) 
                                returns int deterministic
                                begin
                                	declare total int default 0;
                                	
                                	while n > 0 do
                                		set total := total + n;
                                		set n := n - 1;
                                	end while;
                                	
                                	return total;
                                end;
                                -- 调用存储函数,并显示结果
                                select fun1(100);
                                

                                4.3、触发器(trigger)