第16章_变量、流程控制与游标

1. 变量

在MySQL数据库的存储过程和函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终的结果数据。

在 MySQL 数据库中,变量分为 系统变量 以及 用户自定义变量 。

1.1 系统变量
1.1.1 系统变量分类

系统变量分为全局系统变量(需要添加 global 关键字)以及会话系统变量(需要添加 session 关键字),有时也把全局系统变量简称为全局变量,有时也把会话系统变量称为local变量。如果不写,默认会话级别。静态变量(在 MySQL 服务实例运行期间它们的值不能使用 set 动态修改)属于特殊的全局系统变量。
 

  • 全局系统变量针对于所有会话(连接)有效,但不能跨重启
  • 会话系统变量仅针对于当前会话(连接)有效。会话期间,当前会话对某个会话系统变量值的修改,不会影响其他会话同一个会话系统变量的值。
  • 会话1对某个全局系统变量值的修改会导致会话2中同一个全局系统变量值的修改
 1.1.2 查看系统变量
#1.2 查看系统变量
#查询全局系统变量
SHOW GLOBAL VARIABLES;
#查询会话系统变量
SHOW SESSION VARIABLES;

SHOW VARIABLES ;#默认查询的是会话变量

#查询部分系统变量
SHOW GLOBAL VARIABLES LIKE 'admin_%';

SHOW VARIABLES LIKE 'character_%';

查看指定系统变量

#1.3查看指定的系统变量
SELECT @@global.max_connections;
SELECT  @@global.character_set_client;

#错误
SELECT @@global.pseudo_thread_id;

#错误
SELECT @@session.max_connections;#他是全局变量,不是会话变量 

SELECT  @@session.character_set_client;

SELECT @@session.pseudo_thread_id;

#“@@”首先标记会话系统变量,如果会话系统变量不存在,则标记全局系统变量。
SELECT  @@character_set_client;

 修改系统变量的值

#1.4修改系统变量的值
#全局系统变量:
#方式1:
SET @@global.max_connections=161;
#方式2:
SET GLOBAL max_connections=171;
#针对于当前的数据库实例是有效的,一旦重启mysql服务,就失效了


#会话系统变量:
#方式1:
SET @@session.character_set_client='gbk';

#方式2:
SET SESSION character_set_client='utf8';
#针对当前会话是有效的,一旦结束会话,重新建立起新的会话,就会变回默认值了
1.2 用户变量
1.2.1 用户变量分类

会话用户变量
作用域和会话变量一样,只对当前连接 会话有效。

局部变量
只在 BEGIN 和 END 语句块中有效。局部变量只能在存储过程和函数 中使用。

1.2.2 会话用户变量 
#1.6 会话用户变量
CREATE   DATABASE dbtest16;

USE  dbtest16;

CREATE TABLE employees
AS
SELECT *FROM atguigudb.`employees`;

CREATE TABLE departments
AS
SELECT *FROM atguigudb.`departments`;

SELECT *FROM employees;
SELECT *FROM departments;
/*
#变量的声明和赋值
#方式1:“=”或“:=”
 SET @用户变量 = 值;
 SET @用户变量 := 值;
 
 #方式2:“:=” 或 INTO关键字
 SELECT @用户变量 := 表达式 [FROM 等子句];
 SELECT 表达式 INTO @用户变量  [FROM 等子句];
 
 #使用
 select @变量名

*/

#测试:
#方式1:
SET @m1=1;
SET @m2:=2;
SET @sum :=@m1+@m2;
SELECT @sum;

#方式2:
SELECT @count := COUNT(*) FROM employees;
          
SELECT @count;

SELECT AVG(salary) INTO @avg_sal FROM employees;

SELECT @avg_sal;
1.2.3 局部变量
#1.7局部变量
/*
局部变量:1。使用DECLARE声明 
          2.声明并使用在BEGIN.....END中(存储过程或函数中)
          3.使用DECLARE声明,必须声明在BEGIN的首行

2.声明格式
DECLARE 变量名 类型 [default 值];  # 如果没有DEFAULT子句,初始值为NULL

3.赋值
方式1:
 SET 变量名=值;
 SET 变量名:=值;
 
 方式2:
 SELECT 字段名或表达式 INTO 变量名 FROM 表;
 
 4.使用
 select 局部变量名;
*/

#举例
DELIMITER //
CREATE  PROCEDURE  test_var()
BEGIN 
       #声明局部变量
       DECLARE  a INT DEFAULT 0;
       DECLARE  b INT;
       DECLARE  emp_name VARCHAR(25);
       
       #赋值
       SET  a=1;
       SET b:=2;
       SELECT last_name INTO   emp_name FROM employees WHERE employee_id=101;
       
       SELECT a,b,emp_name;
      
END //       
DELIMITER ;

CALL test_var();

