Nature's Beauty
Explore the wonders of the world
参考资料
简介
存储过程是一段事先经过编译、存储在数据库中的 SQL 语句集合,可以像函数一样传入参数并调用执行。
优点
- 可封装,隐藏复杂的处理逻辑
- 可以传入参数,也可以返回值
- 编写好的存储过程可重复使用
- 安全性高,可设定用户对存储过程的使用权限
不足
不同的数据库系统,其存储过程可能存在差异,所以在迁移时可能需要重写部分内容。
下面记录的是 MySQL 的存储过程
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
DELIMITER //
CREATE PROCEDURE calculate_order_stats(
IN customer_id INT,
OUT total_amount DECIMAL(10,2),
OUT order_count INT
)
BEGIN
-- 声明局部变量(仅在存储过程内有效)
DECLARE avg_amount DECIMAL(10,2);
-- 计算总金额和订单数量
SELECT SUM(amount), COUNT(*)
INTO total_amount, order_count
FROM orders
WHERE customer_id = customer_id;
-- 计算平均金额(演示局部变量使用)
IF order_count > 0 THEN
SET avg_amount = total_amount / order_count;
-- 根据平均金额设置不同级别的日志
IF avg_amount > 1000 THEN
INSERT INTO customer_log(customer_id, message)
VALUES (customer_id, CONCAT('VIP客户, 平均订单金额:', avg_amount));
ELSE
INSERT INTO customer_log(customer_id, message)
VALUES (customer_id, CONCAT('普通客户, 平均订单金额:', avg_amount));
END IF;
ELSE
SET total_amount = 0;
SET order_count = 0;
INSERT INTO customer_log(customer_id, message)
VALUES (customer_id, '暂无订单');
END IF;
END //
DELIMITER ;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
-- 准备测试数据
INSERT INTO orders VALUES (1, 101, 500.00);
INSERT INTO orders VALUES (2, 101, 1500.00);
-- 调用存储过程
SET @cust_id = 101;
SET @total = 0;
SET @count = 0;
CALL calculate_order_stats(@cust_id, @total, @count);
-- 查看结果
SELECT @total AS 总金额, @count AS 订单数;
-- 查看生成的日志
SELECT * FROM customer_log WHERE customer_id = 101;
|
关键语法说明
DELIMITER:用于更改命令结束符,以便在存储过程中使用 BEGIN ... END 语句。通常使用 // 作为新的结束符,定义结束后改回 ;
CREATE PROCEDURE:用于创建新的存储过程,后接存储过程名称
(IN 传入参数 类型, OUT 输出参数 类型):定义输入和输出参数
BEGIN ... END:存储过程的主体,包含要执行的 SQL 语句
基础语法
用户变量
定义用户变量的方式:
1
2
3
4
5
6
7
8
9
|
-- 定义用户变量(会话级)
SET @user_email = 'john@example.com';
SET @discount_rate = 0.9; -- 9折优惠
-- 或者在查询中设置
SELECT COUNT(*) INTO @total_users FROM users WHERE status = 'active';
-- 使用用户变量
SELECT @user_email AS email, @discount_rate AS discount, @total_users AS active_users;
|
用户变量使用 @ 符号,定义时可以做数值计算。存储过程中能直接读取 @x 的值并用于处理。注意滥用用户变量会导致程序难以理解和管理。
参数传递
-
IN(输入参数):假设外部定义 SET @a = 1;,作为输入参数传入存储过程。存储过程中修改该参数 SET a = 233;,不影响外部 @a 的值
-
OUT(输出参数):假设外部定义 SET @b = 1;,作为输出参数传入存储过程。存储过程读取不到该值(结果是 NULL),因为 OUT 参数专用于返回值。在存储过程中修改该参数会改变外部 @b 的值
-
INOUT(输入输出参数):可以作为传入值,也可以作为存储过程的传出值
注意:确保参数名不等于列名,防止存储过程将参数当作列名处理。尽量少用 INOUT 参数。
流程控制
条件语句
if else:会员等级判定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
DELIMITER //
CREATE PROCEDURE check_member_level(IN user_id INT)
BEGIN
DECLARE purchase_total DECIMAL(10,2);
DECLARE member_level VARCHAR(20);
-- 计算用户总消费
SELECT COALESCE(SUM(amount), 0) INTO purchase_total
FROM orders
WHERE user_id = user_id AND status = 'completed';
-- 根据消费金额确定会员等级
IF purchase_total >= 10000 THEN
SET member_level = '钻石会员';
UPDATE users SET level = 'DIAMOND' WHERE id = user_id;
ELSEIF purchase_total >= 5000 THEN
SET member_level = '黄金会员';
UPDATE users SET level = 'GOLD' WHERE id = user_id;
ELSEIF purchase_total >= 1000 THEN
SET member_level = '白银会员';
UPDATE users SET level = 'SILVER' WHERE id = user_id;
ELSE
SET member_level = '普通会员';
UPDATE users SET level = 'NORMAL' WHERE id = user_id;
END IF;
-- 返回结果
SELECT
user_id AS 用户ID,
purchase_total AS 总消费额,
member_level AS 会员等级;
END //
DELIMITER ;
|
CASE:订单状态处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
DELIMITER //
CREATE PROCEDURE handle_order_status(IN order_id INT)
BEGIN
DECLARE current_status VARCHAR(20);
DECLARE action_message VARCHAR(100);
-- 获取当前订单状态
SELECT status INTO current_status FROM orders WHERE id = order_id;
-- 根据不同状态执行相应操作
CASE current_status
WHEN 'PENDING' THEN
SET action_message = '发送支付提醒邮件';
UPDATE orders SET reminder_sent = NOW() WHERE id = order_id;
WHEN 'PAID' THEN
SET action_message = '安排发货并通知仓库';
UPDATE orders SET shipped_at = NOW() WHERE id = order_id;
WHEN 'SHIPPED' THEN
SET action_message = '发送物流跟踪信息';
WHEN 'COMPLETED' THEN
SET action_message = '请求用户评价';
UPDATE orders SET review_requested = NOW() WHERE id = order_id;
ELSE
SET action_message = '未知状态,需要人工处理';
INSERT INTO exception_log(order_id, issue) VALUES (order_id, '未知订单状态');
END CASE;
-- 记录操作日志
INSERT INTO order_audit(order_id, action, executed_at)
VALUES (order_id, action_message, NOW());
SELECT order_id AS 订单号, action_message AS 执行操作;
END //
DELIMITER ;
|
循环语句
批量数据处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
DELIMITER //
CREATE PROCEDURE batch_update_prices(IN category_name VARCHAR(50), IN increase_rate DECIMAL(5,2))
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE product_id INT;
DECLARE old_price DECIMAL(10,2);
DECLARE new_price DECIMAL(10,2);
DECLARE update_count INT DEFAULT 0;
-- 声明游标来处理产品列表
DECLARE product_cursor CURSOR FOR
SELECT id, price FROM products
WHERE category = category_name AND status = 'active';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN product_cursor;
read_loop: LOOP
FETCH product_cursor INTO product_id, old_price;
IF done THEN
LEAVE read_loop;
END IF;
-- 计算新价格
SET new_price = old_price * (1 + increase_rate / 100);
-- 更新产品价格
UPDATE products SET price = new_price WHERE id = product_id;
-- 记录价格变更历史
INSERT INTO price_history(product_id, old_price, new_price, updated_at)
VALUES (product_id, old_price, new_price, NOW());
SET update_count = update_count + 1;
END LOOP;
CLOSE product_cursor;
SELECT
CONCAT('成功更新 ', update_count, ' 个产品的价格') AS 执行结果;
END //
DELIMITER ;
|
其他操作
1
2
|
SHOW PROCEDURE STATUS
WHERE db = 'pt_dev'
|
1
|
SHOW CREATE PROCEDURE pt_dev.fabricInspect_single_print_data
|
1
|
DROP PROCEDURE IF EXISTS operation_time_details();
|
与 SQL 函数的区别
函数(function)总是向调用者返回数据,一般只返回一个值。存储过程不直接返回数据,但可以通过改变输出参数的值来返回值,且输出参数的个数没有限制。
从实际应用来看:如果不需要返回值或需要多个返回值,使用存储过程;如果只需要一个返回值,使用函数。
function 定义中只能有 DDL(如 SELECT 等)语句;procedure 中主要是 DML 语句(对数据库进行复杂操作时,如 UPDATE、INSERT、QUERY、DELETE)。
拓展与后续学习建议
- 触发器:学习在表上定义触发器,自动响应 INSERT、UPDATE、DELETE 事件
- 事件调度器:了解如何利用 MySQL 事件调度器定期执行存储过程
- 预处理语句:深入学习 PREPARE 和 EXECUTE 的使用
- 错误处理:学习 DECLARE HANDLER 来处理存储过程中的异常情况
- 性能考量:了解存储过程的优缺点,在何时应该使用或避免使用存储过程