SQL 进阶:触发器、存储过程
文章目录
- 一、触发器
- 1.1 定义
- 1.2 创建语法
- 1.3 关键变量:NEW 与 OLD
- 1.4 案例:下单减库存
- 准备表与初始数据
- 需求 1:新增订单时自动减固定库存(减少 2 个)
- 需求 2:修改订单数量时,自动调整库存(动态适配数量)
- 二、存储过程
- 2.1 核心特性
- 2.2 创建语法与参数类型
- 2.3 示例:按参数类型分类
- IN 参数示例(仅输入值)
- OUT 参数示例(仅输出结果)
- INOUT 参数示例(双向传递)
一、触发器
触发器是与表关联的 “自动执行的存储过程”,无需手动调用,当表发生INSERT/UPDATE/DELETE(DML 操作)时自动触发,用于保证数据完整性(如 “下单减库存”)。
1.1 定义
定义:与表绑定的特殊逻辑,由 “事件”(DML 操作)触发,不可手动调用,执行结果依赖于触发事件。
4 要素(创建触发器必须指定):
- 监视对象:触发逻辑绑定的表(只能是单个表)。
- 监视事件:触发触发器的 DML 操作,可选INSERT(插入)、UPDATE(更新)、DELETE(删除)。
- 触发时间:事件执行的时机,可选BEFORE(事件前执行)、AFTER(事件后执行)。
- 触发动作:触发器触发后执行的逻辑(如UPDATE其他表、INSERT日志记录)。
1.2 创建语法
CREATE TRIGGER trigger_name -- 触发器名称(自定义,需唯一)
trigger_time trigger_event -- 触发时间(BEFORE/AFTER)+ 监视事件(INSERT/UPDATE/DELETE)
ON tbl_name FOR EACH ROW -- 绑定的表(tbl_name),FOR EACH ROW表示“每行触发一次”
[trigger_order] -- 可选,多个触发器的执行顺序(FOLLOWS/PRECEDES 其他触发器名)
trigger_body; -- 触发动作(单个语句可直接写,多个语句需用BEGIN...END包裹)
1.3 关键变量:NEW 与 OLD
用于获取触发事件中 “数据的变化”,不同事件对应的变量含义不同:
| 触发事件 | NEW 变量含义 | OLD 变量含义 |
|---|---|---|
| INSERT | 将要 / 已经插入的新数据 | 无(插入操作无原数据) |
| DELETE | 无(删除操作无新数据) | 将要 / 已经删除的原数据 |
| UPDATE | 将要 / 已经修改为的新数据 | 将要 / 已经修改的原数据 |
- 使用方式:NEW.columnName(获取新数据的某列值)、OLD.columnName(获取原数据的某列值),columnName 为绑定表的列名。
1.4 案例:下单减库存
需求:当用户在order(订单表)中插入新订单时,自动减少goods(商品表)中对应商品的库存(库存 = 原库存 - 下单数量)。
准备表与初始数据
-- 1. 商品表:存储商品ID、名称、库存
CREATE TABLE `goods` (`id` INT PRIMARY KEY auto_increment, -- 商品ID`name` VARCHAR (32), -- 商品名称`num` SMALLINT DEFAULT 0 -- 库存数量
);-- 2. 订单表:存储订单ID、商品ID、下单数量
CREATE TABLE `order` (`id` INT PRIMARY KEY auto_increment, -- 订单ID`goods_id` INT, -- 关联商品表的ID`quantity` SMALLINT COMMENT '下单数量' -- 购买数量
);-- 3. 插入初始商品数据
INSERT INTO goods VALUES (NULL, 'C++', 40), -- 商品1:C++教程,库存40(NULL, 'C', 63), -- 商品2:C教程,库存63(NULL, 'mysql', 87); -- 商品3:MySQL教程,库存87-- 4. 插入初始订单数据(此时无触发器,需手动减库存)
INSERT INTO `order` VALUES (NULL, 1, 3); -- 订单1:买3本C++教程
INSERT INTO `order` VALUES (NULL, 2, 4); -- 订单2:买4本C教程
需求 1:新增订单时自动减固定库存(减少 2 个)
-- 修改分隔符为//(触发器体含多个语句)
DELIMITER //
-- 创建触发器:插入订单后,自动减少商品1的库存2个
CREATE TRIGGER trig_order_1
AFTER INSERT -- 触发时间:插入订单后;监视事件:INSERT
ON `order` FOR EACH ROW -- 绑定订单表,每行订单触发一次
BEGIN-- 触发动作:更新商品表,商品1(id=1)的库存=原库存-2UPDATE goods SET num = num - 2 WHERE id = 1;
END
//
DELIMITER ;-- 测试:插入新订单,触发库存减少
INSERT INTO `order` VALUES (NULL, 1, 1); -- 新增订单:买1本C++教程
SELECT num FROM goods WHERE id=1; -- 结果:原库存40-3(初始订单)-2(触发器)=35
需求 2:修改订单数量时,自动调整库存(动态适配数量)
DELIMITER //
-- 创建触发器:修改订单前,自动调整对应商品的库存
CREATE TRIGGER trig_order_2
BEFORE UPDATE -- 触发时间:修改订单前;监视事件:UPDATE
ON `order` FOR EACH ROW
BEGIN-- 逻辑:库存=原库存 + 订单原数量 - 订单新数量(原数量多退少补)UPDATE goods SET num = num + OLD.quantity - NEW.quantity -- OLD.quantity=原下单数,NEW=新下单数WHERE id = NEW.goods_id; -- 关联新订单的商品ID
END
//
DELIMITER ;-- 测试:修改订单1的数量(从3改为5,需多减2个库存)
UPDATE `order` SET quantity = quantity + 2 WHERE id = 1;
SELECT num FROM goods WHERE id=1; -- 结果:35(需求1后库存) +3(原数量)-5(新数量)=33
二、存储过程
存储过程是 “预编译的 SQL 语句集”,经编译后存储在数据库中,通过 “调用名称 + 参数” 执行,可包含流程控制逻辑,适合实现复杂业务(如批量数据处理)。
2.1 核心特性
- 预编译:首次执行时编译,后续调用直接使用编译结果,比单次执行多条 SQL 更快。
- 可编程性:支持IF、WHILE等流程控制,可实现复杂逻辑(如数据校验、批量计算)。
- 重复使用:一次创建,多次调用,减少重复 SQL 书写,降低维护成本。
- 减少网络传输:客户端只需发送 “调用指令”,无需传输大量 SQL 语句,节省网络开销。
2.2 创建语法与参数类型
基础语法
CREATE PROCEDURE 过程名([[IN|OUT|INOUT] 参数名 数据类型[,[IN|OUT|INOUT] 参数名 数据类型…]] -- 参数列表
) [特性] -- 可选,如SQL SECURITY DEFINER(按创建者权限执行)
过程体; -- 业务逻辑,多语句需用BEGIN...END包裹,需配合DELIMITER修改分隔符
参数类型说明
存储过程支持 3 种参数类型,用于实现 “输入值”“输出结果”“输入输出双向传递”:
| 参数类型 | 核心特点 |
|---|---|
| IN | 输入参数:调用时必须传入值,存储过程中修改该参数值不影响外部变量,可设默认值。 |
| OUT | 输出参数:调用时无需传入初始值(传变量名),存储过程中修改后会同步到外部变量。 |
| INOUT | 输入输出参数:调用时需传入初始值,存储过程中修改后同步到外部变量,双向传递。 |
2.3 示例:按参数类型分类
IN 参数示例(仅输入值)
需求:创建存储过程,接收输入参数p_in,查询参数值并修改,观察外部变量是否变化。
DELIMITER //
CREATE PROCEDURE proc_in_param (IN p_in INT) -- IN参数:p_in(整数类型)
BEGINSELECT p_in; -- 第一次查询:输出传入的初始值SET p_in = 2; -- 存储过程中修改参数值为2SELECT p_in; -- 第二次查询:输出修改后的值(2)
END ;
//
DELIMITER ;-- 调用存储过程
SET @p_in = 1; -- 定义外部变量,初始值1
CALL proc_in_param (@p_in); -- 传入外部变量
SELECT @p_in; -- 查询外部变量:结果仍为1(IN参数修改不影响外部)
OUT 参数示例(仅输出结果)
需求:创建存储过程,通过 OUT 参数输出修改后的值,观察外部变量变化。
DELIMITER //
CREATE PROCEDURE proc_out_param(OUT p_out INT) -- OUT参数:p_out
BEGINSELECT p_out; -- 第一次查询:输出初始值(NULL,因OUT参数无需传入初始值)SET p_out = 2; -- 修改参数值为2SELECT p_out; -- 第二次查询:输出2
END;
//
DELIMITER ;-- 调用存储过程
SET @p_out = 1; -- 定义外部变量,初始值1(OUT参数会忽略该值)
CALL proc_out_param(@p_out); -- 传入外部变量
SELECT @p_out; -- 查询外部变量:结果为2(OUT参数修改同步到外部)
INOUT 参数示例(双向传递)
需求:创建存储过程,接收输入值并修改,同步到外部变量。
DELIMITER //
CREATE PROCEDURE proc_inout_param(INOUT p_inout INT) -- INOUT参数
BEGINSELECT p_inout; -- 第一次查询:输出传入的初始值(1)SET p_inout = 2; -- 修改参数值为2SELECT p_inout; -- 第二次查询:输出2
END;
//
DELIMITER ;-- 调用存储过程
SET @p_inout = 1; -- 定义外部变量,初始值1
CALL proc_inout_param(@p_inout); -- 传入外部变量
SELECT @p_inout; -- 查询外部变量:结果为2(INOUT参数双向传递)