#举例1:声明局部变量,并分别赋值为employees表中
#          employee_id为102的last_name和salary
DELIMITER //
CREATE PROCEDURE test_pro()
BEGIN 
      #声明
      DECLARE empname VARCHAR(25);
      DECLARE sal DOUBLE(10,2) DEFAULT 0;
      #赋值
      SELECT  last_name,salary INTO empname,sal
      FROM employees 
      WHERE employee_id=102;
      
      #使用
      SELECT empname,sal;
END //     
DELIMITER ;

CALL test_pro();


#举例2:声明两个变量,求和并打印 (分别使用会话用户变量、局部变量的方式实现)
#方式1:会话用户变量
SET @v1=10;
SET @v2:=20;
SET @v3 :=@v1 +@v2;

SELECT @v3;

#方式2:局部变量
DELIMITER //
CREATE PROCEDURE add_value()
BEGIN 
        DECLARE value1,value2,sum_value INT;
        SET  value1=10;
        SET  value2:=20;
        SET    sum_value=value1+value2;
        SELECT sum_value;
END //        
DELIMITER ;
DROP PROCEDURE add_value;
CALL add_value();

#举例3:创建存储过程“different_salary”查询某员工和他领导的薪资差距,
#       并用IN参数emp_id接收员工id,用OUT参数dif_salary输出薪资差距结果
  
DELIMITER //
CREATE PROCEDURE different_salary(IN id INT,OUT dif_salary DOUBLE)
BEGIN 
          #声明变量
          DECLARE emp_sal DOUBLE DEFAULT 0;
          DECLARE mgr_sal DOUBLE DEFAULT 0;
          
          DECLARE mgr_id INT ;
          
         #赋值
         SELECT salary INTO emp_sal FROM employees WHERE employee_id=id;
                  
         SELECT manager_id INTO mgr_id FROM employees WHERE employee_id=id;
          
          SELECT salary INTO mgr_sal FROM employees WHERE employee_id=mgr_id;
          
          SET dif_salary = mgr_sal - emp_sal ;
          
          
END //
DELIMITER ;

CALL different_salary(102,@dif_sal);
SELECT @dif_sal;

 

1.2.4 对比会话用户变量与局部变量

2. 定义条件与处理程序 

定义条件是事先定义程序执行过程中可能遇到的问题, 处理程序定义了在遇到问题时应当采取的处理方式,并且保证存储过程或函数在遇到警告或错误时能继续执行。这样可以增强存储程序处理问题的能力,避免程序异常停止运行

2.1 案例分析
#错误案例分析:
 DELIMITER //
 CREATE PROCEDURE UpdateDataNoCondition()
 BEGIN
 SET @x = 1;
 UPDATE employees SET email = NULL WHERE last_name = 'Abel';
 SET @x = 2;
 UPDATE employees SET email = 'aabbel' WHERE last_name = 'Abel';
 SET @x = 3;
 END //
 DELIMITER ;
 
#Column 'email' cannot be null
CALL UpdateDataNoCondition();
SELECT @x;
2.2 定义条件

定义条件就是给MySQL中的错误码命名

#格式:DECLARE 错误名称 CONDITION FOR 错误码(或错误条件)


#举例1:定义“Field_Not_Be_NULL”错误名与MySQL中违反非空约束的错误类型
#       是“ERROR 1048 (23000)”对应。
#方式1:使用MYSQL_error_code 
DECLARE Field_Not_Be_NULL CONDITION FOR 1048;

#方式2:使用sqlstate_value
DECLARE  Field_Not_Be_NULL CONDITION FOR SQLSTATE'23000';

#举例2:定义"ERROR 1148(42000)"错误,名称为command_not_allowed。

DECLARE command_not_allowed  CONDITION FOR 1148;

DECLARE  command_not_allowed CONDITION FOR  SQLSTATE '42000';

 2.3 定义处理程序

#格式:DECLARE 处理方式 HANDLER FOR 错误类型 处理语句

处理方式:处理方式有3个取值:CONTINUE、EXIT、UNDO

  • CONTINUE :表示遇到错误不处理,继续执行。
  • EXIT :表示遇到错误马上退出。
  • UNDO :表示遇到错误后撤回之前的操作。MySQL中暂时不支持这样的操作。

 

#方法1:捕获sqlstate_value
 DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @info = 'NO_SUCH_TABLE';
 
 #方法2:捕获mysql_error_value
 DECLARE CONTINUE HANDLER FOR 1146 SET @info = 'NO_SUCH_TABLE';
 
 #方法3:先定义条件,再调用
 DECLARE no_such_table CONDITION FOR 1146;
 DECLARE CONTINUE HANDLER FOR NO_SUCH_TABLE SET @info = 'NO_SUCH_TABLE';
 
 #方法4:使用SQLWARNING
 DECLARE EXIT HANDLER FOR SQLWARNING SET @info = 'ERROR';
 
 #方法5:使用NOT FOUND
 DECLARE EXIT HANDLER FOR NOT FOUND SET @info = 'NO_SUCH_TABLE';
 
 #方法6:使用SQLEXCEPTION
 DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @info = 'ERROR';
