当前位置: 首页 > news >正文

【MySQL学习】存储过程

目录

一、定义

二、基本语法

1.创建存储过程

2.删除存储过程

3.查看存储过程

三、控制语句

1.变量声明与赋值

四、游标(Cursor)

(1)声明游标

 (2)处理游标结束

(3)打开游标

(4)读取数据

(5)关闭景点

五、案例语句

1.值匹配

2.条件匹配

五、综合案例

第一步:创建表

第二步:创建存储过程:ProcessStudentGrades

第三步:测试


一、定义

存储过程是一组预编译的 SQL 语句集合,经过编译后存储在数据库服务器上。

可以把它想象成数据库中的一个自定义函数或脚本。

主要用于封装常用的、复杂的或重复性的数据库操作,简化应用程序开发,提高性能和安全性。

二、基本语法

1.创建存储过程

DELIMITER //
CREATE PROCEDURE procedure_name ([parameter_list])
BEGIN-- SQL 语句
END //
DELIMITER ;

(1)DELIMITER //  和  DELIMITER ;

存储过程内部通常包含分号 (;) 作为 SQL 语句的结束符,为了让 MySQL 客户端能够区分存储过程定义的结束 (END) 和内部 SQL 语句的结束,需要临时改变语句结束符(常用 //$$),定义完毕后再改回默认的分号 (;)。

(2) parameter_list:参数列表(可选),可以是输入、输出或输入输出参数。

  • IN: 输入参数(默认模式)。值由调用者传入存储过程,在过程中只读。
  • OUT: 输出参数。值在存储过程中被设置,并可以返回给调用者。调用时传入的变量会被修改。
  • INOUT: 输入输出参数。调用者传入初始值,存储过程可以读取和修改它,修改后的值可以返回给调用者。

示例1:计算公司给定部门的总人数

DELIMITER //
CREATE PROCEDURE get_employee_count_by_dept(IN dept_id INT, OUT emp_count INT)
BEGINSELECT COUNT(*) INTO emp_countFROM employeesWHERE department_id = dept_id;
END //
DELIMITER ;
CALL get_employee_count_by_dept(10, @count); 
SELECT @count; 

CALL get_employee_count_by_dept(10, @count); -- 将部门 10 的员工数放入 @count 变量

示例2:给定num,计算num+1的值

CREATE PROCEDURE increment(INOUT num INT)
BEGINSET num = num + 1;
END //
SET @value = 10;
CALL increment(@value);
SELECT @value; 

2.删除存储过程

DROP PROCEDURE [IF EXISTS] procedure_name;
使用 IF EXISTS避免因存储过程不存在而报错

3.查看存储过程

(1)查看数据库中的存储过程列表
SHOW PROCEDURE STATUS [WHERE condition];

示例1:

SHOW PROCEDURE STATUS;
  • 说明:没有WHERE子句,启动当前用户有权限访问的所有存储过程的信息。
  • 结果:返回所有存储过程的元数据,可能包含多个数据库中的存储过程。

示例二:

SHOW PROCEDURE STATUS WHERE Db = 'AlinJ';

(2)查看存储过程定义

SHOW CREATE PROCEDURE procedure_name;

三、控制语句

1.变量声明与赋值

(1)变量声明

在存储过程中定义局部变量,用于存储临时数据

DECLARE variable_name data_type [DEFAULT value];
  • 变量声明必须位于BEGIN ... END块的开头,且位于任何其他语句之前。
  • 变量的作用域仅限于当前BEGIN ... END块,块结束后变量自动推理。

(2)变量赋值

方法一:SET语句

SET variable_name = ALinJ;

方法二:SELECT ... INTO 语句

SELECT column_name INTO variable_name FROM table_name WHERE condition;

示例:将employees表中id为1的员工的姓名赋给变量

SELECT name INTO @emp_name FROM employees WHERE id = 1;
SELECT @emp_name;

(3)示例:统计用户表中用户数量,并根据数量进行状态设置。

DELIMITER //
CREATE PROCEDURE count_users()
BEGINDECLARE total_users INT DEFAULT 0;DECLARE user_status VARCHAR(20);SELECT COUNT(*) INTO total_users FROM users;SET user_status = IF(total_users > 100, 'High', 'Low');SELECT total_users AS 'Total Users', user_status AS 'User Status';
END //
DELIMITER ;CALL count_users();

2. 条件语句(IF-ELSE)

IF condition THEN-- 语句
ELSEIF condition THEN-- 语句
ELSE-- 语句
END IF;

示例1:根据用户分数判断是否及格

DELIMITER //
CREATE PROCEDURE check_score(IN score INT, OUT result VARCHAR(20))
BEGINIF score >= 60 THENSET result = 'Pass';ELSESET result = 'Fail';END IF;SELECT result AS 'Result';
END //
DELIMITER ;CALL check_score(75, @result);

示例2:根据分数评定等级(A、B、C)

DELIMITER //
CREATE PROCEDURE grade_score(IN score INT, OUT grade VARCHAR(10))
BEGINIF score >= 90 THENSET grade = 'A';ELSEIF score >= 75 THENSET grade = 'B';ELSEIF score >= 60 THENSET grade = 'C';ELSESET grade = 'Fail';END IF;SELECT grade AS 'Grade';
END //
DELIMITER ;CALL grade_score(85, @grade);

示例3:检查分数是否有效,并评估等级

DELIMITER //
CREATE PROCEDURE validate_score(IN score INT, OUT message VARCHAR(50))
BEGINIF score < 0 OR score > 100 THENSET message = 'Invalid score';ELSEIF score >= 60 THENSET message = 'Pass';ELSESET message = 'Fail';END IF;END IF;SELECT message AS 'Message';
END //
DELIMITER ;CALL validate_score(-10, @message);

3. 循环语句

(1)WHILE 循环

WHILE condition DO-- 语句
END WHILE;
  • 条件为真时执行循环体。
  • 条件在循环开始前检查,可能一次都不执行。

示例:插入 1 到 5 的数字到表中。

CREATE TABLE numbers (num INT);DELIMITER //
CREATE PROCEDURE insert_numbers()
BEGINDECLARE i INT DEFAULT 1;WHILE i <= 5 DOINSERT INTO numbers VALUES (i);SET i = i + 1;END WHILE;SELECT * FROM numbers;
END //
DELIMITER ;CALL insert_numbers();

(2)REPEAT 循环

REPEAT-- 语句
UNTIL condition
END REPEAT;
  • 先执行循环体,再检查条件。
  • 至少执行一次,条件为真时退出循环。
示例:计算 1 到 5 的累加和。
DELIMITER //
CREATE PROCEDURE calculate_sum(OUT total INT)
BEGINDECLARE i INT DEFAULT 1;SET total = 0;REPEATSET total = total + i;SET i = i + 1;UNTIL i > 5END REPEAT;SELECT total AS 'Total Sum';
END //
DELIMITER ;CALL calculate_sum(@total);

(3)LOOP 循环

[label:] LOOP-- 语句IF condition THENLEAVE label; -- 相当于breakEND IF;-- ITERATE label; -- 相当于continue
END LOOP [label];
  • 无条件循环,必须通过LEAVE语句退出。
  • ITERATE语句可跳到下一次循环。
  • label是可选的循环标签,用于LEAVE和ITERATE。

示例1:插入 1 到 5 的数字,并跳过 3

DELIMITER //
CREATE PROCEDURE insert_numbers_with_skip()
BEGINDECLARE i INT DEFAULT 1;my_loop: LOOPIF i > 5 THENLEAVE my_loop;END IF;IF i = 3 THENSET i = i + 1;ITERATE my_loop;END IF;INSERT INTO numbers VALUES (i);SET i = i + 1;END LOOP my_loop;SELECT * FROM numbers;
END //
DELIMITER ;CALL insert_numbers_with_skip();

四、游标(Cursor)

游标是 MySQL 存储过程中用于处理查询结果集的工具,允许逐行读取和操作查询结果。

基本使用步骤:

  • 声明指标:用于存储游标读取的数据和控制循环的标志。
  • 声明游标:绑定一个SELECT语句。
  • 声明HANDLER:处理游标读取结束。
  • 打开景点。
  • 循环读取数据:用FETCH读取每一行,处理逻辑。
  • 关闭游标。

(1)声明游标

CLOSE cursor_name;
DECLARE cursor_name CURSOR FOR select_statement;
  • 游标必须绑定一个SELECT语句。
  • 声明必须在存储过程的BEGIN ... END块开头,在变量声明之后。

 (2)处理游标结束

  • 问题:当游标读取到最后一行后,继续FETCH会导致错误。
  • 解决方法:使用HANDLER处理NOT FOUND条件。
DECLARE CONTINUE HANDLER FOR NOT FOUND SET variable = value;

(3)打开游标

OPEN cursor_name;
  • 游标后,MySQL 会执行SELECT语句,并将结果集加载到内存中。
  • 此时游标指向结果集的第一行之前。

(4)读取数据

从游标中读取一行数据,将数据存储到指定的变量中。

FETCH cursor_name INTO variable_list;

(5)关闭景点

CLOSE cursor_name;

示例1:遍历用户表,记录每个用户的ID和名称到日志表

第一步:准备数据

CREATE TABLE users (id INT, name VARCHAR(50));
CREATE TABLE logs (log_id INT AUTO_INCREMENT PRIMARY KEY, message VARCHAR(100));INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie');

第二步:创建存储过程

DELIMITER //
CREATE PROCEDURE log_users()
BEGIN-- 声明变量DECLARE done INT DEFAULT 0;DECLARE u_id INT;DECLARE u_name VARCHAR(50);-- 声明游标DECLARE cur CURSOR FOR SELECT id, name FROM users;-- 声明 HANDLERDECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;-- 打开游标OPEN cur;-- 循环读取read_loop: LOOPFETCH cur INTO u_id, u_name;IF done THENLEAVE read_loop;END IF;-- 插入日志INSERT INTO logs (message) VALUES (CONCAT('User: ', u_id, ', Name: ', u_name));END LOOP read_loop;-- 关闭游标CLOSE cur;-- 查看结果SELECT * FROM logs;
END //
DELIMITER ;

第三步:调用

CALL log_users();

示例二:遍历用户表,给分数低于60的用户加10分,并记录操作日志。

第一步:准备数据

CREATE TABLE users (id INT, name VARCHAR(50), score INT);
CREATE TABLE logs (log_id INT AUTO_INCREMENT PRIMARY KEY, message VARCHAR(100), log_time DATETIME);INSERT INTO users VALUES (1, 'Alice', 50), (2, 'Bob', 70), (3, 'Charlie', 45);

第二步:创建存储过程

DELIMITER //
CREATE PROCEDURE update_scores()
BEGINDECLARE done INT DEFAULT 0;DECLARE u_id INT;DECLARE u_score INT;DECLARE cur CURSOR FOR SELECT id, score FROM users;DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;OPEN cur;update_loop: LOOPFETCH cur INTO u_id, u_score;IF done THENLEAVE update_loop;END IF;-- 检查分数是否低于 60IF u_score < 60 THENSET u_score = u_score + 10;UPDATE users SET score = u_score WHERE id = u_id;INSERT INTO logs (message, log_time) VALUES (CONCAT('User ', u_id, ' score updated to ', u_score), NOW());END IF;END LOOP update_loop;CLOSE cur;SELECT * FROM users;SELECT * FROM logs;
END //
DELIMITER ;

第三步:调用

CALL update_scores();

示例3:将users表中的数据迁移到users_backup表,跳过分数低于50的用户。

第一步:准备数据

CREATE TABLE users (id INT, name VARCHAR(50), score INT);
CREATE TABLE users_backup (id INT, name VARCHAR(50), score INT);INSERT INTO users VALUES (1, 'Alice', 50), (2, 'Bob', 70), (3, 'Charlie', 40);

第二步:创建存储过程

DELIMITER //
CREATE PROCEDURE backup_users()
BEGINDECLARE done INT DEFAULT 0;DECLARE u_id INT;DECLARE u_name VARCHAR(50);DECLARE u_score INT;DECLARE cur CURSOR FOR SELECT id, name, score FROM users;DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;OPEN cur;backup_loop: LOOPFETCH cur INTO u_id, u_name, u_score;IF done THENLEAVE backup_loop;END IF;-- 跳过分数低于 50 的用户IF u_score < 50 THENITERATE backup_loop;END IF;-- 插入到备份表INSERT INTO users_backup VALUES (u_id, u_name, u_score);END LOOP backup_loop;CLOSE cur;SELECT * FROM users_backup;
END //
DELIMITER ;

第三步:调用

CALL backup_users();

五、案例语句

1.值匹配

CASE expressionWHEN value1 THEN-- 语句WHEN value2 THEN-- 语句[ELSE-- 语句]
END CASE;

示例:根据用户等级设置描述。

DELIMITER //
CREATE PROCEDURE describe_level(IN level CHAR(1), OUT description VARCHAR(20))
BEGINCASE levelWHEN 'A' THENSET description = 'Excellent';WHEN 'B' THENSET description = 'Good';WHEN 'C' THENSET description = 'Average';ELSESET description = 'Unknown';END CASE;SELECT description AS 'Description';
END //
DELIMITER ;CALL describe_level('B', @description);

2.条件匹配

CASEWHEN condition1 THEN-- 语句WHEN condition2 THEN-- 语句[ELSE-- 语句]
END CASE;

示例:根据分数评定等级。

DELIMITER //
CREATE PROCEDURE grade_score_case(IN score INT, OUT grade VARCHAR(10))
BEGINCASEWHEN score >= 90 THENSET grade = 'A';WHEN score >= 75 THENSET grade = 'B';WHEN score >= 60 THENSET grade = 'C';ELSESET grade = 'Fail';END CASE;SELECT grade AS 'Grade';
END //
DELIMITER ;CALL grade_score_case(85, @grade);

五、综合案例

我将创建一个存储过程ProcessStudentGrades,用于管理学生的成绩数据,支持以下功能:

  • 批量更新成绩(加分或减分)。
  • 根据成绩评定等级(A、B、C、Fail)。
  • 记录操作日志(例如,谁的成绩被更新、等级如何)。
  • 如果操作失败,记录错误日志。

第一步:创建表

CREATE TABLE students (id INT PRIMARY KEY,name VARCHAR(50),grade INT
);CREATE TABLE logs (log_id INT AUTO_INCREMENT PRIMARY KEY,message VARCHAR(255),log_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);INSERT INTO students (id, name, grade)
VALUES (1, 'Alice', 85),(2, 'Bob', 55),(3, 'Charlie', 92);

第二步:创建存储过程:ProcessStudentGrades

DELIMITER //CREATE PROCEDURE ProcessStudentGrades(IN action VARCHAR(20),  -- 操作类型:ADD(加分)、SUBTRACT(减分)、GRADE(评定等级)IN score_change INT,    -- 分数变化值(加分或减分)OUT result_message VARCHAR(100) 
)
BEGIN-- 声明变量DECLARE done INT DEFAULT 0;DECLARE s_id INT;DECLARE s_name VARCHAR(50);DECLARE s_grade INT;DECLARE new_grade INT;DECLARE grade_level VARCHAR(10);DECLARE loop_counter INT DEFAULT 0;-- 声明游标DECLARE cur CURSOR FOR SELECT id, name, grade FROM students;-- 声明游标结束处理器DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;-- 声明错误处理器DECLARE EXIT HANDLER FOR SQLEXCEPTIONBEGINSET result_message = 'Error occurred during operation.';INSERT INTO logs (message) VALUES (CONCAT('Error: ', result_message, ' during action: ', action));END;SET result_message = 'Operation completed successfully.';-- 使用 CASE 语句处理操作类型CASE UPPER(action)WHEN 'ADD' THEN-- 使用游标遍历学生,加分OPEN cur;add_loop: LOOPFETCH cur INTO s_id, s_name, s_grade;IF done THENLEAVE add_loop;END IF;-- 计算新分数SET new_grade = s_grade + score_change;-- 确保分数在 0-100 范围内IF new_grade > 100 THENSET new_grade = 100;END IF;-- 更新分数UPDATE students SET grade = new_grade WHERE id = s_id;INSERT INTO logs (message) VALUES (CONCAT('Student ', s_name, ' (ID: ', s_id, ') grade updated from ', s_grade, ' to ', new_grade));END LOOP add_loop;CLOSE cur;WHEN 'SUBTRACT' THEN-- 使用 WHILE 循环遍历学生,减分SET done = 0; OPEN cur;WHILE NOT done DOFETCH cur INTO s_id, s_name, s_grade;IF done THENLEAVE;END IF;-- 计算新分数SET new_grade = s_grade - score_change;-- 确保分数在 0-100 范围内IF new_grade < 0 THENSET new_grade = 0;END IF;-- 更新分数UPDATE students SET grade = new_grade WHERE id = s_id;INSERT INTO logs (message) VALUES (CONCAT('Student ', s_name, ' (ID: ', s_id, ') grade updated from ', s_grade, ' to ', new_grade));END WHILE;CLOSE cur;WHEN 'GRADE' THEN-- 使用 REPEAT 循环遍历学生,评定等级SET done = 0; OPEN cur;REPEATFETCH cur INTO s_id, s_name, s_grade;IF NOT done THEN-- 使用 IF-ELSE 评定等级IF s_grade >= 90 THENSET grade_level = 'A';ELSEIF s_grade >= 75 THENSET grade_level = 'B';ELSEIF s_grade >= 60 THENSET grade_level = 'C';ELSESET grade_level = 'Fail';END IF;-- 记录等级日志INSERT INTO logs (message) VALUES (CONCAT('Student ', s_name, ' (ID: ', s_id, ') grade: ', s_grade, ', level: ', grade_level));-- 简单计数SET loop_counter = loop_counter + 1;END IF;UNTIL doneEND REPEAT;CLOSE cur;SET result_message = CONCAT('Graded ', loop_counter, ' students successfully.');ELSESIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid action specified.';END CASE;
END //DELIMITER ;

