系统变量分为全局系统变量(需要添加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 中的用户变量以一个“@” 开头。根据作用范围不同,又分为会话用户变量和局部变量。
-- 会话用户变量
#准备工作
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 变量名 类型 [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 错误类型 处理语句
【处理方式:】
【错误类型(即条件)可以有如下取值:】
#方法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
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
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
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
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
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
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
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;