2.4 案例解决
ROP PROCEDURE UpdateDataNoCondition;
#重新定义一个存储过程
 DELIMITER //
 CREATE PROCEDURE UpdateDataNoCondition()
 BEGIN
               #声明处理程序
               #处理方式1:
               DECLARE CONTINUE HANDLER FOR 1048 SET @prc_value=-1;
               #处理方式2:
              # DECLARE CONTINUE HANDLER FOR sqlstate '23000' SET @prc_value=-1;
                SET @x = 1;
		UPDATE employees SET email = NULL WHERE last_name = 'Abel';
		SET @x = 2;
		UPDATE employees SET email = 'aabbel' WHERE last_name = 'Abel';
		SET @x = 3;
 END //
 DELIMITER ;

#调用存储过程
CALL UpdateDataNoCondition();

SELECT  @prc_value,@x;

#2.5 举例:
#创建一个名称为“InsertDataWithCondition”的存储过程,

#准备工作
CREATE TABLE departments
 AS
 SELECT * FROM atguigudb.`departments`;

DESC departments; 
 
 ALTER TABLE departments
 ADD CONSTRAINT uk_dept_name UNIQUE(department_id);

# 定义存储过程
 DELIMITER //
 CREATE PROCEDURE InsertDataWithCondition()
 BEGIN
        SET @x = 1;
	INSERT INTO departments(department_name) VALUES('测试');
	SET @x = 2;
	INSERT INTO departments(department_name) VALUES('测试');
	SET @x = 3;
 END //
 DELIMITER ;

CALL InsertDataWithCondition();
 
SELECT @x; 

#删除此过程
DROP PROCEDURE InsertDataWithCondition;

#重新定义
 DELIMITER //
 CREATE PROCEDURE InsertDataWithCondition()
 BEGIN
        #处理程序
        #方式1:
        DECLARE EXIT HANDLER FOR 1062 SET @pro_value=-1;
        #方式2:
        #declare exit handler for sqlstate '23000' set  @pro_value=-1;
        #方式3:
        #定义条件
       # declare  entry  condition for  1062;
       # declare exit  handler for entry set  @pro_value=-1;
        SET @x = 1;
	INSERT INTO departments(department_name) VALUES('测试');
	SET @x = 2;
	INSERT INTO departments(department_name) VALUES('测试');
	SET @x = 3;
 END //
 DELIMITER ;
  
CALL InsertDataWithCondition();

SELECT @x, @pro_value;

3. 流程控制

  • 条件判断语句 :IF 语句和 CASE 语句
  • 循环语句 :LOOP、WHILE 和 REPEAT 语句
  • 跳转语句 :ITERATE 和 LEAVE 语句
3.1 分支结构之 IF 
举例1:
DELIMITER //
CREATE PROCEDURE  test_if()
BEGIN 
       /*#情况1:
       declare stu_name varchar(15);
       
       if stu_name is null 
                   then select 'stu_name is null';
       end if ;            
        */
     
        /*#情况2:
        declare email varchar(25) default 'aaa';
        if  email is null
                   then select 'email is null';
        else select 'email is not null';
        
        end if;               
       */
        
        #情况3:多选一
        DECLARE age INT DEFAULT 20;
        
        IF age>40 THEN SELECT '中老年';
        ELSEIF  age >18 
                   THEN SELECT '青壮年';
        ELSEIF age >8  
                    THEN  SELECT '青少年';
        ELSE 
               SELECT '幼儿';
         END IF;      
        
           
       
       
       
       
END //
DELIMITER ;

CALL test_if();

DROP PROCEDURE test_if;


#举例2:声明存储过程“update_salary_by_eid1”,定义IN参数emp_id,输入员工编号。
#  判断该员工薪资如果低于8000元并且入职时间超过5年,就涨薪500元;否则就不变。
DELIMITER //
CREATE PROCEDURE update_salary_by_eid1(IN emp_id INT)
BEGIN 
        DECLARE emp_sal DOUBLE;
        DECLARE emp_hire_date DATE;
        
       SELECT salary INTO emp_sal FROM employees WHERE employee_id=emp_id;
       SELECT hire_date INTO emp_hire_date FROM employees WHERE employee_id=emp_id;
       
       IF emp_sal < 8000 AND DATEDIFF(NOW(),emp_hire_date)/365 > 5
        THEN  UPDATE employees 
        SET salary=salary+500 
         WHERE  employee_id=emp_id;
      END IF; 
END //            
DELIMITER ;

CALL  update_salary_by_eid1(104);



SELECT DATEDIFF(NOW(),hire_date)/365,employee_id,salary
FROM employees
WHERE salary <8000 AND DATEDIFF(NOW(),hire_date)/365 >5;

