在 MySQL 数据库的存储过程和存储函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终的结果数据。
在 MySQL 数据库中,变量分为系统变量以及用户自定义变量。
1.1 系统变量分类
变量由系统定义,不是用户定义,属于服务器层面。启动 MySQL 服务,生成 MySQL 服务实例期间,MySQL 将为 MySQL 服务器内存中的系统变量赋值,这些系统变量定义了当前 MySQL 服务实例的属性、特征。这些系统变量的值要么是编译 MySQL 时参数的默认值,要么是配置文件(例如 my.ini 等)中的参数值。可以通过网址 Server System Variables 查看 MySQL 中系统变量的相关信息。
系统变量分为全局系统变量(需要添加 GLOBAL 关键字)以及会话系统变量(需要添加 SESSION 关键字),有时也把全局系统变量简称为全局变量,有时也把会话系统变量称为 local 变量。如果不写,默认会话级别。
静态变量(在 MySQL 服务实例运行期间它们的值不能使用 SET 进行动态修改)属于特殊的全局系统变量。
每一个 MySQL 客户机成功连接 MySQL 服务器后,都会产生与之对应的会话。会话期间,MySQL 服务实例会在 MySQL 服务器内存中生成与该会话对应的会话系统变量,这些会话系统变量的初始值是全局系统变量值的复制。如下图:
# 使用局部变量DELIMITER //CREATE PROCEDURE add_value()BEGIN DECLARE m INT DEFAULT 1; DECLARE n INT DEFAULT 3; DECLARE SUM INT; SET SUM = m + n; SELECT SUM;END //DELIMITER ;# 调用存储过程CALL add_value();
示例三:创建存储过程 different_salary 查询某员工和他领导的薪资差距,并用 IN 参数 emp_id 接收员工 id,用 OUT 参数 dif_salary 输出薪资差距结果。
DECLARE command_not_allowed CONDITION FOR 1148;DECLARE command_not_allowed CONDITION FOR SQLSTATE '42000';
2.3 定义处理程序
可以为 SQL 执行过程中发生的某些类型的错误定义特殊的处理程序。语法如下:
1
DECLARE 处理方式 HANDLER FOR 错误类型 处理语句;
处理方式
有三个取值:CONTINUE、EXIT 和 UNDO。
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 错误码。
处理语句
如果出现上述条件之一,则采用对应的处理方式,并执行指定的处理语句。语句可以是像 SET 变量 = 值 这样的简单语句,也可以是使用 BEGIN...END 编写的复合语句。
定义处理程序的使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
# 捕获 sqlstate_valueDECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @info = 'NO_SUCH_TABLE';# 捕获 mysql_error_valueDECLARE CONTINUE HANDLER FOR 1146 SET @info = 'NO_SUCH_TABLE';# 先定义条件,再调用DECLARE no_such_table CONDITION FOR 1146;DECLARE CONTINUE HANDLER FOR NO_SUCH_TABLE SET @info = 'NO_SUCH_TABLE';# 使用 SQLWARNINGDECLARE EXIT HANDLER FOR SQLWARNING SET @info = 'ERROR';# 使用 NOT FOUNDDECLARE EXIT HANDLER FOR NOT FOUND SET @info = 'NO_SUCH_TABLE';# 使用 SQLEXCEPTIONDECLARE EXIT HANDLER FOR SQLEXCEPTION SET @info = 'ERROR';
DELIMITER //CREATE PROCEDURE update_salary_by_eid1(IN emp_id INT)BEGIN DECLARE emp_salary DOUBLE; DECLARE hire_year DOUBLE; SELECT salary INTO emp_salary FROM employees WHERE employee_id = emp_id; SELECT DATEDIFF(CURDATE(), hire_date) / 365 INTO hire_year FROM employees WHERE employee_id = emp_id; IF emp_salary < 8000 AND hire_year > 5 THEN UPDATE employees SET salary = salary + 500 WHERE employee_id = emp_id; END IF;END //DELIMITER ;
DELIMITER //CREATE PROCEDURE update_salary_by_eid3(IN emp_id INT)BEGIN DECLARE emp_salary DOUBLE; DECLARE bonus DECIMAL(3, 2); SELECT salary INTO emp_salary FROM employees WHERE employee_id = emp_id; SELECT commission_pct INTO bonus FROM employees WHERE employee_id = emp_id; IF emp_salary < 9000 THEN UPDATE employees SET salary = 9000 WHERE employee_id = emp_id; ELSEIF emp_salary < 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 ;
3.2 分支结构之 CASE
CASE 语句的语法结构一:
1 2 3 4 5 6 7
# 类似与 switchCASE 表达式WHEN 值1 THEN 结果1或语句1(如果是语句, 需要加分号) WHEN 值2 THEN 结果2或语句2(如果是语句, 需要加分号)...ELSE 结果n或语句n(如果是语句, 需要加分号)END [CASE](如果是放在BEGIN END中需要加上CASE, 如果放在SELECT后面不需要)
CASE 语句的语法结构二:
1 2 3 4 5 6 7
# 类似于多重 ifCASEWHEN 条件1 THEN 结果1或语句1(如果是语句, 需要加分号)WHEN 条件2 THEN 结果2或语句2(如果是语句, 需要加分号)...ELSE 结果n或语句n(如果是语句, 需要加分号)END [case](如果是放在BEGIN END中需要加上CASE, 如果放在SELECT后面不需要)
使用示例
示例一:使用 CASE 流程控制语句的第 1 种格式,判断 val 值等于 1、等于 2,或者两者都不等。
1 2 3 4 5
CASE val WHEN 1 THEN SELECT 'val is 1'; WHEN 2 THEN SELECT 'val is 2'; ELSE SELECT 'val is not 1 or 2';END CASE;
示例二:使用 CASE 流程控制语句的第 2 种格式,判断 val 是否为空、小于 0、大于 0 或者等于 0。
1 2 3 4 5 6
CASE WHEN val IS NULL THEN SELECT 'val is null'; WHEN val < 0 THEN SELECT 'val is less than 0'; WHEN val > 0 THEN SELECT 'val is greater than 0'; ELSE SELECT 'val is 0';END CASE;
DELIMITER //CREATE PROCEDURE update_salary_by_eid4(IN emp_id INT)BEGIN DECLARE emp_sal DOUBLE; DECLARE bonus DECIMAL(3, 2); 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 ;
DELIMITER //CREATE PROCEDURE update_salary_by_eid5(IN emp_id INT)BEGIN DECLARE emp_sal DOUBLE; DECLARE hire_year DOUBLE; SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id; SELECT ROUND(DATEDIFF(CURDATE(), hire_date) / 365) INTO hire_year FROM employees WHERE employee_id = emp_id; CASE 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 ;
DELIMITER //CREATE PROCEDURE update_salary_loop(OUT num INT)BEGIN DECLARE avg_salary DOUBLE; DECLARE loop_count INT DEFAULT 0; SELECT AVG(salary) INTO avg_salary FROM employees; label_loop: LOOP IF avg_salary >= 12000 THEN LEAVE label_loop; END IF; UPDATE employees SET salary = salary * 1.1; SET loop_count = loop_count + 1; SELECT AVG(salary) INTO avg_salary FROM employees; END LOOP label_loop; SET num = loop_count;END //DELIMITER ;
3.4 循环结构之 WHILE
WHILE 语句创建一个带条件判断的循环过程。WHILE 在执行语句执行时,先对指定的表达式进行判断,如果为真,就执行循环内的语句,否则退出循环。WHILE 语句的基本格式如下:
1 2 3
[while_label:] WHILE 循环条件 DO 循环体END WHILE [while_label];
while_label 为 WHILE 语句的标注名称,可以省略;如果循环条件结果为真,WHILE 语句内的语句或语句群被执行,直至循环条件为假,退出循环。
使用示例
示例一:i 值小于 10 时,重复执行循环过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
DELIMITER //CREATE PROCEDURE test_while()BEGIN DECLARE i INT DEFAULT 0; WHILE i < 10 DO SET i = i + 1; END WHILE; SELECT i;END //DELIMITER ;# 调用存储过程CALL test_while(); -- 10
示例二:声明存储过程 update_salary_while(),声明 OUT 参数 num,输出循环次数。存储过程中使用循环实现降薪逻辑,薪资降为原来的 90%,直到全公司的平均薪资达到 5000 结束,同时统计循环次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
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 ;
3.5 循环结构之 REPEAT
REPEAT 循环与 WHILE 循环类似,与 WHILE 循环不同的是,REPEAT 循环首先会执行一次循环,然后在 UNTIL 中进行表达式的判断,如果满足条件就退出,即 END REPEAT;如果条件不满足,则会就继续执行循环,直到满足退出条件为止。
DELIMITER //CREATE PROCEDURE test_repeat()BEGIN DECLARE i INT DEFAULT 0; REPEAT SET i = i + 1; UNTIL i >= 10 END REPEAT; SELECT i;END //DELIMITER ;
示例二:声明存储过程 update_salary_repeat(),声明 OUT 参数 num,输出循环次数。存储过程中使用循环实现涨薪逻辑,薪资涨为原来的 1.15 倍,直到全公司的平均薪资达到 13000 结束,同时统计循环次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
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 ;
示例一:创建存储过程 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 表的最高薪资。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
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 ;
示例二:声明存储过程 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 ;
3.7 跳转语句之 ITERATE
ITERATE 语句只能用在循环语句(LOOP、REPEAT 和 WHILE 语句)内,表示重新开始循环,将执行顺序转到语句段开头处(与 Java 中的 continue 类似)。基本格式如下:
DELIMITER //CREATE PROCEDURE test_iterate() BEGIN DECLARE num INT DEFAULT 0; my_loop: LOOP SET num = num + 1; IF num < 10 THEN ITERATE my_loop; ELSEIF num > 15 THEN LEAVE my_loop; END IF; END LOOP my_loop; SELECT num;END //DELIMITER ;CALL test_iterate(); -- 16
4. 游标
4.1 什么是游标
虽然可以通过筛选条件 WHERE 和 HAVING,或者限定返回记录的关键字 LIMIT 返回一条记录,但无法在结果集中像指针一样,向前或向后定位一条记录,再或者是随意定位到某一条记录,并对记录的数据进行处理。
DELIMITER // CREATE TRIGGER before_insertBEFORE INSERT ON test_triggerFOR EACH ROW BEGIN INSERT INTO test_trigger_log (t_log) VALUES('before_insert'); END //DELIMITER ;
向 test_trigger 表中插入数据:
1
INSERT INTO test_trigger(t_note) VALUES ('测试 BEFORE INSERT 触发器');
DELIMITER //CREATE TRIGGER after_insertAFTER INSERT ON test_triggerFOR EACH ROW BEGIN INSERT INTO test_trigger_log (t_log) VALUES('after_insert'); END //DELIMITER ;
向 test_trigger 数据表中插入数据:
1
INSERT INTO test_trigger(t_note) VALUES ('测试 AFTER INSERT 触发器');
DELIMITER //CREATE TRIGGER salary_check_triggerBEFORE INSERT ON employees FOR EACH ROW BEGIN DECLARE mgrsalary DOUBLE; SELECT salary INTO mgrsalary FROM employees WHERE employee_id = NEW.manager_id; # NEW 表示执行 INSERT 语句新增的记录 IF NEW.salary > mgrsalary THEN SIGNAL SQLSTATE 'HY000' SET MESSAGE_TEXT = '薪资高于领导薪资错误'; END IF; END //DELIMITER ;
6.5 查看触发器
可以使用以下三种方式查看数据库中已经存在的触发器的定义、状态和语法等信息。
查看当前数据库的所有触发器的定义
1 2 3 4
# 命令行中使用SHOW TRIGGERS\G# 图形化界面中使用SHOW TRIGGERS;
查看当前数据库中某个触发器的定义
1
SHOW CREATE TRIGGER 触发器名;
比如:
1
SHOW CREATE TRIGGER after_insert;
从系统库 information_schema 的 TRIGGERS 表中查询触发器的信息。
1
SELECT * FROM information_schema.TRIGGERS;
比如:
1
SELECT * FROM information_schema.TRIGGERS WHERE TRIGGER_NAME = 'after_insert';