1.存储过程
1.存储过程的概念
MySQL 存储过程(Stored Procedure)是一组预编译的 SQL 语句和可选的控制流语句,它们被作为一个单元存储在数据库中。存储过程可以接受输入参数、返回输出参数,并且可以包含复杂的业务逻辑,包括条件判断、循环等结构。它们通过提供封装好的代码块来简化重复性任务,提高代码复用性和维护性。
存储过程的主要特点
- 预编译:当创建存储过程时,MySQL 会对其进行语法检查并编译,这使得调用存储过程比直接执行相同的 SQL 语句更快。
- 减少网络流量:因为存储过程是在服务器端执行的,所以只需要发送一次请求就可以完成多个操作,减少了客户端与服务器之间的数据传输量。
- 增强安全性:可以通过授予用户对特定存储过程的执行权限而不是表级别的权限来限制访问,从而增加安全性。
- 模块化编程:允许将复杂的逻辑分解成较小的部分,便于管理和维护。
- 事务支持:存储过程中可以包含事务控制语句,如
BEGIN
,COMMIT
和ROLLBACK
,确保一系列操作要么全部成功,要么全部失败。 - 输入/输出参数:可以定义 IN、OUT 或者 INOUT 参数,用于传递数据进出存储过程。
2.创建存储过程
以下是一个简单的例子,展示如何创建一个名为 GetEmployeeById
的存储过程,它根据员工 ID 获取员工信息:
-- 更改语句结束符为 $$,以便在存储过程中使用分号作为语句结束符
DELIMITER $$
-- 创建名为 GetEmployeeById 的存储过程
-- 该存储过程接受一个整数类型的输入参数 emp_id,并根据此 ID 查询 employees 表中的记录
CREATE PROCEDURE GetEmployeeById(IN emp_id INT)
BEGIN
-- 定义存储过程的主体
-- 使用 SELECT 语句查询 employees 表中 id 等于输入参数 emp_id 的所有列数据
SELECT * FROM employees WHERE id = emp_id;
END$$
-- 恢复默认的语句结束符为分号 ;
DELIMITER ;
-- 注释:存储过程 GetEmployeeById 已创建完毕。
-- 通过 CALL GetEmployeeById(员工ID) 可以调用此存储过程并传入具体的员工ID来获取员工信息。
在这个例子中:
DELIMITER $$
更改了语句结束符为$$
,以便可以在存储过程中使用分号作为语句结束符。CREATE PROCEDURE GetEmployeeById(IN emp_id INT)
定义了一个名为GetEmployeeById
的存储过程,接收一个整数类型的输入参数emp_id
。BEGIN ... END
是存储过程体,其中包含了要执行的 SQL 语句。
3.调用存储过程
一旦创建了存储过程,你可以使用 CALL
语句来调用它:
CALL GetEmployeeById(1);
这行命令将会执行 GetEmployeeById
存储过程,并传入值 1
作为 emp_id
参数,返回所有字段的数据行,其中 id
等于 1
。
4.删除存储过程
如果不再需要某个存储过程,可以使用 DROP PROCEDURE
语句将其删除:
DROP PROCEDURE IF EXISTS GetEmployeeById;
这行命令会检查是否存在名为 GetEmployeeById
的存储过程,并在存在的情况下将其删除。
注意事项
- 性能考虑:虽然存储过程通常能提高性能,但在某些情况下,过度使用或不当设计可能会导致性能下降。因此,应该合理评估其使用场景。
- 调试困难:相比于应用程序代码,存储过程的调试可能更为复杂,尤其是在处理错误和异常时。
- 版本控制:由于存储过程直接存在于数据库中,因此需要特别注意版本控制系统中的管理,以避免不同环境之间的不一致性。
希望这些信息能够帮助您理解 MySQL 存储过程的概念。
2.存储函数
MySQL 存储函数(Stored Function)类似于存储过程,但它们的主要区别在于存储函数必须返回一个值,并且可以在 SQL 语句中像内置函数一样使用。这使得存储函数非常适合用于计算和返回特定结果,而不仅仅是执行一系列操作。
创建存储函数
创建存储函数的语法与创建存储过程相似,但需要指定返回的数据类型,并且函数体中必须包含 RETURN
语句来返回值。
示例:创建一个简单的存储函数
假设我们想要创建一个名为 GetEmployeeCount
的存储函数,它返回 employees
表中的员工总数:
-- 更改语句结束符为 $$,以便在存储函数中使用分号作为语句结束符
DELIMITER $$
-- 创建名为 GetEmployeeCount 的存储函数
-- 参数:无
-- 返回值:INT - 返回 employees 表中的员工总数
-- 属性:DETERMINISTIC - 表示只要输入相同,函数的结果就会一致
-- 目标:提供一个简单的方法来获取当前数据库中所有员工的数量
CREATE FUNCTION GetEmployeeCount()
RETURNS INT
DETERMINISTIC
BEGIN
-- 开始函数体
-- 声明一个局部变量 emp_count 来保存员工数量
-- 注意:局部变量的作用域仅限于 BEGIN...END 块内
DECLARE emp_count INT;
-- 执行查询以计算 employees 表中的记录数,并将结果存入 emp_count 变量
-- 使用 INTO 关键字可以直接将查询结果存入变量中
-- 注意:确保 'employees' 表结构和数据完整性,以保证查询结果的准确性
SELECT COUNT(*) INTO emp_count FROM employees;
-- 返回存储在 emp_count 中的员工数量
-- 注意:RETURN 语句必须是函数体的最后一行,因为它会立即退出函数并返回值
RETURN emp_count;
-- 结束函数体
END$$
-- 恢复默认的语句结束符为分号 ;
DELIMITER ;
-- 注释:存储函数 GetEmployeeCount 创建完成。
-- 调用方法:可以在 SQL 查询中像使用内置函数一样调用此函数,例如:
-- SELECT GetEmployeeCount() AS employee_count;
-- 这条语句将会返回 employees 表中的总员工数,并将其显示为 employee_count 列。
在这个例子中:
CREATE FUNCTION GetEmployeeCount()
定义了一个名为GetEmployeeCount
的存储函数。RETURNS INT
指定了该函数将返回一个整数值。DETERMINISTIC
关键字表示只要输入相同,函数的结果就会一致。如果函数的行为不是确定性的,则可以省略这个关键字或使用NOT DETERMINISTIC
。BEGIN ... END
是函数体,其中包含了要执行的 SQL 语句和逻辑。RETURN emp_count;
语句用来返回查询到的员工数量。
使用存储函数
一旦创建了存储函数,你就可以在 SQL 语句中像使用内置函数一样调用它。例如:
SELECT GetEmployeeCount() AS employee_count;
这行命令将会调用 GetEmployeeCount
函数,并将返回的员工数量显示为 employee_count
列。
更复杂示例:带参数的存储函数
如果你希望根据部门 ID 获取某个部门的员工数量,可以创建一个带有参数的存储函数:
-- 更改语句结束符为 $$,以便在存储函数中使用分号作为语句结束符
DELIMITER $$
-- 创建名为 GetDepartmentEmployeeCount 的存储函数
-- 该函数根据提供的部门ID返回对应部门的员工总数
CREATE FUNCTION GetDepartmentEmployeeCount(dept_id INT)
RETURNS INT
DETERMINISTIC
BEGIN
-- 声明一个局部变量 dept_emp_count 来保存部门员工数量
DECLARE dept_emp_count INT;
-- 查询 employees 表中 department_id 等于输入参数 dept_id 的记录数,
-- 并将结果赋值给 dept_emp_count 变量
SELECT COUNT(*) INTO dept_emp_count
FROM employees
WHERE department_id = dept_id;
-- 返回存储在 dept_emp_count 中的部门员工数量
RETURN dept_emp_count;
END$$
-- 恢复默认的语句结束符为分号 ;
DELIMITER ;
-- 注释:存储函数 GetDepartmentEmployeeCount 创建完成。
-- 该函数可以在 SQL 查询中像内置函数一样使用,例如:
-- SELECT GetDepartmentEmployeeCount(1) AS dept_employee_count;
然后你可以这样调用它:
SELECT GetDepartmentEmployeeCount(1) AS dept_employee_count;
这将返回部门 ID 为 1 的员工数量。
删除存储函数
如果不再需要某个存储函数,可以使用 DROP FUNCTION
语句将其删除:
DROP FUNCTION IF EXISTS GetEmployeeCount;
这行命令会检查是否存在名为 GetEmployeeCount
的存储函数,并在存在的情况下将其删除。
注意事项
- 事务支持:存储函数不能包含
COMMIT
或ROLLBACK
等事务控制语句,因为它们可能会影响数据库的一致性。 - 限制修改数据:默认情况下,存储函数不允许修改表中的数据。如果你想在存储函数中修改数据,需要在创建时声明
MODIFIES SQL DATA
。 - 性能考虑:虽然存储函数可以提高代码复用性和维护性,但在某些情况下,过度使用或不当设计可能会导致性能下降。因此,应该合理评估其使用场景。
- 调试困难:相比于应用程序代码,存储函数的调试可能更为复杂,尤其是在处理错误和异常时。
- 版本控制:由于存储函数直接存在于数据库中,因此需要特别注意版本控制系统中的管理,以避免不同环境之间的不一致性。
通过这些信息,你应该能够理解如何在 MySQL 中创建和使用存储函数了。
3.变量的使用
在 MySQL 存储过程中,变量用于存储临时数据或计算结果,并可以在存储过程的执行过程中使用。定义和使用变量可以帮助简化复杂的逻辑、提高代码可读性并增强功能。下面我将通过一个示例来展示如何在 MySQL 存储过程中定义和使用变量。
示例:计算两个日期之间的天数
假设我们有一个需求,需要创建一个存储过程 CalculateDaysBetweenDates
,它接受两个日期作为输入参数,并返回这两个日期之间的天数。我们将在这个存储过程中使用变量来存储中间计算结果。
创建存储过程
DELIMITER $$
CREATE PROCEDURE CalculateDaysBetweenDates(IN start_date DATE, IN end_date DATE, OUT days INT)
BEGIN
-- 声明一个局部变量来保存天数差
DECLARE day_difference INT;
-- 计算两个日期之间的天数差异,并将其存储到局部变量中
SET day_difference = DATEDIFF(end_date, start_date);
-- 将计算的结果赋值给输出参数
SET days = day_difference;
-- 可选:添加一些额外的逻辑,比如检查是否为负数(如果开始日期大于结束日期)
IF day_difference < 0 THEN
SET days = -day_difference;
END IF;
END$$
DELIMITER ;
在这个例子中:
DECLARE day_difference INT;
定义了一个名为day_difference
的局部变量,用来存储两个日期之间的天数。SET day_difference = DATEDIFF(end_date, start_date);
使用DATEDIFF
函数计算两个日期之间的天数,并将结果存储到day_difference
变量中。SET days = day_difference;
将局部变量中的值赋给输出参数days
。IF day_difference < 0 THEN ... END IF;
添加了一段逻辑,确保即使开始日期晚于结束日期,返回的天数也是正数。
调用存储过程
要调用这个存储过程并获取结果,你可以这样做:
-- 声明一个变量来接收输出参数
SET @days_between = 0;
-- 调用存储过程,并传递输入参数以及输出参数的引用
CALL CalculateDaysBetweenDates('2024-01-01', '2024-12-31', @days_between);
-- 查询输出参数的值
SELECT @days_between AS days;
这段代码将会调用 CalculateDaysBetweenDates
存储过程,并传入起始日期 '2024-01-01'
和结束日期 '2024-12-31'
。最终,它会返回两个日期之间的天数,并显示出来。
注意事项
- 局部变量的作用域:局部变量仅在声明它们的 BEGIN…END 块内有效。如果你在嵌套的 BEGIN…END 块中再次声明同名变量,则新声明的变量将覆盖外部块中的同名变量。
- 变量命名:为了区分局部变量和表中的列名,通常会在局部变量前面加上前缀,例如
v_
或者var_
。 - 默认值:可以为局部变量指定默认值,如
DECLARE v_name VARCHAR(50) DEFAULT 'Unknown';
。 - 错误处理:可以结合使用
DECLARE CONTINUE HANDLER
或DECLARE EXIT HANDLER
来处理可能发生的错误。
通过这个例子,你应该能够理解如何在 MySQL 存储过程中定义和使用变量了。
4.游标的使用
在 MySQL 中,游标(Cursor)允许你逐行处理查询结果集中的每一行数据。游标通常用于存储过程或函数中,当你需要对结果集的每一行执行复杂的操作时特别有用。下面通过一个具体的示例来展示如何在 MySQL 存储过程中使用游标。
示例:更新员工工资
假设我们有一个 employees
表,其中包含员工的信息,包括他们的 ID 和当前工资。我们希望创建一个存储过程,该存储过程可以遍历所有员工,并根据某些条件(例如部门)增加他们的工资。我们将使用游标来逐行处理每个员工的数据。
创建存储过程
DELIMITER $$
CREATE PROCEDURE UpdateEmployeeSalaries(IN dept_id INT, IN increase_rate DECIMAL(5,2))
BEGIN
-- 声明变量
DECLARE done INT DEFAULT FALSE;
DECLARE emp_id INT;
DECLARE current_salary DECIMAL(10,2);
-- 声明游标,用于遍历指定部门的所有员工
DECLARE cur CURSOR FOR SELECT id, salary FROM employees WHERE department_id = dept_id;
-- 声明继续处理器,当没有更多行时设置 done 为 TRUE
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- 打开游标
OPEN cur;
read_loop: LOOP
-- 获取下一行数据并赋值给变量
FETCH cur INTO emp_id, current_salary;
-- 如果没有更多行,则退出循环
IF done THEN
LEAVE read_loop;
END IF;
-- 更新员工工资
UPDATE employees
SET salary = current_salary + (current_salary * increase_rate)
WHERE id = emp_id;
-- 可选:添加日志或其他逻辑
-- INSERT INTO salary_update_log (emp_id, old_salary, new_salary) VALUES (emp_id, current_salary, current_salary + (current_salary * increase_rate));
END LOOP;
-- 关闭游标
CLOSE cur;
END$$
DELIMITER ;
在这个例子中:
- 声明变量:定义了几个局部变量来保存从游标中获取的数据以及控制循环结束的标志。
- 声明游标:使用
DECLARE CURSOR
语句定义了一个名为cur
的游标,它将遍历employees
表中department_id
等于输入参数dept_id
的所有记录。 - 声明处理器:使用
DECLARE CONTINUE HANDLER
定义了一个处理器,当游标遇到NOT FOUND
条件(即没有更多行可读取)时,将done
设置为TRUE
。 - 打开游标:使用
OPEN
语句打开游标以开始遍历结果集。 - 循环读取数据:使用
FETCH
语句从游标中读取一行数据到局部变量中,并检查是否已经到达结果集末尾。如果没有,则执行更新操作。 - 关闭游标:使用
CLOSE
语句关闭游标以释放资源。
调用存储过程
要调用这个存储过程并更新特定部门员工的工资,你可以这样做:
-- 调用存储过程,传入部门ID和加薪比例
CALL UpdateEmployeeSalaries(1, 0.10);
这段代码将会调用 UpdateEmployeeSalaries
存储过程,并传入部门 ID 1
和加薪比例 10%
。最终,它会遍历所有属于部门 1
的员工,并将他们的工资提高 10%
。
注意事项
- 性能考虑:虽然游标提供了一种灵活的方式来逐行处理数据,但它们可能不如批量更新高效。因此,在选择使用游标之前,请评估是否有更高效的方法实现相同的目标。
- 锁定问题:使用游标时,可能会导致长时间持有表锁,特别是在大表上。这可能会影响并发性。确保你的应用能够容忍这种锁定行为。
- 错误处理:可以通过声明多个处理器来增强错误处理能力,确保即使发生异常也能正确关闭游标并清理资源。
- 事务管理:如果需要保证原子性,可以在存储过程中显式地管理事务,使用
START TRANSACTION
,COMMIT
, 和ROLLBACK
语句。
通过这个例子,你应该能够理解如何在 MySQL 存储过程中定义和使用游标了。
5.流程控制
在 MySQL 存储过程中,流程控制语句允许你根据条件或循环结构来执行不同的逻辑。这使得存储过程更加灵活和强大,可以处理复杂的业务逻辑。以下是几种常见的流程控制语句及其用法示例:IF...THEN...ELSE
, CASE
, LOOP
, WHILE
, 和 REPEAT
。
1. IF…THEN…ELSE
IF...THEN...ELSE
语句用于基于条件判断执行不同的代码块。它支持简单的 IF
、带 ELSE
的 IF
以及嵌套的 IF...ELSEIF...ELSE
结构。
示例:根据员工绩效调整工资
-- 更改语句结束符为 $$,以便在存储过程中使用分号作为语句结束符
DELIMITER $$
-- 创建名为 AdjustSalary 的存储过程
-- 参数:
-- IN emp_id INT: 输入参数,表示要调整工资的员工ID
-- IN performance_score INT: 输入参数,表示员工的绩效评分
-- 目标:根据员工的绩效评分调整其工资
CREATE PROCEDURE AdjustSalary(IN emp_id INT, IN performance_score INT)
BEGIN
-- 声明一个局部变量 current_salary 来保存当前工资
DECLARE current_salary DECIMAL(10,2);
-- 从 employees 表中查询指定员工的当前工资,并将其赋值给 current_salary 变量
-- 注意:确保 'id' 是唯一标识员工的主键,以保证查询结果的准确性
SELECT salary INTO current_salary FROM employees WHERE id = emp_id;
-- 使用 IF...THEN...ELSE 进行条件判断,根据不同的绩效评分调整工资
-- 绩效评分越高,工资增加的比例越大
IF performance_score >= 90 THEN
-- 如果绩效评分大于等于 90,则将工资增加 20%
UPDATE employees SET salary = current_salary * 1.2 WHERE id = emp_id;
ELSEIF performance_score >= 75 THEN
-- 如果绩效评分介于 75 和 89 之间,则将工资增加 10%
UPDATE employees SET salary = current_salary * 1.1 WHERE id = emp_id;
ELSE
-- 如果绩效评分低于 75,则将工资增加 5%
UPDATE employees SET salary = current_salary * 1.05 WHERE id = emp_id;
END IF;
END$$
-- 恢复默认的语句结束符为分号 ;
DELIMITER ;
-- 注释:存储过程 AdjustSalary 创建完成。
-- 调用方法:CALL AdjustSalary(员工ID, 绩效评分);
-- 例如:CALL AdjustSalary(1, 92); 将会根据绩效评分 92 更新员工 ID 为 1 的工资。
在这个例子中,我们根据员工的绩效分数(performance_score
)来决定他们工资的增长比例。
2. CASE
CASE
语句提供了更简洁的方式来实现多条件分支逻辑。它可以有简单形式和搜索形式两种。
示例:根据部门名称获取部门ID
-- 更改语句结束符为 $$,以便在存储过程中使用分号作为语句结束符
DELIMITER $$
-- 创建名为 GetDepartmentIdByName 的存储过程
-- 参数:
-- IN dept_name VARCHAR(100): 输入参数,表示要查询的部门名称
-- OUT dept_id INT: 输出参数,用于返回对应的部门ID
-- 目标:根据提供的部门名称返回相应的部门ID
CREATE PROCEDURE GetDepartmentIdByName(IN dept_name VARCHAR(100), OUT dept_id INT)
BEGIN
-- 使用 CASE 语句进行多条件判断,根据输入的部门名称设置输出参数 dept_id
-- 注意:这里的映射关系是硬编码的,实际应用中应考虑从数据库表中动态获取部门信息
CASE dept_name
WHEN 'Sales' THEN
-- 如果部门名称为 'Sales',则设置 dept_id 为 1
SET dept_id = 1;
WHEN 'Engineering' THEN
-- 如果部门名称为 'Engineering',则设置 dept_id 为 2
SET dept_id = 2;
WHEN 'HR' THEN
-- 如果部门名称为 'HR',则设置 dept_id 为 3
SET dept_id = 3;
ELSE
-- 默认情况下,如果未找到匹配的部门名称,则设置 dept_id 为 0
SET dept_id = 0; -- 默认值表示未找到匹配的部门
END CASE;
END$$
-- 恢复默认的语句结束符为分号 ;
DELIMITER ;
-- 注释:存储过程 GetDepartmentIdByName 创建完成。
-- 调用方法:CALL GetDepartmentIdByName('部门名称', @dept_id);
-- 例如:CALL GetDepartmentIdByName('Sales', @dept_id); 然后 SELECT @dept_id; 将会返回部门 ID 1。
或者使用搜索形式的 CASE
:
DELIMITER $$
CREATE PROCEDURE GetDepartmentIdByName(IN dept_name VARCHAR(100), OUT dept_id INT)
BEGIN
-- 使用搜索形式的 CASE 语句
SET dept_id = CASE
WHEN dept_name = 'Sales' THEN 1
WHEN dept_name = 'Engineering' THEN 2
WHEN dept_name = 'HR' THEN 3
ELSE 0 -- 默认值表示未找到匹配的部门
END;
END$$
DELIMITER ;
3. LOOP
LOOP
语句创建一个无限循环,直到遇到 LEAVE
或 ITERATE
语句才会退出或继续下一次迭代。
示例:遍历并打印数字
-- 更改语句结束符为 $$,以便在存储过程中使用分号作为语句结束符
DELIMITER $$
-- 创建名为 PrintNumbers 的存储过程
-- 目标:打印从 1 到 10 的数字序列
CREATE PROCEDURE PrintNumbers()
BEGIN
-- 声明一个局部变量 i 并初始化为 1,用作计数器
DECLARE i INT DEFAULT 1;
-- 开始一个名为 print_loop 的无限循环
print_loop: LOOP
-- 打印当前的数字 i
SELECT i AS number;
-- 增加计数器 i 的值
SET i = i + 1;
-- 如果计数器 i 超过 10,则退出循环
IF i > 10 THEN
LEAVE print_loop;
END IF;
END LOOP;
-- 结束存储过程体
END$$
-- 恢复默认的语句结束符为分号 ;
DELIMITER ;
-- 注释:存储过程 PrintNumbers 创建完成。
-- 调用方法:CALL PrintNumbers();
-- 该存储过程将依次打印数字 1 到 10。
4. WHILE
WHILE
语句在满足特定条件时重复执行代码块,类似于其他编程语言中的 while
循环。
示例:计算阶乘
-- 更改语句结束符为 $$,以便在存储过程中使用分号作为语句结束符
DELIMITER $$
-- 创建名为 CalculateFactorial 的存储过程
-- 参数:
-- IN n INT: 输入参数,表示要计算阶乘的正整数
-- OUT result BIGINT: 输出参数,用于返回 n 的阶乘结果
-- 目标:计算并返回给定正整数 n 的阶乘(n!)
CREATE PROCEDURE CalculateFactorial(IN n INT, OUT result BIGINT)
BEGIN
-- 声明一个局部变量 i 并初始化为 1,用作循环计数器
DECLARE i INT DEFAULT 1;
-- 初始化输出参数 result 为 1,因为任何数的 0! 和 1! 都等于 1
SET result = 1;
-- 开始一个名为 factorial_loop 的 WHILE 循环,条件是 i 小于或等于输入参数 n
factorial_loop: WHILE i <= n DO
-- 将当前的 result 与 i 相乘,并更新 result 的值
SET result = result * i;
-- 增加计数器 i 的值
SET i = i + 1;
END WHILE;
-- 结束存储过程体
END$$
-- 恢复默认的语句结束符为分号 ;
DELIMITER ;
-- 注释:存储过程 CalculateFactorial 创建完成。
-- 调用方法:CALL CalculateFactorial(n, @result);
-- 例如:CALL CalculateFactorial(5, @result); 然后 SELECT @result; 将会返回 5 的阶乘,即 120。
5. REPEAT
REPEAT
语句会先执行代码块,然后检查条件是否为真,如果为真则退出循环;否则继续执行。这与 WHILE
的行为相反。
示例:寻找符合条件的第一个员工
-- 更改语句结束符为 $$,以便在存储过程中使用分号作为语句结束符
DELIMITER $$
-- 创建名为 FindFirstQualifiedEmployee 的存储过程
-- 参数:
-- IN min_salary DECIMAL(10,2): 输入参数,表示最低工资要求
-- OUT emp_id INT: 输出参数,用于返回符合条件的第一个员工ID
-- 目标:查找第一个工资不低于给定最低工资要求的员工,并返回其ID
CREATE PROCEDURE FindFirstQualifiedEmployee(IN min_salary DECIMAL(10,2), OUT emp_id INT)
BEGIN
-- 声明局部变量 done 作为循环结束标志,默认值为 FALSE
DECLARE done INT DEFAULT FALSE;
-- 声明局部变量 cur_emp_id 和 cur_salary 分别保存当前遍历到的员工ID和工资
DECLARE cur_emp_id INT;
DECLARE cur_salary DECIMAL(10,2);
-- 定义游标 cur,用于遍历 employees 表中的所有记录,并按 id 排序
DECLARE cur CURSOR FOR SELECT id, salary FROM employees ORDER BY id;
-- 声明一个继续处理器,当游标遇到 NOT FOUND 条件(即没有更多行)时,设置 done 为 TRUE
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- 打开游标,开始遍历数据
OPEN cur;
-- 开始 REPEAT 循环,直到 done 变量为 TRUE
find_loop: REPEAT
-- 从游标中获取下一行数据,并赋值给 cur_emp_id 和 cur_salary 变量
FETCH cur INTO cur_emp_id, cur_salary;
-- 如果找到了符合条件的员工(工资不低于 min_salary),则设置输出参数 emp_id 并退出循环
IF NOT done AND cur_salary >= min_salary THEN
SET emp_id = cur_emp_id;
LEAVE find_loop;
END IF;
-- 循环条件:直到 done 为 TRUE(即游标遍历完毕)
UNTIL done END REPEAT;
-- 关闭游标,释放资源
CLOSE cur;
END$$
-- 恢复默认的语句结束符为分号 ;
DELIMITER ;
-- 注释:存储过程 FindFirstQualifiedEmployee 创建完成。
-- 调用方法:CALL FindFirstQualifiedEmployee(最低工资, @emp_id);
-- 例如:CALL FindFirstQualifiedEmployee(50000.00, @emp_id); 然后 SELECT @emp_id; 将会返回第一个工资不低于 50000.00 的员工ID。
在这个例子中,我们使用 REPEAT
循环配合游标来查找第一个符合最低工资要求的员工。
总结
通过这些例子,你应该能够理解如何在 MySQL 存储过程中使用各种流程控制语句了。每种语句都有其适用场景,合理选择和组合它们可以帮助你编写出功能丰富且易于维护的存储过程。