#举例3:声明存储过程“update_salary_by_eid2”,定义IN参数emp_id,输入员工编号。
#判断该员工薪资如果低于9000元并且入职时间超过5年,就涨薪500元;否则就涨薪100元。
DELIMITER //
CREATE PROCEDURE update_salary_by_eid2(IN emp_id INT)
BEGIN 
        DECLARE emp_sal DOUBLE;
        DECLARE emp_hire_date DATE;
        
       SELECT salary INTO emp_sal FROM employees WHERE employee_id=emp_id;
       SELECT hire_date INTO emp_hire_date FROM employees WHERE employee_id=emp_id;
       
       IF emp_sal < 9000 AND DATEDIFF(NOW(),emp_hire_date)/365 > 5
        THEN  UPDATE employees 
              SET salary=salary+500 
              WHERE  employee_id=emp_id;
        ELSE  UPDATE employees 
              SET salary=salary+100 
              WHERE  employee_id=emp_id;
      END IF; 
END //            
DELIMITER ;

CALL  update_salary_by_eid2(103);
CALL  update_salary_by_eid2(104);

SELECT DATEDIFF(NOW(),hire_date)/365,employee_id,salary
FROM employees
WHERE salary <9000 AND DATEDIFF(NOW(),hire_date)/365 >5;




#举例4:声明存储过程“update_salary_by_eid3”,定义IN参数emp_id,输入员工编号。
#      判断该员工薪资如果低于9000元,就更新薪资为9000元;薪资如果大于等于9000元且
#      低于10000的,但是奖金比例为NULL的,就更新奖金比例为0.01;其他的涨薪100元
DELIMITER //
CREATE PROCEDURE update_salary_by_eid3(IN emp_id INT)
BEGIN 
        DECLARE emp_sal DOUBLE;
        DECLARE bonus  DOUBLE ;
        
        SELECT salary INTO emp_sal FROM employees WHERE employee_id=emp_id;
        SELECT commission_pct INTO bonus FROM employees WHERE employee_id=emp_id;
        
        IF emp_sal<9000 
             THEN  UPDATE employees
                    SET salary=9000
                    WHERE  employee_id=emp_id;
        ELSEIF   emp_sal <10000 AND bonus IS NULL          
                   THEN  UPDATE employees
                         SET    commission_pct=0.01
                         WHERE    employee_id=emp_id;  
        ELSE         UPDATE employees
                         SET   salary =salary+100
                         WHERE    employee_id=emp_id; 
          END IF;                      
        
END //            
DELIMITER ;

CALL update_salary_by_eid3(102);
CALL update_salary_by_eid3(103);
CALL update_salary_by_eid3(104);

SELECT *FROM  employees;
3.2 分支结构之 CASE

#3.2分支结构 case
#举例1:
DELIMITER //
CREATE PROCEDURE test_case()
BEGIN 
      #演示1:case....when...then (switch)
     /*
      declare var int default 2;
      case var 
           when 1 then select 'var =1';
           when 2  then select 'var =2';
           when 3  then select 'var= 3';
           else select 'other var';
      end case;     
       */
      #演示2:case when ....then ... when ..then (if)
      DECLARE var1 INT  DEFAULT 10;
       CASE WHEN  var1 >=100 THEN SELECT '三位数';
            WHEN var1>=10 THEN SELECT '两位数';
        ELSE SELECT '个位数';
        END CASE;     
      
      
      
      
END//
DELIMITER ;

CALL test_case();

#举例3:声明存储过程“update_salary_by_eid4”,定义IN参数emp_id,输入员工编号。
#        判断该员工薪资如果低于9000元,就更新薪资为9000元;
#        薪资大于等于9000元且低于10000的,但是奖金比例
#        为NULL的,就更新奖金比例为0.01;其他的涨薪100元。


DELIMITER //
CREATE PROCEDURE update_salary_by_eid4(IN emp_id INT)
BEGIN 
       #局部变量声明
       DECLARE emp_sal DOUBLE;
       DECLARE bonus  DOUBLE;
       
       
       SELECT salary INTO emp_sal FROM employees WHERE employee_id=emp_id; 
       SELECT commission_pct INTO  bonus FROM employees WHERE employee_id=emp_id; 
       
      CASE  WHEN emp_sal <9000
            THEN  UPDATE employees
                  SET salary=9000
                  WHERE employee_id=emp_id; 
            WHEN emp_sal <10000 AND bonus IS NULL
            THEN   UPDATE employees
                  SET  commission_pct=0.01
                  WHERE employee_id=emp_id;         
            ELSE   UPDATE employees
                  SET salary=salary+100
                  WHERE employee_id=emp_id;               
      END CASE;
END //                           
DELIMITER ;

CALL update_salary_by_eid4(103);
CALL update_salary_by_eid4(104);
CALL update_salary_by_eid4(105);

SELECT *
FROM employees
WHERE employee_id IN(103,104,105);



