MySQL存储过程

先赞后看,养成习惯!!! ^ _ ^ ❤️ ❤️ ❤️

码字不易,大家的支持就是我坚持下去的动力,点赞后不要忘记关注我哦

📘 本系列文章为本人在学习路上遇到的问题和解决方法,在这里撰写成文是为了巩固知识和帮助其他友友。

个人主页 🔍: 小许学java

专栏链接 📁: 问题分析简介

如有错误,请您指正批评 ^ _ ^

1. 存储过程

1.1 存储过程是什么

存储过程是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字和参数来执行,并获取相应的结果。

1.2 特点

封装性:将业务逻辑封装在数据库内部,减少应用程序的复杂性。

可维护性:集中管理数据库操作,便于维护和更新。

可重用性:可以被多次调用,提高代码的重用性。

1.3 优缺点

1.3.1 优点

性能优化:存储过程在创建时编译并存储在数据库中,执行速度比单个SQL语句快。

代码重用:存储过程可以重复调用,减少重复代码,提高代码的可维护性。

安全性:可以限制用户直接访问数据库,通过存储过程间接访问,从而保证系统安全性。

事务管理:可以在存储过程中实现复杂的事务逻辑。

降低耦合:当表结构发生变化时,只需要修改相应的存储过程,应用程序的改动较小。

1.4 缺点

可移植性差:存储过程不能跨数据库移植,更换数据库时需要重新编写。

调试困难:只有少数数据库管理系统支持存储过程的调试,开发和维护困难。

不适合高并发场景:在高并发场景下,存储过程可能会增加数据库的压力,难以维护。

1.5 语法

1.5.1 创建

sql 复制代码
-- 修改SQL语句结束标识符为 //  
DELIMITER //
-- 创建存储过程 
CREATE PROCEDURE 存储过程名 (参数列表)
BEGIN
 -- SQL 语句 
END //
-- 修改SQL语句结束标识符为 ; 
DELIMITER ;

1.5.2 调用

sql 复制代码
-- 调⽤存储过程 
CALL 存储过程名 (参数列表);

1.5.3 查看

sql 复制代码
-- 查看指定数据库中创建的存储过程 
SELECT * FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = '数据库名';
-- 查看存储过程的定义 
SHOW CREATE PROCEDURE 存储过程名;

1.5.4 删除

sql 复制代码
DROP PROCEDURE [IF EXISTS] 存储过程名;

1.6 示例

sql 复制代码
-- 创建存储过程 
CREATE PROCEDURE p_calAvg()
BEGIN
 select name, chinese + math + english as total from exam;
END;
-- 调⽤存储过程 
CALL p_calAvg();

在命令行中创建存储过程要先修改SQL语句结束标识符,比如DELIMITER //。

因为在存储过程中编写SQL语句时使用分号(;)做为结束符时会导致提交整个存储过程创建语句, 但是其实这时存储过程的创建并未完成。

2. 变量

在MySQL中变量可以分为三类:系统变量、用户自定义变量、以及局部变量。下面将详细解释这三类变量的使用。

2.1 系统变量

系统变量是MySQL服务器的配置变量,控制着服务器的行为和性能。分为全局变量(GLOBAL)和会话变量(SESSION)。

2.1.1 查看系统变量

sql 复制代码
-- 查看所有系统变量 
SHOW [GLOBAL|SESSION] VARIABLES;
-- 查看指定的系统变量 
SHOW [GLOBAL|SESSION] VARIABLES LIKE 'xxx';
-- 查看指定的系统变量,可以通过LKIE进⾏模糊查询 
SHOW [GLOBAL|SESSION] VARIABLES like '%xxx%';
-- 使⽤SELECT查看指定系统变量 
SELECT @@[GLOBAL|SESSION].系统变量名;
-- ----------------------------------------------------
-- ⽰例:查看以auto开头的全局系统变量 
SHOW GLOBAL VARIABLES LIKE 'auto%';
-- ⽰例:查看以char开头的会话系统变量 
SHOW SESSION VARIABLES LIKE 'char%';
-- ⽰例:查看事务⾃动提交全局系统变量 
SELECT @@GLOBAL.autocommit;

2.1.2 设置系统变量

sql 复制代码
SET [GLOBAL|SESSION] 系统变量名 = 值;
SET @@SESSION.系统变量名 = 值;
-- -------------------------------------------
-- ⽰例:设置事务⾃动提交会话变量为关闭/开启 
SET @@SESSION.autocommit = 0;
SET autocommit = 1;

如果没有指定GLOBAL|SESSION,默认设置会话(SESSION)全局变量。

