【SQL】之变量,流程控制与游标

【SQL】之变量,流程控制与游标

  • 一、变量
    • 系统变量
      • 查看系统变量
      • 修改系统变量
    • 用户变量
      • 会话用户变量
      • 局部变量
  • 二、定义条件与处理程序
    • 定义条件
    • 定义处理程序
  • 三、流程控制
    • if···elseif···else···end if
    • case
    • 三种循环
      • loop
      • while
      • repeat
    • leave
    • iterate
  • 游标

一、变量

系统变量

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

查看系统变量

-- 变量,流程控制与游标
-- 系统变量(全局,会话)
#查看系统变量
SHOW GLOBAL VARIABLES; #全局
SHOW SESSION VARIABLES; #会话
SHOW VARIABLES; #默认是会话系统变量 
SHOW GLOBAL VARIABLES LIKE 'admin_%';
SHOW VARIABLES LIKE 'character_%';
#查看指定系统变量
SELECT @@global.max_connections;
SELECT @@session.max_connections;#,错误:Variable 'max_connections' is a GLOBAL variable
SELECT @@session.character_set_client;#全局或会话
SELECT @@session.pseudo_thread_id; #会话
SELECT @@character_set_client;#先查询会话,再查询全局

修改系统变量

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

#会话系统变量:针对于当前会话是有效的,一旦结束会话,重新建立起新的会话,就失效了。
#方式1:
SET @@session.character_set_client='gbk';
#方式2:
SET SESSION character_set_client='gbk';

用户变量

用户变量是用户自己定义的,作为 MySQL 编码规范,MySQL 中的用户变量以一个“@” 开头。根据作用范围不同,又分为会话用户变量和局部变量。

  • 会话用户变量:作用域和会话变量一样,只对当前连接会话有效。
  • 局部变量:只在 BEGIN 和 END 语句块中有效。局部变量只能在存储过程和函数中使用。

会话用户变量

-- 会话用户变量
#准备工作
CREATE DATABASE debtest;
USE debtest;
CREATE TABLE employees
AS 
SELECT * FROM atguigudb.`employees`;
 
CREATE TABLE departments
AS 
SELECT * FROM atguigudb.`departments`;

SELECT * FROM employees;
SELECT * FROM departments;

#测试:
#方式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;

局部变量

  • 局部变量必须使用declare声明
  • 声明并使用在begin···end中(使用在存储过程、函数中)
  • declare的方式声明的局部变量必须声明在begin中的首行的位置
  • 局部变量必须指明类型:declare 变量名 类型 [default 值];
-- 局部变量
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=102;
	
	#使用
	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 emp_name VARCHAR(25);
	DECLARE sal DOUBLE(10,2) DEFAULT 0.0;
	#赋值
	SELECT last_name,salary INTO emp_name,sal
	FROM employees
	WHERE employee_id=102;
	#使用
	SELECT emp_name,sal;
END //
DELIMITER;

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

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

#方式2:
DELIMITER //
CREATE PROCEDURE add_value()
BEGIN 
	#声明
	DECLARE value1,value2,sum_val INT;
	#赋值
	SET value1 = 10;
	SET value2 = 100;
	SET sum_val = value1+value2;
	#使用
	SELECT sum_val;
END //
DELIMITER;

CALL add_value();

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

SELECT @dif_sal;

二、定义条件与处理程序

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

定义条件

定义条件就是给MySQL中的错误码命名,这有助于存储的程序代码更清晰。它将一个错误名字和指定的错误条件关联起来。这个名字可以随后被用在定义处理程序的DECLARE HANDLER 语句中。定义条件使用DECLARE语句,语法格式如下:
DECLARE 错误名称 CONDITION FOR 错误码(或错误条件)

-- 定义条件与处理程序
#错误场景:
INSERT INTO employees(last_name)
VALUES('Tom');
#Field 'email' doesn't have a default value

DESC employees;

#定义条件
#题目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';

定义处理程序

可以为SQL执行过程中发生的某种类型的错误定义特殊的处理程序。定义处理程序时,使用DECLARE语句的语法如下:
DECLARE 处理方式 HANDLER FOR 错误类型 处理语句
【处理方式:】

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