#举例4:声明存储过程update_salary_by_eid5,定义IN参数emp_id,输入员工编号。
#   判断该员工的入职年限,如果是0年,薪资涨50;如果是1年,薪资涨100;
#   如果是2年,薪资涨200;如果是3年,薪资涨300;如果是4年,薪资涨400;其他的涨薪500。
DELIMITER //
CREATE PROCEDURE  update_salary_by_eid5(IN emp_id INT)
BEGIN 
       #声明局部变量
       DECLARE hire_year DOUBLE ;
       
       SELECT DATEDIFF(NOW(),hire_date)/365 INTO hire_year
       FROM employees 
       WHERE  employee_id = emp_id;
       
       CASE  ROUND(hire_year)
           WHEN 0 THEN UPDATE employees
                       SET salary=salary+50
                       WHERE employee_id=emp_id;
           WHEN 1 THEN UPDATE employees
                       SET salary=salary+100
                       WHERE employee_id=emp_id; 
           WHEN 2 THEN UPDATE employees
                       SET salary=salary+200
                       WHERE employee_id=emp_id;
           WHEN 3 THEN UPDATE employees
                       SET salary=salary+300
                       WHERE employee_id=emp_id;                                   
           WHEN 4 THEN UPDATE employees
                       SET salary=salary+400
                       WHERE employee_id=emp_id;
           ELSE   UPDATE employees
                  SET salary=salary+500
                  WHERE employee_id=emp_id;  
        END CASE; 
END //                        
DELIMITER ;


CALL  update_salary_by_eid5(101);


SELECT *
FROM employees
3.3 循环结构之LOOP

#4.1循环结构之LOOP
/*
[loop_label:] LOOP
     循环执行的语句
END LOOP [loop_label]

*/

#举例1:
DELIMITER //
CREATE PROCEDURE test_loop()
BEGIN 
          #声明局部变量
          DECLARE num INT DEFAULT 1;
          
          loop_tabe1:LOOP 
                SET num = num+1;
                IF num >=10 THEN  LEAVE loop_tabe1; #结束语句
                END IF;
           END LOOP loop_tabe1;
           
           #查看
           SELECT num;    
END //
DELIMITER ;

CALL test_loop();


#举例2:
#当市场环境变好时,公司为了奖励大家,决定给大家涨工资。声明存储过程
#         “update_salary_loop()”,声明OUT参数num,输出循环次数。
#          存储过程中实现循环给大家涨薪,薪资涨为原来的1.1倍。
#           直到全公司的平均薪资达到12000结束。并统计循环次数。
DELIMITER //
CREATE  PROCEDURE update_salary_loop(OUT num INT)
BEGIN 
         DECLARE avg_sal DOUBLE;
         DECLARE  loop_count INT DEFAULT 0;#记录循环的次数
         
          SELECT AVG(salary) INTO avg_sal
            FROM employees;
         
         loop_lab:LOOP
            #循环条件
            #结束循环的条件
            IF  avg_sal >=12000 
                   THEN   LEAVE   loop_lab;  
             END IF;  
           #循环体
           # 低于12000 ,更新工资
           UPDATE employees
           SET salary=salary*1.1;
         
            SET loop_count=loop_count+1;  #记录循环的次数
            
            #迭代条件
            #跟新avg_sal的值
            SELECT AVG(salary) INTO avg_sal
            FROM employees;
             
            SELECT loop_count INTO num;
         
         END LOOP  loop_lab;                                
                                           
END //
DELIMITER ;
DROP PROCEDURE update_salary_loop;
CALL update_salary_loop(@m);
SELECT @m;

SELECT AVG(salary)FROM employees;



3.4 循环结构之WHILE
#4.2 循环结构之WHILE
/*
[while_label:] WHILE 循环条件  DO
              循环体
END WHILE [while_label];
*/

#举例1:
DELIMITER //
CREATE PROCEDURE test_while()
BEGIN      
           #初始条件
           DECLARE num INT DEFAULT 1;
           
           WHILE num <=10 DO
                  #循环体
                  
                  #迭代条件
                  SET num=num+1;                  
           END WHILE;
           
           SELECT num;                      
                
END //
DELIMITER ;

CALL  test_while();


#举例2:市场环境不好时,公司为了渡过难关,决定暂时降低大家的薪资。声明存储过程
#“update_salary_while()”,声明OUT参数num,输出循环次数。
#存储过程中实现循环给大家降薪,薪资降为原来的90%。
#直到全公司的平均薪资达到5000结束。并统计循环次数。
DELIMITER //
CREATE PROCEDURE update_salary_while(OUT num INT)
BEGIN 
              DECLARE avg_sal DOUBLE;
              DECLARE while_count INT DEFAULT 0;
              
              SELECT AVG(salary) INTO avg_sal FROM employees ;
              
             WHILE  avg_sal >5000 DO
                   UPDATE employees
                   SET  salary=salary*0.9;
                   
                   SET while_count =while_count +1;
                   SELECT AVG(salary) INTO avg_sal FROM employees ;            
             END WHILE;
             
             SET num=while_count ;
                           
END //              
DELIMITER ;

CALL update_salary_while(@num);
SELECT @num;

SELECT AVG(salary) FROM employees;
3.5 循环结构之REPEAT
4.3 循环结构之REPEAT
/*

[repeat_label:] REPEAT
循环体的语句

UNTIL 结束循环的条件表达式
END REPEAT [repeat_label]



*/