会话关闭后,设置的会话(SESSION)变量失效;新建的会话读取全局系统变量的值做为初始值。

MySQL重启后,设置的全局(GLOBAL)变量失效,如果想使全局系统变量永久生效,需要修改选 项文件。

2.2 用户自定义变量

用户自定义变量是在SQL会话中定义的变量,不用提前声明,作用域为当前会话。

2.2.1 赋值

sql 复制代码
-- ⽅式⼀ 
SET @var_name = expr [, @var_name]...;
-- ⽅式⼆ 【推荐】 
SET @var_name := expr [, @var_name]...;
-- ⽅式三:在SELECT语句中 
SELECT @var_name := expr [, @var_name]...;
-- ⽅式四:查询结果赋值给⾃定义变量  
SELECT 列名 INTO @var_name FROM 表名 WHERE ...;

2.2.2 使用

sql 复制代码
-- ⽰例:定义⼀个age变量并赋值为18,并查看 
SET @age := 18;
SELECT @age;
-- ⽰例:从学⽣表中查询编号为1的学⽣学号并赋值给sno变量 
SELECT sno INTO @sno from student where id = 1; 
SELECT @sno;
-- ⽰例:查询学⽣表中的总记录数并赋值给conut变量 
SELECT count(*) INTO @count from student;
SELECT @count;
-- ⽰例:访问⼀个未赋值的变量,返回NULL 
SELECT @var;

由于SQL中比较相等也是用等号(=),所以在为变量赋值的时候推荐使用(:=)。

2.3 局部变量

局部变量只在存储过程、函数或触发器的范围内有效。需要使用 DECLARE 声明,作用域的范围在声明的 BEGIN ... END 块内。

2.3.1 声明

变量可以是任何有效的MySQL数据类型,如 INT 、 VARCHAR 、 DATETIME 等。

sql 复制代码
DECLARE 变量名 变量类型 [DEFAULT 默认值] ...;

2.3.2 赋值

sql 复制代码
-- ⽅式⼀ 
SET var_name = 值;
-- ⽅式⼆ 【推荐】 
SET var_name := 值;
-- ⽅式三:查询结果赋值给⾃定义变量  
SELECT 列名 INTO var_name FROM 表名 WHERE ...;

2.3.3 使用

sql 复制代码
-- ⽰例:在存储过程中定义局部变量记录学⽣表的总记录数 
CREATE PROCEDURE p1() 
BEGIN
 -- 定义局部变量,并指定默认值 
 DECLARE stu_count INT DEFAULT 0;
 -- 把查询结果赋值给局部变量 
 select count(*) into stu_count from student;
 -- 使⽤局部变量 
 select stu_count;
END;
-- 调⽤存储过程 
call p1();

2.4 注意事项

变量名不区分大小写。

在存储过程和函数中,局部变量必须在使用前声明。

用户自定义变量在会话结束时失效,而局部变量在存储过程或函数结束时失效。

避免使用保留字作为变量名。

3. SQL编程

结构化查询语言(Structured Query Language)简称SQL,是⼀种特殊目的的编程语言,是一种数据库查询和程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统。

3.1 条件判断-IF语句

3.1.1 语法

sql 复制代码
IF 条件1 THEN
 ......
[ELSEIF 条件2 THEN
 ......
ELSE
 ......]
END IF;

3.1.2 练习

示例:根据分数的值,判定当前分数对应的等级

sql 复制代码
-- 创建存储过程 
CREATE PROCEDURE p2() 
BEGIN
 -- 定义初始分数变量 
 DECLARE score INT DEFAULT 86;
 -- 定义等级结果变量 
 DECLARE result VARCHAR(10);
 
 -- 判断 
 IF score >= 90 THEN
 SET result := '优秀';
 ELSEIF score >= 80 AND score < 90 THEN
 SET result := '良好';
 ELSEIF score >= 60 AND score < 80 THEN
 SET result := '及格';
 ELSE
 SET result := '不及格';
 END IF;
 
 -- 查询结果 
 SELECT result;
 
END;
-- 调⽤存储过程 
CALL p2();

当前有个问题:分数值是固定的,我们需要把分数当一个参数传进存储过程就需要用参数接收

3.2 参数

|-------|---------------------------|
| 类型 | 描述 |
| IN | 输入类型,调用存储过程时要传入的值,默认参数类型 |
| OUT | 输出类型,可以作为存储过程的返回值 |
| INOUT | 输入输出类型,即可以作为输入类型也可以作为输出类型 |

3.2.2 语法

