存储过程能够将复杂的业务逻辑封装在数据库内部执行,有效提升了数据处理效率与系统稳定性,它允许我们通过编写一系列SQL语句及流程控制语句,实现自动化、模块化的数据操作。本文我将全面介绍MySQL存储过程的语法结构、创建与调用方式、参数传递、流程控制以及实际应用场景,并结合丰富的示例,带你全面理解这一强大的数据库应用技术。
存储过程(Stored Procedure)是一组预编译并存储在数据库中的SQL语句集合,可包含数据查询、数据更新、流程控制等操作。它像一个数据库中的“函数”,接受输入参数,执行内部逻辑,并可返回结果。与普通SQL语句相比,存储过程具有更高的复用性、安全性和执行效率。
CALL
语句调用;函数可在SQL表达式中直接调用,类似于其他内置函数。使用CREATE PROCEDURE
语句创建存储过程,基本语法如下:
CREATE PROCEDURE procedure_name ([IN|OUT|INOUT parameter_name data_type,...])
BEGIN
-- 存储过程体,包含SQL语句和流程控制语句
SQL_statements;
END;
procedure_name
:存储过程的名称,在数据库中必须唯一。parameter_name
:参数名称,可根据需要定义多个参数。IN
:输入参数,用于将数据传入存储过程,在存储过程内部只能读取,不能修改。OUT
:输出参数,用于从存储过程中返回数据,在存储过程内部只能赋值,不能读取传入的值。INOUT
:输入输出参数,既可以传入数据,也可以返回数据,在存储过程内部既可读取也可修改。data_type
:参数的数据类型,如INT
、VARCHAR
、DATE
等。BEGIN
和END
:用于界定存储过程体的开始和结束。示例1:创建一个无参数的存储过程,查询employees
表中的所有员工信息:
CREATE PROCEDURE get_all_employees()
BEGIN
SELECT * FROM employees;
END;
示例2:创建一个带输入参数的存储过程,根据员工ID查询员工信息:
CREATE PROCEDURE get_employee_by_id(IN p_employee_id INT)
BEGIN
SELECT * FROM employees WHERE employee_id = p_employee_id;
END;
示例3:创建一个带输出参数的存储过程,统计employees
表中的员工数量:
CREATE PROCEDURE count_employees(OUT p_count INT)
BEGIN
SELECT COUNT(*) INTO p_count FROM employees;
END;
示例4:创建一个带输入输出参数的存储过程,将传入的字符串反转后返回:
CREATE PROCEDURE reverse_string(INOUT p_str VARCHAR(255))
BEGIN
SET p_str = REVERSE(p_str);
END;
使用CALL
语句调用存储过程:
CALL get_all_employees();
CALL get_employee_by_id(101); -- 假设101为员工ID
SET @count = 0;
CALL count_employees(@count);
SELECT @count;
SET @input_str = 'Hello, World!';
CALL reverse_string(@input_str);
SELECT @input_str;
DROP PROCEDURE
语句删除存储过程:DROP PROCEDURE IF EXISTS procedure_name;
IF EXISTS
子句用于避免删除不存在的存储过程时产生错误。
IF condition THEN
statements;
[ELSEIF condition THEN
statements;]
[ELSE
statements;]
END IF;
示例:创建一个存储过程,根据员工的工资情况判断其工资级别:
CREATE PROCEDURE check_salary_level(IN p_salary DECIMAL(10, 2))
BEGIN
DECLARE level VARCHAR(20);
IF p_salary < 5000 THEN
SET level = '低';
ELSEIF p_salary < 10000 THEN
SET level = '中';
ELSE
SET level = '高';
END IF;
SELECT level;
END;
CASE expression
WHEN value1 THEN statements;
WHEN value2 THEN statements;
...
[ELSE statements;]
END CASE;
示例:创建一个存储过程,根据员工的部门ID输出部门名称:
CREATE PROCEDURE get_department_name(IN p_department_id INT)
BEGIN
DECLARE department VARCHAR(50);
CASE p_department_id
WHEN 1 THEN SET department = '销售部';
WHEN 2 THEN SET department = '技术部';
WHEN 3 THEN SET department = '财务部';
ELSE SET department = '其他部门';
END CASE;
SELECT department;
END;
[loop_label:] LOOP
statements;
[IF condition THEN LEAVE loop_label; END IF;]
END LOOP [loop_label];
LEAVE
语句用于退出循环。
示例:创建一个存储过程,计算从1到10的累加和:
CREATE PROCEDURE calculate_sum()
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE sum INT DEFAULT 0;
my_loop: LOOP
SET sum = sum + i;
SET i = i + 1;
IF i > 10 THEN LEAVE my_loop; END IF;
END LOOP my_loop;
SELECT sum;
END;
WHILE condition DO
statements;
END WHILE;
示例:使用WHILE
语句实现上述相同功能:
CREATE PROCEDURE calculate_sum_while()
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE sum INT DEFAULT 0;
WHILE i <= 10 DO
SET sum = sum + i;
SET i = i + 1;
END WHILE;
SELECT sum;
END;
REPEAT
statements;
UNTIL condition END REPEAT;
示例:使用REPEAT
语句实现累加和计算:
CREATE PROCEDURE calculate_sum_repeat()
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE sum INT DEFAULT 0;
REPEAT
SET sum = sum + i;
SET i = i + 1;
UNTIL i > 10 END REPEAT;
SELECT sum;
END;
在存储过程中可以使用事务,确保一组相关操作要么全部成功提交,要么全部失败回滚。
示例:创建一个存储过程,实现银行转账功能,包含事务控制:
CREATE PROCEDURE transfer_money(
IN p_from_account INT,
IN p_to_account INT,
IN p_amount DECIMAL(10, 2)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
UPDATE accounts SET balance = balance - p_amount WHERE account_id = p_from_account;
UPDATE accounts SET balance = balance + p_amount WHERE account_id = p_to_account;
COMMIT;
END;
上述存储过程中,使用DECLARE EXIT HANDLER
捕获异常,若转账过程中出现错误,将回滚事务并重新抛出异常。
游标用于处理查询结果集,允许逐行访问和处理数据。
示例:创建一个存储过程,使用游标遍历employees
表,输出每个员工的姓名和工资:
CREATE PROCEDURE iterate_employees()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE v_name VARCHAR(50);
DECLARE v_salary DECIMAL(10, 2);
DECLARE cur CURSOR FOR SELECT employee_name, salary FROM employees;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO v_name, v_salary;
IF done THEN LEAVE read_loop; END IF;
SELECT CONCAT('姓名:', v_name, ', 工资:', v_salary);
END LOOP;
CLOSE cur;
END;
在上述存储过程中,定义了游标cur
,通过FETCH
语句逐行获取数据,并使用CONTINUE HANDLER
处理游标遍历结束的情况。
动态SQL允许在存储过程中根据运行时的条件动态生成SQL语句,提高存储过程的灵活性。
示例:创建一个存储过程,根据传入的表名和条件,动态查询数据:
CREATE PROCEDURE dynamic_query(
IN p_table_name VARCHAR(50),
IN p_condition VARCHAR(255)
)
BEGIN
SET @sql = CONCAT('SELECT * FROM ', p_table_name,'WHERE ', p_condition);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END;
调用该存储过程:
CALL dynamic_query('employees', 'department_id = 1');
DECLARE HANDLER
),捕获并处理可能出现的异常,避免因错误导致数据不一致或程序崩溃。在电商系统中,创建一个存储过程用于处理订单,包括插入订单记录、更新库存、计算订单总金额等操作:
CREATE PROCEDURE process_order(
IN p_user_id INT,
IN p_product_ids TEXT, -- 多个产品ID以逗号分隔
IN p_quantities TEXT -- 对应产品的数量以逗号分隔
)
BEGIN
DECLARE total_amount DECIMAL(10, 2) DEFAULT 0;
DECLARE v_product_id INT;
DECLARE v_quantity INT;
DECLARE v_price DECIMAL(10, 2);
DECLARE v_index INT DEFAULT 1;
DECLARE v_product_ids TEXT DEFAULT p_product_ids;
DECLARE v_quantities TEXT DEFAULT p_quantities;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
WHILE v_index <= LENGTH(v_product_ids) - LENGTH(REPLACE(v_product_ids, ',', '')) + 1 DO
SET v_product_id = CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(v_product_ids, ',', v_index), ',', -1) AS UNSIGNED);
SET v_quantity = CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(v_quantities, ',', v_index), ',', -1) AS UNSIGNED);
SELECT price INTO v_price FROM products WHERE product_id = v_product_id;
SET total_amount = total_amount + v_price * v_quantity;
-- 插入订单详情记录
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES (NULL, v_product_id, v_quantity, v_price);
-- 更新库存
UPDATE products SET stock_quantity = stock_quantity - v_quantity WHERE product_id = v_product_id;
SET v_index = v_index + 1;
END WHILE;
-- 插入订单主记录
INSERT INTO orders (order_id, user_id, order_date, total_amount)
VALUES (NULL, p_user_id, NOW(), total_amount);
COMMIT;
END;
调用该存储过程:
CALL process_order(1, '1,2,3', '2,1,3'); -- 假设用户ID为1,购买产品ID为1、2、3,数量分别为2、1、3
创建一个存储过程,用于统计每月的销售数据,并生成报表:
CREATE PROCEDURE generate_sales_report()
BEGIN
DECLARE v_month INT;
DECLARE v_year INT;
DECLARE done INT DEFAULT FALSE;
DECLARE cur CURSOR FOR SELECT DISTINCT YEAR(order_date) AS year, MONTH(order_date) AS month FROM orders;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- 创建临时表存储报表数据
CREATE TEMPORARY TABLE IF NOT EXISTS sales_report (
year INT,
month INT,
total_orders INT,
total_amount DECIMAL(10, 2)
);
OPEN cur;
read_loop: LOOP
FETCH cur INTO v_year, v_month;
IF done THEN LEAVE read_loop; END IF;
INSERT INTO sales_report (year, month, total_orders, total_amount)
SELECT v_year, v_month, COUNT(*), SUM(total_amount)
FROM orders
WHERE YEAR(order_date) = v_year AND MONTH(order_date) = v_month;
END LOOP;
CLOSE cur;
-- 输出报表数据
SELECT * FROM sales_report;
-- 删除临时表
DROP TEMPORARY TABLE sales_report;
END;
调用该存储过程即可生成销售报表:
CALL generate_sales_report();
若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