#举例1:
DELIMITER //
CREATE PROCEDURE test_repeat()
BEGIN 
       #声明
       DECLARE num INT DEFAULT 1;
       
       REPEAT 
             SET num=num+1;
             UNTIL num >=10
       END REPEAT;
       
       #查看
       SELECT  num;
              
END //       
DELIMITER ;

CALL test_repeat();


#举例2:当市场环境变好时,公司为了奖励大家,决定给大家涨工资。声明存储过程
#“update_salary_repeat()”,声明OUT参数num,输出循环次数。
#存储过程中实现循环给大家涨薪,薪资涨为原来的1.15倍。
#直到全公司的平均薪资达到13000结束。并统计循环次数。
DELIMITER //
CREATE PROCEDURE update_salary_repeat(OUT num INT)
BEGIN 
              DECLARE avg_sal DOUBLE;
              DECLARE repeat_count INT DEFAULT 0;
              
              SELECT AVG(salary) INTO avg_sal FROM employees ;
               
             REPEAT 
                   UPDATE employees
                   SET salary=salary*1.15;
                   
                   SET  repeat_count=repeat_count+1; 
                    
                   SELECT AVG(salary) INTO avg_sal FROM employees ;
             UNTIL   avg_sal>=13000
             END REPEAT;       
              
             SET num= repeat_count;
                           
                             
END //              
DELIMITER ;
DROP PROCEDURE update_salary_repeat;

CALL update_salary_repeat(@num);
SELECT @num;
对比三种循环结构:

1、这三种循环都可以省略名称,但如果循环中添加了循环控制语句(LEAVE或ITERATE)则必须添加名称。

2、 LOOP:一般用于实现简单的"死"循环 WHILE:先判断后执行 REPEAT:先执行后判断,无条件至少执行一次

3.6 跳转语句之LEAVE语句 

LEAVE语句:可以用在循环语句内,或者以 BEGIN 和 END 包裹起来的程序体内,表示跳出循环或者跳出程序体的操作。如果你有面向过程的编程语言的使用经验,你可以把 LEAVE 理解为 break。

格式:LEAVE 标记名


#5.1 LEAVE的使用
/*#举例1:创建存储过程 “leave_begin()”,声明INT类型的IN参数num。给BEGIN...END加标记名,、、
# 并附BEGIN...END中使用IF语句判断num参数的值。
如果num<=0,则使用LEAVE语句退出BEGIN...END;
如果num=1,则查询“employees”表的平均薪资;
如果num=2,则查询“employees”表的最低薪资;
如果num>2,则查询“employees”表的最高薪资。
IF语句结束后查询“employees”表的总人数。


*/

DELIMITER //
CREATE PROCEDURE leave_begin(IN num INT)
begin_label:BEGIN 
           IF num <=0 THEN LEAVE begin_label;
           ELSEIF num=1 THEN SELECT  AVG(salary)
                             FROM employees;
            ELSEIF num=2 THEN SELECT  MIN(salary)
                             FROM employees;                 
             ELSEIF num>2 THEN SELECT  MAX(salary)
                             FROM employees;  
            END IF;
            SELECT COUNT(*) FROM employees;
END //                                    
DELIMITER ;

CALL leave_begin(1);


#举例2:
#当市场环境不好时,公司为了渡过难关,决定暂时降低大家的薪资。
#声明存储过程“leave_while()”,声明OUT参数num,输出循环次数,
#存储过程中使用WHILE循环给大家降低薪资为原来薪资的90%,
#直到全公司的平均薪资小于等于10000,并统计循环次数。
DELIMITER //
CREATE PROCEDURE leave_while(OUT num INT)
BEGIN 
        DECLARE avg_sal DOUBLE ;
        DECLARE  while_count INT DEFAULT 0;
        
        SELECT AVG(salary) INTO avg_sal FROM employees ;#初始条件
        
  while_label:WHILE TRUE DO#循环条件
                #循环体
              IF avg_sal <=10000 THEN LEAVE while_label;
              END IF;
              
              UPDATE employees
              SET salary=salary *0.9;
              
              SET while_count=while_count+1;
              SELECT AVG(salary) INTO avg_sal FROM employees ;#迭代条件
        END WHILE;
        SET  num=while_count;
END //              
DELIMITER ;

SELECT AVG(salary) FROM  employees;

CALL leave_while(@num);

SELECT @num;
3.7 跳转语句之ITERATE语句

ITERATE语句:只能用在循环语句(LOOP、REPEAT和WHILE语句)内,表示重新开始循环,将执行顺序转到语句段开头处。如果你有面向过程的编程语言的使用经验,你可以把 ITERATE 理解为 continue,意思为“再次循环”。

格式:ITERATE label

举例: 定义局部变量num,初始值为0。循环结构中执行num + 1操作。
如果num < 10,则继续执行循环;
如果num > 15,则退出循环结构;
*/
DELIMITER //
CREATE PROCEDURE test_iterate()
BEGIN  
         DECLARE num INT DEFAULT 0;
   loop_label:LOOP
              SET num=num+1;
              IF num<10 THEN  ITERATE loop_label;
              ELSEIF num>15 THEN LEAVE loop_label;
              END IF ;
             
             SELECT '哈哈哈';
           END LOOP;      