sql 复制代码
-- 修改SQL语句结束标识符为 //  
DELIMITER //
-- 创建存储过程 
CREATE PROCEDURE 存储过程名 ([IN/OUT/INOUT 参数名 参数类型] [,...])
BEGIN
 -- SQL 语句 
END //
-- 修改SQL语句结束标识符为 ; 
DELIMITER ;

3.2.3 练习

示例1:传入一个分数的值,判定当前分数对应的等级

sql 复制代码
-- 创建存储过程 
CREATE PROCEDURE p3(IN score INT, OUT result VARCHAR(10)) 
BEGIN
 -- 判断 
 IF score >= 90 THEN
 SET result := '优秀';
 ELSEIF score >= 80 AND score < 90 THEN
 SET result := '良好';
 ELSEIF score >= 60 AND score < 80 THEN
 SET result := '及格';
 ELSE
 SET result := '不及格';
 END IF;
END;
-- 调⽤存储过程 
CALL p3 (88, @result);
-- 查看结果 
SELECT @result;

示例2:传入一个分数的值,在传入分数的基础上加10分,然后返回

sql 复制代码
-- 创建存储过程 
CREATE PROCEDURE p4(INOUT score INT) 
BEGIN
 -- 在原分数的基础上加10分 
 SET score := score + 10;
 
END;
SET @score := 98;
-- 调⽤存储过程 
CALL p4 (@score);
-- 查看结果 
SELECT @score;

3.3 CASE

3.3.1 语法

sql 复制代码
-- 语法⼀ 
CASE case_value
 WHEN when_value THEN statement_list
 [WHEN when_value THEN statement_list] ...
 [ELSE statement_list]
END CASE
sql 复制代码
-- 语法⼆ 
CASE
 WHEN search_condition THEN statement_list
 [WHEN search_condition THEN statement_list] ...
 [ELSE statement_list]
END CASE

3.3.2 练习

示例一:传入一个状态码,输出该状态码表示的含义。

sql 复制代码
-- 创建存储过程 
CREATE PROCEDURE p5(IN code INT, OUT result varchar(50)) 
BEGIN
 CASE code
 WHEN 0 THEN
 SET result := '成功';
 WHEN 10001 THEN
 SET result := '⽤⼾名或密码错误';
 WHEN 10002 THEN
 SET result := '您没有对应的权限';
 WHEN 20001 THEN
 SET result := '你传⼊的参数有误';
 WHEN 20002 THEN
SET result := '没有找到相应的结果';
 ELSE
 SET result := '服务器错误,请联系管理员';
 END CASE;
END;
-- 调⽤存储过程 
CALL p5(10001, @result);
-- 查看结果 
SELECT @result;

示例二:根据传入的月份,输出该月份属于哪个季度。

sql 复制代码
-- 创建存储过程 
CREATE PROCEDURE p6(IN month INT, OUT result varchar(50)) 
BEGIN
 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;
END;
-- 调⽤存储过程 
CALL p6(6, @result);
-- 查询结果 
SELECT @result;

3.4 循环

3.4.1 WHILE

3.4.1.1 语法

先判断条件表达式search_condition 是否为 TRUE ,如果条件成立,则执行循环体中的 statement_list。

sql 复制代码
WHILE search_condition DO
 statement_list
END WHILE;
3.4.1.2 练习
sql 复制代码
-- 创建存储过程 
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);

3.4.2 REPEAT

3.4.2.1 语法

先执行一次循环体中的 statement_list ,再先判断条件表达式 search_condition 是否为 TRUE ,如果条件成立,则继续执行循环体中的语句,如果条件不成立则退出循环。

statement_list 少少会执行一次,类似与C和JAVA中的do...while循环。

sql 复制代码
REPEAT
 statement_list
 UNTIL search_condition 
END REPEAT;
3.4.2.2 练习

示例:传入一个树n,计算从1累加到n的值。

sql 复制代码
-- 创建存储过程 
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);

3.4.3 LOOP

LOOP也可以实现一个简单的循环,并且当满足某个条件时终止当前循环或退出整个循环,通常配 合以下两个子句使用

LEAVE label :退出整个循环,类似于C++或JAVA中的 break;

ITERATE label :终止当前循环,进入下一次循环,类似于C++或JAVA中的 continue;

3.4.3.1 语法
sql 复制代码
[begin_label:] LOOP
 statement_list
END LOOP [end_label]
3.4.3.2 练习

示例:传入一个数n,计算从1累加到n的值。