第三步:测试

-- 加分操作
CALL ProcessStudentGrades('ADD', 10, @msg);
SELECT @msg AS Result;
SELECT * FROM students;
SELECT * FROM logs;-- 减分操作
CALL ProcessStudentGrades('SUBTRACT', 5, @msg);
SELECT @msg AS Result;
SELECT * FROM students;
SELECT * FROM logs;-- 评定等级
CALL ProcessStudentGrades('GRADE', NULL, @msg);
SELECT @msg AS Result;
SELECT * FROM logs;-- 无效操作
CALL ProcessStudentGrades('INVALID', NULL, @msg);
SELECT @msg AS Result;
SELECT * FROM logs;

相关文章:

  • 学习笔记十四——一文看懂 Rust 迭代器
  • SIMULIA-Abaqus有限元分析软件针对汽车行业的解决方案
  • 通信算法之266: 无人机信号带宽计算
  • 软件需求说明书模板
  • 遨游防爆手机:构筑煤矿安全通讯的数字护盾
  • 【Java学习笔记】运算符
  • 【星海随笔】Python-JSON数据的处理
  • C++中类拷贝、赋值与销毁详解
  • FairyGUI图标文字合批失败的原因
  • HarmonyOS 5.0应用开发——五子棋游戏(鸿蒙版)开发
  • 【双指针】专题:LeetCode 611题解——有效三角形的个数
  • OpenCV 图形API(39)图像滤波----同时计算图像在 X 和 Y 方向上的一阶导数函数SobelXY()
  • 企业采购平台搭建指南:从流程重构到生态协同的数字化转型路径
  • 【学习笔记】Taming 3DGS泛读
  • 【android bluetooth 协议分析 02】【bluetooth hal 层详解 1】【uart 介绍】
  • 【病毒分析】定向财务的钓鱼木马分析
  • 过滤器及拦截器
  • 一文掌握RK3568开发板Android13挂载Windows共享目录
  • C++Cherno 学习笔记day21 [86]-[90] 持续集成、静态分析、参数计算顺序、移动语义、stdmove与移动赋值操作符
  • 蓝桥杯 8. 分巧克力
  • 南浔哪有做网站的/营销型网站建设步骤
  • 网站建设名词解释/国家职业技能培训官网
  • 陕西营销型网站制作/百度投诉中心24小时电话
  • 怎么做网站的内部链接/宁波网站建设方案推广
  • 电商网站建设方案/进行优化
  • 静态网站与动态网站区别/海外营销公司