END //               
DELIMITER ;

CALL test_iterate();
	

 4. 游标

4.1 什么是游标(或光标)

随意定位到某一条记录,并对记录的数据进行处理

结果集中的每一条记录进行定位,并对指向的记录中的数据进行操作的数据结构

4.2 使用游标步骤 

游标的使用步骤:
1.声明游标
2。打开游标
3.使用游标(从游标中获取数据)
4。关闭游标
*/

4.3 举例 
 4.3 举例 
#创建存储过程“get_count_by_limit_total_salary()”,
#声明IN参数 limit_total_salary,DOUBLE类型;声明OUT参数total_count,INT类型。
#函数的功能可以实现累加薪资最高的几个员工的薪资值,
#直到薪资总和达到limit_total_salary参数的值,返回累加的人数给total_count。
DELIMITER //
CREATE PROCEDURE  get_count_by_limit_total_salary(IN limit_total_salary DOUBLE,OUT total_count INT)
BEGIN 
         DECLARE sum_sal DOUBLE DEFAULT 0;
         DECLARE emp_sal DOUBLE ;
         DECLARE emp_count INT DEFAULT 0;
         #声明游标
         DECLARE emp_cursor CURSOR FOR SELECT salary FROM employees ORDER BY salary DESC;
         #打开游标
         OPEN  emp_cursor;
         
        REPEAT 
              #使用游标
              FETCH emp_cursor INTO emp_sal;
              
              SET  sum_sal= sum_sal+emp_sal;
              SET emp_count=emp_count+1;
              UNTIL  sum_sal >=limit_total_salary
         END  REPEAT ;
             SET total_count=emp_count;
             #关闭游标
             CLOSE emp_cursor;
END //              
DELIMITER ;

CALL get_count_by_limit_total_salary(200000,@k);
SELECT @k;
4.5 小结

跟在应用层面实现相同的功能相比,游标可以在存储程序中使用,效率高,程序也更加简洁。

但同时也会带来一些性能问题,比如在使用游标的过程中,会对数据行进行加锁 ,这样在业务并发量大的时候,不仅会影响业务之间的效率,还会消耗系统资源 ,造成内存不足,这是因为游标是在内存中进行的处理。

建议:养成用完之后就关闭的习惯,这样才能提高系统的整体效率。

补充:MySQL 8.0的新特性—全局变量的持久化**
#补充:MySQL 8.0的新特性—全局变量的持久化**
SET GLOBAL MAX_EXECUTION_TIME=2000;

SHOW VARIABLES LIKE	'%max_connections%';
SET persist max_connections=1000;

#重启MySQL服务器 ,再次查询max_connections的值:

SHOW VARIABLES LIKE	'%max_connections%';

课后练习

#第16章_变量、流程控制与游标练习题

 1. 变量 
题目:
#0.准备工作
CREATE DATABASE test16_var_cur;
 USE test16_var_cur;
 CREATE TABLE employees
 AS
 SELECT * FROM atguigudb.`employees`;
 CREATE TABLE departments
 AS
 SELECT * FROM atguigudb.`departments`;
 #无参有返回
#1. 创建函数get_count(),返回公司的员工个数
DELIMITER //
CREATE FUNCTION  get_count() 
RETURNS INT 
BEGIN 
        DECLARE emp_count INT;
         SELECT COUNT(*) INTO emp_count 
         FROM employees;
        RETURN emp_count;
             
END //
DELIMITER ;

DROP FUNCTION get_count;
SELECT get_count();
#有参有返回

#2. 创建函数ename_salary(),根据员工姓名,返回它的工资
DELIMITER //
CREATE FUNCTION ename_salary(emp_name VARCHAR(15))
RETURNS DOUBLE
BEGIN 
          SET @sal=0;#会话用户变量
           SELECT salary INTO @sal FROM employees WHERE last_name=emp_name;
         RETURN @sal;
END //
DELIMITER ;

DROP FUNCTION ename_salary;
SELECT *FROM employees;
SELECT ename_salary('Ernst')
#3. 创建函数dept_sal() ,根据部门名,返回该部门的平均工资
DELIMITER //
CREATE FUNCTION dept_sal(dep_name VARCHAR(15))
RETURNS DOUBLE
BEGIN 
          DECLARE avg_sal DOUBLE ;
           SELECT AVG(salary) INTO avg_sal
                  FROM employees  e JOIN departments d
                  ON e.department_id=d.department_id
                  WHERE d.department_name=dep_name;
         RETURN avg_sal;
                 
                 
END //
DELIMITER ;

DROP FUNCTION dept_sal;
SELECT *FROM departments;
SELECT dept_sal('Marketing');




#4. 创建函数add_float(),实现传入两个float,返回二者之和
DELIMITER //
CREATE FUNCTION add_float(f1 FLOAT ,f2 FLOAT )
RETURNS  FLOAT
BEGIN
          DECLARE SUM FLOAT ;
          SET   SUM=f1+f2;
          RETURN SUM;