sql 复制代码
-- 创建存储过程 
CREATE PROCEDURE p9(IN n INT) 
BEGIN
 -- 定义⼀个变量,表⽰结果 
 DECLARE total int DEFAULT 0;
 
 -- 累加 
 sum_label: LOOP
 -- 判断是否退出 
 IF n <= 0 THEN
 LEAVE sum_label; 
 END IF;
 
 -- 累加操作 
 SET total := total + n;
 SET n := n - 1;
 END LOOP sum_label;
 -- 查询结果 
 SELECT total;
END;
-- 调⽤存储过程 
CALL p9(100);

示例:传入一个数n,累加从1累加到n之间偶数的值。

sql 复制代码
-- 创建存储过程 
CREATE PROCEDURE p10(IN n INT) 
BEGIN
 -- 定义⼀个变量,表⽰结果 
 DECLARE total int DEFAULT 0;
 
 -- 累加 
 sum_label: LOOP
 -- 判断是否退出 
 IF n <= 0 THEN
 LEAVE sum_label; 
 END IF;
 
 -- 判断是否偶数 
 IF n % 2 = 1 THEN
 SET n := n - 1;
 -- 跳出本次循环 
 ITERATE sum_label; 
 END IF;
 
 -- 累加操作 
 SET total := total + n;
 SET n := n - 1;
 END LOOP sum_label;
 -- 查询结果 
 SELECT total;
END;
-- 调⽤存储过程 
CALL p10(100);

3.5 游标

MySQL中的游标是一种数据库对象,允许在存储过程和函数中对查询到的结果集进行逐行检索。

MySQL的游标是只读的,不能进行更新操作。

3.5.1 语法

使用游标之前必须先声明游标,之后使用OPEN、FETCH、CLOSE语句来打开游标、获取游标记录、关闭游标。

游标必须在条件处理程序之前被声明,并且变量必须在游标或条件处理程序之前被声明。

sql 复制代码
-- 声明游标 
DECLARE 游标名 CURSOR FOR 查询语句;
-- 打开游标 
OPEN 游标名;
-- 获取游标记录 
FETCH 游标名 INTO 变量[, 变量] ...;
-- 关闭游标 
CLOSE 游标名;

3.5.2 练习

示例:传入班级编号,查询学生表中属于该班级的学生信息,并将符合条件的学生信息写入到一张新表中。

新表及字段t_student_class(id,student_name,class_name)。

sql 复制代码
-- 创建存储过程 
CREATE PROCEDURE p11(IN class_id INT) 
BEGIN
 -- ⾸先声明变量 
 -- 定义学⽣姓名变量 
 DECLARE student_name VARCHAR(20);
 -- 定义班级名变量 
 DECLARE class_name VARCHAR(20);
 
 -- 定义游标 
 DECLARE s_cursor CURSOR FOR select s.`name` student_name, c.`name` 
class_name from student s, class c where s.class_id = c.id and s.class_id = 
class_id;
 
 -- 创建新表 
 DROP TABLE IF EXISTS t_student_class;
 CREATE TABLE IF NOT EXISTS t_student_class (
 id BIGINT PRIMARY KEY AUTO_INCREMENT,
 student_name VARCHAR(20),
 class_name VARCHAR(20)
 );
 
 -- 开启游标 
 OPEN s_cursor;
 -- 遍历结果集 
 WHILE TRUE DO
 -- 获取游标记录 
 FETCH s_cursor INTO student_name, class_name;
 -- 写⼊新表 
 INSERT INTO t_student_class VALUES (NULL, student_name, class_name);
 END WHILE;
 
 -- 关闭游标  
 CLOSE s_cursor;
 
END;
-- 调⽤存储过程 出错 
mysql> CALL p11(1);
ERROR 1329 (02000): No data - zero rows fetched, selected, or processed

由于while循环的退出条件是true,此时是一个死循环,当游标遍历完成之后继续向后遍历,发现没 有记录,所以报错,可以通过条件处理程序解决。

3.6 条件处理程序

定义条件是事先定义程序执行过程中可能遇到的问题。

处理程序定义了在遇到问题时应当采取的处理方式 。

使用条件处理程序保证存储过程或函数在遇到警告或错误时能继续执行,可以增强程序处理问题的 能力,避免程序异常停止运行。

3.6.1 语法

sql 复制代码
DECLARE handler_action HANDLER
 FOR condition_value [, condition_value] ...
 statement
handler_action: {
 CONTINUE -- 继续执⾏当前程序 
 | EXIT -- 终⽌执⾏当前程序 
}
condition_value: {
 mysql_error_code -- MYSQL错误码 
 | SQLSTATE [VALUE] sqlstate_value -- 状态码 
 | SQLWARNING -- 所有以01开头的SQLSTATE代码 
 | NOT FOUND -- 所有以02开头的SQLSTATE代码 
 | SQLEXCEPTION -- 所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE代码 
}