【错误类型(即条件)可以有如下取值:】

  • SQLSTATE ‘字符串错误码’ :表示长度为5的sqlstate_value类型的错误代码;
  • MySQL_error_code :匹配数值类型错误代码;
  • 错误名称:表示DECLARE … CONDITION定义的错误条件名称。
  • SQLWARNING :匹配所有以01开头的SQLSTATE错误代码;
  • NOT FOUND :匹配所有以02开头的SQLSTATE错误代码;
  • SQLEXCEPTION :匹配所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE错误代码;
#方法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';

三、流程控制

凡是循环结构,一定具备四个要素:
初始化条件
循环条件
循环体
迭代条件

if···elseif···else···end if

-- 流程控制
#分支结构之if
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);
	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;

#声明存储过程“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 DOUBLE; #记录员工入职公司的年头
	#赋值
	SELECT salary INTO emp_sal FROM employees WHERE employee_id=emp_id;
	SELECT DATEDIFF(CURDATE(),hire_date)/365 INTO emp_hire_date FROM employees WHERE employee_id=emp_id;
	#判断
	IF emp_sal<8000 AND emp_hire_date>=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(CURDATE(),hire_date)/365,employee_id,salary
FROM employees
WHERE salary<8000 AND DATEDIFF(CURDATE(),hire_date)/365 >=5;

DROP PROCEDURE update_salary_by_eid1;

#声明存储过程“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 DOUBLE; #记录员工入职公司的年头
	#赋值
	SELECT salary INTO emp_sal FROM employees WHERE employee_id=emp_id;
	SELECT DATEDIFF(CURDATE(),hire_date)/365 INTO emp_hire_date FROM employees WHERE employee_id=emp_id;
	#判断
	IF emp_sal<9000 AND emp_hire_date>=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;

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

#调用
CALL update_salary_by_eid2(103);
CALL update_salary_by_eid2(104);

#声明存储过程“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;

SELECT * FROM employees WHERE employee_id IN (104,103,102);
#调用
CALL update_salary_by_eid3(102);
CALL update_salary_by_eid3(103);
CALL update_salary_by_eid3(104);

case

-- case
USE debtest;
DELIMITER //
CREATE PROCEDURE test_case()
BEGIN
	/*情况1:
	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';
	end case;
	*/
	/*情况2:*/
	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();
DROP PROCEDURE test_case;

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

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(105);

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

三种循环

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

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

loop

-- loop
DELIMITER //
CREATE PROCEDURE test_loop()
BEGIN
	#声明局部变量
	DECLARE num INT DEFAULT 1;
	loop_label:LOOP
		#重新赋值
		SET num=num+1;
		IF num >= 10 THEN LEAVE loop_label;
		END IF;
	END LOOP loop_label;
	#查看num
	SELECT num;
END //
DELIMITER ;
#调用
CALL test_loop();

#声明存储过程“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;
		#更新avg_sal变量的值
		SELECT AVG(salary) INTO avg_sal FROM employees;
		#记录循环次数
		SET loop_count=loop_count+1;
	END LOOP loop_lab;
	#给num赋值
	SET num=loop_count;
	
END //
DELIMITER ;

#调用
CALL update_salary_loop(@num);
SELECT @num;

SELECT AVG(salary) FROM employees;

while

-- while
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();
DROP PROCEDURE test_while;

repeat

-- repeat
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();

leave

-- leave
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;
	ELSE
		SELECT MAX(salary) FROM employees;
	END IF;
	SELECT COUNT(*) FROM employees;
END //
DELIMITER ;

CALL leave_begin(2);

iterate

-- iterate
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 'hello sql';#输出6次
	END LOOP;
	
END //
DELIMITER ;
CALL test_iterate();

游标

虽然我们也可以通过筛选条件 WHERE 和 HAVING,或者是限定返回记录的关键字 LIMIT 返回一条记录,但是,却无法在结果集中像指针一样,向前定位一条记录、向后定位一条记录,或者是随意定位到某一条记录,并对记录的数据进行处理。
这个时候,就可以用到游标。游标,提供了一种灵活的操作方式,让我们能够对结果集中的每一条记录进行定位,并对指向的记录中的数据进行操作的数据结构。游标让 SQL 这种面向集合的语言有了面向过程开发的能力
在 SQL 中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针。这里游标充当了指针的作用,我们可以通过操作游标来对数据行进行操作。
MySQL中游标可以在存储过程和函数中使用。

-- 游标
#创建存储过程“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.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,@total_count);

SELECT @total_count;

你可能感兴趣的:(sql笔记,mysql)