END //
DELIMITER ;

DROP  FUNCTION add_float;
 



2. 流程控制 
题目:
#1. 创建函数test_if_case(),实现传入成绩,如果成绩>90,返回A,
#   如果成绩>80,返回B,如果成绩>60,返回C,否则返回D
DELIMITER //
CREATE FUNCTION test_if_case(grade DOUBLE)
RETURNS CHAR(1)
BEGIN        
         DECLARE gra CHAR(1) ;
         IF grade>90 THEN  SET gra='A';
         ELSEIF grade >80 THEN SET gra='B'; 
         ELSEIF grade >60 THEN SET gra='C';
         ELSE SET  gra='D' ;
         END  IF;
         RETURN gra;
END //         
DELIMITER ;

SELECT   test_if_case(90);



#方式2:
DELIMITER //
CREATE FUNCTION test_if_case2(grade DOUBLE)
RETURNS CHAR(1)
BEGIN        
         DECLARE gra CHAR(1) ;
         CASE WHEN grade>90 THEN  SET gra='A';
         WHEN grade >80 THEN SET gra='B'; 
        WHEN grade >60 THEN SET gra='C';
         ELSE SET  gra='D' ;
         END  CASE;
         RETURN gra;
END //         
DELIMITER ;

SELECT   test_if_case2(76);
#要求:分别使用if结构和case结构实现
#2. 创建存储过程test_if_pro(),传入工资值,如果工资值<3000,
#则删除工资为此值的员工,如果3000 <= 工资值 <= 5000,
#则修改此工资值的员工薪资涨1000,否则涨工资500
DELIMITER //
CREATE PROCEDURE test_if_pro(IN sal DOUBLE)
BEGIN 
        IF sal <3000 THEN DELETE FROM employees
                          WHERE salary=sal;
        ELSEIF  sal <=5000 THEN  UPDATE employees
                                 SET salary =salary+1000
                                 WHERE  salary=sal;                  
        ELSE  UPDATE employees
              SET salary =salary+500
              WHERE  salary=sal;
        END IF;
END //               
DELIMITER ;

DROP PROCEDURE test_if_pro;

SELECT *FROM  employees;
CALL  test_if_pro(24000);

#3. 创建存储过程insert_data(),传入参数为 IN 的 INT 类型变量 insert_count,
#   实现向admin表中批量插入insert_count条记录
CREATE TABLE admin(
 id INT PRIMARY KEY AUTO_INCREMENT,
 user_name VARCHAR(25) NOT NULL,
 user_pwd VARCHAR(35) NOT NULL
 );
 SELECT * FROM admin;
 
DELIMITER //
CREATE PROCEDURE insert_data(IN insert_count INT)
BEGIN 
        DECLARE a INT DEFAULT 1;
        
        WHILE a<=insert_count DO 
                INSERT INTO admin(user_name,user_pwd)
                VALUE('tom',a);
                SET a=a+1;
        END WHILE;       
END //
DELIMITER ; 
 
CALL insert_data(10);




3. 游标的使用 
#创建存储过程update_salary(),参数1为 IN 的INT型变量dept_id,
#表示部门id;参数2为 IN的INT型变量change_sal_count,
#表示要调整薪资的员工个数。查询指定id部门的员工信息,
#按照salary升序排列,根据hire_date的情况,
#调整前change_sal_count个员工的薪资,详情如下。
DELIMITER //
CREATE PROCEDURE update_salary(IN dept_id INT,IN change_sal_count INT )
BEGIN 
           DECLARE i INT DEFAULT 1 ;
           DECLARE emp_id VARCHAR(25)  ;
           DECLARE emp_date DATE;
           DECLARE count_cursor CURSOR FOR  SELECT employee_id,hire_date 
                                            FROM employees
                                            WHERE department_id=dept_id
                                            ORDER BY salary ;
           OPEN  count_cursor;
           WHILE i<=change_sal_count DO
                   FETCH count_cursor INTO emp_id,emp_date;
                                      
                   IF YEAR(emp_date) < 1995 THEN  UPDATE employees 
                                                  SET salary=salary*1.2
                                                  WHERE  employee_id=emp_id;
                   ELSEIF YEAR(emp_date) <=1998 THEN UPDATE employees
                                                SET salary=1.15
                                                WHERE  employee_id=emp_id;
                   ELSEIF  YEAR(emp_date) <=2001 THEN UPDATE employees
                                                 SET salary=salary*1.1
                                                 WHERE    employee_id=emp_id;                         
                   ELSE    UPDATE employees
                           SET salary=salary*1.05
                           WHERE   employee_id=emp_id; 
                           
                   END IF;  
                   SET i=i+1;
            END WHILE ;
            CLOSE count_cursor;
                  
                                           
END //
DELIMITER ;
DROP PROCEDURE update_salary;

SELECT salary 
FROM employees
WHERE department_id=90
ORDER BY salary ;

SELECT *FROM employees;
   
CALL   update_salary(90,3);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值