3.6.2 练习

示例:加入条件程序,解决游标越界问题。

sql 复制代码
-- 创建存储过程 
CREATE PROCEDURE p12(IN class_id INT) 
BEGIN
 -- ⾸先声明变量 
 -- 定义学⽣姓名变量 
 DECLARE student_name VARCHAR(20);
 -- 定义班级名变量 
DECLARE class_name VARCHAR(20);
 -- 定义游标结束标识 
 DECLARE is_done bool DEFAULT FALSE;
 
 -- 定义游标 
 DECLARE s_cursor CURSOR FOR select s.`name` student_name, c.`name` 
class_name from student s, class c where s.class_id = c.id and s.class_id = 
class_id;
 -- 定义条件处理程序 
 DECLARE CONTINUE HANDLER FOR NOT FOUND SET is_done := TRUE;
 
 -- 创建新表 
 DROP TABLE IF EXISTS t_student_class;
 CREATE TABLE IF NOT EXISTS t_student_class (
 id BIGINT PRIMARY KEY AUTO_INCREMENT,
 student_name VARCHAR(20),
 class_name VARCHAR(20)
 );
 
 -- 开启游标 
 OPEN s_cursor;
 -- 遍历结果集 
 read_loop: LOOP
 -- 获取游标记录 
 FETCH s_cursor INTO student_name, class_name;
 -- 退出循环 
 IF is_done THEN
 LEAVE read_loop; 
 END IF; 
 
 -- 写⼊到新表 
 INSERT INTO t_student_class VALUES (NULL, student_name,class_name);
 SELECT student_name;
 END LOOP read_loop;
 
 -- 关闭游标  
 CLOSE s_cursor;
 
END;
-- 调⽤存储过程 
CALL p12(1);

3.7 存储函数

MySQL存储函数是有返回值的存储过程,参数只能是IN类型,类似于内置函数。存储函数与存储过程的主要区别在于存储函数必须有返回值,而存储过程则不一定。

3.7.1 语法

在MySQL 8.0版本中,如果binlog是开启的,那么在定义存储函数时,需要指定characteristic特 性,否则会报错。

sql 复制代码
CREATE FUNCTION 存储函数名称 ([参数列表]) 
RETURNS type [characteristic ...]
BEGIN
 -- SQL语句 
 RETURN ...;
END;
characteristic:
 [NOT] DETERMINISTIC --表⽰相同的输⼊参数总是产⽣[不同]相同的结果 
| NO SQL --不包含SQL语句 
| READS SQL DATA --包含读取数据的语句,如select 
| MODIFIES SQL DATA -- 包含写⼊数据的语句,如update,delete 
-- 使⽤存储函数 
select 存储函数名称 ([参数列表]);

3.7.2 练习

示例:传入一个数n,计算从1累加到n的值。

sql 复制代码
-- 定义存储函数 
CREATE FUNCTION fun1(n INT)
 RETURNS INT
BEGIN
 -- 定义⼀个变量,表⽰结果 
 DECLARE total int DEFAULT 0;
 
 -- 累加 
 WHILE n > 0 DO
 SET total := total + n;
 SET n := n - 1;
 END WHILE;
 
 -- 返回结果 
 RETURN total;
END;






-- 定义存储函数 
CREATE FUNCTION fun1(n INT)
 RETURNS INT DETERMINISTIC -- 指定characteristic 
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);
相关推荐
Elias不吃糖4 小时前
MYSQL指令合集
数据库·mysql
!chen6 小时前
解决 Oracle 监听外网 IP
数据库·tcp/ip·oracle
LBuffer7 小时前
破解入门学习笔记题四十六
数据库·笔记·学习
chase。7 小时前
关于 nvidia-smi: no devices were found 解决方案
服务器·数据库·postgresql
几何心凉7 小时前
openGauss:多核时代企业级数据库的性能与高可用新标杆
前端·数据库·数据库开发
q***04058 小时前
在 Ubuntu 上安装 MySQL 的详细指南
mysql·ubuntu·adb
瑞思蕊萌8 小时前
redis实战篇--商品缓存模块
数据库·redis·缓存
AiXed9 小时前
PC微信协议之AES-192-GCM算法
前端·数据库·python
武子康11 小时前
Java-171 Neo4j 备份与恢复 + 预热与执行计划实战
java·开发语言·数据库·性能优化·系统架构·nosql·neo4j