%% # 纲要 > 主干纲要、Hint/线索/路标 # Q&A #### 已明确 #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** # Buffer ## 闪念 > sudden idea ## 候选资料 > Read it later %% # 存储程序 Stored Program **存储程序**(Stored Programs)是 MySQL 中的一种**数据库对象**,是**对一组 SQL 语句的==封装==**,可保存于数据库中**以供复用**。包括两种: | | 说明 | | -------------------------- | ---------------------------------------------------------------- | | **存储过程**(Stored Procedure) | 预编译的一组 SQL 语句,可**通过名称被调用**。存储过程**可接受输入参数、输出参数**,执行逻辑并返回结果。 | | **存储函数**(Stored Function) | 类似于存储过程,但它 **==返回一个值==**,通常用于计算或转换操作,**可嵌入到 SQL 查询中**,用作返回结果的函数。 | ## 存储过程与存储函数的差异 | | 存储过程 | 存储函数 | | ------ | -------------------------------------------- | ------------------------------------------------------------------------------------------- | | 调用方式 | **只能通过 `CALL` 语句显式调用** | 可直接**在表达式中调用** | | 参数类型 | 支持`IN`、`OUT`、`INOUT`参数 | 只支持`IN`参数 | | 返回值 | - 可通过设置多个`OUT` 或 `INOUT` 参数返回多个值 <br>- 无需标明 | - 必须且只能**返回一个值**<br>- 必须使用 `RETURNS` 语句标明**返回的数据类型** <br>- 必须使用 `RETURN` 语句**显式指定返回值**<br> | | 执行的结果集 | ✔️ 显示打印 | ❌ 不显示 | ## 注意事项 > [!caution] 存储程序中 `DECLARE` 语句的声明次序 > > 参见 [^1] (P22) > > ![[_attachment/02-开发笔记/08-数据库/SQL 数据库对象.assets/IMG-SQL 数据库对象-9D58A75A2283A1F90608CC5EF768EAD9.png|589]] > > > <br><br> ## 存储过程 Procedure #### 参数前缀 存储过程的参数前缀:`IN`(**缺省默认**)、`OUT`、`INPUT` ![[_attachment/02-开发笔记/08-数据库/SQL 语句.assets/IMG-SQL 语句-A2D3C12227062A9DF78D29DBD2594C6E.png|615]] #### 创建存储过程 ```sql -- 创建存储过程 CREATE PROCEDURE 存储过程名([参数列表]) BEGIN 需要执行的语句 END; ``` 示例一: ```sql -- 示例一: DELIMITER $ CREATE PROCEDURE t1_operation( [IN] m1_value INT, [IN] n1_value CHAR(1) ) BEGIN SELECT * FROM t1; INSERT INTO t1(m1, n1) VALUES(m1_value, n1_value); SELECT * FROM t1; END $ DELIMITER ; -- 调用该存储过程 CALL t1_operation(4, 'd'); ``` 示例二: ```sql -- 示例二: 指定参数的OUT前缀 DELIMITER $ CREATE PROCEDURE get_score_data( OUT max_score DOUBLE, OUT min_score DOUBLE, OUT avg_score DOUBLE, s VARCAHR(100) ) BEGIN SELECT MAX(score), MIN(score), AVG(score) FROM student_score WHERE subject = s INTO max_score, min_score, avg_score -- 将查询结果赋给out参数. END $ DELIMITER ; -- 调用该存储过程 CALL get_score_data(@a, @b, @c, '母猪的产后护理'); SELECT @a, @b, @c; -- 三个变量以`OUT`参数传入, 在执行存储过程中被赋值 ``` 示例三: ```sql -- 订单合计(计算是否需要对合计增加营业税 CREATE PROCEDURE ordertotal ( IN onumber INT, -- 订单批号 IN taxable BOOLEAN, -- 是否计入税额 OUT ototal DECIMAL(8, 2) -- 订单合计数额 ) COMMENT 'Obtain oder total, optionally adding tax' -- 税率 BEGIN DECLARE total DECIMAL(8, 2); -- 总额 DECLARE taxrate INT DEFAULT 6; -- 税率(百分数) SELECT SUM(item_price * quantity) FROM orderitems WHERE order_num = onumber INTO total; IF taxable THEN SELECT total+(total/100*taxrate) INTO total; END IF; SELECT total INTO ototal; -- 存入外部变量 -- SET ototal = total; -- 等效 END; ``` #### 调用存储过程 ```sql -- 调用存储过程 CALL 存储过程([参数列表]); -- 示例: CALL t1_operation(4, 'd'); ``` #### 查看存储过程 ```sql -- 查看指定存储过程的定义 SHOW CREATE PROCEDURE 存储过程名; ``` #### 删除存储过程 ```sql -- 删除存储过程 DROP PROCEDURE [IF EXISTS] 存储过程名称 ``` <br><br> ## 存储函数 Function > [!caution] 创建存储函数时报错 "This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA" > > 原因: MySQL 需要明确了解**该函数在处理相同输入时是否总会返回相同的输出**(即 **确定性**),或者**是否会读取或修改数据库数据**,尤其是在涉及 `binary logging`(二进制日志记录)或 `replication`(主从复制)的情况下,MySQL 需要明确函数行为。 > > 解决方案:**为函数声明 `DETERMINISTIC` 或其他属性**: > > - `DETERMINISTIC`:表示该函数对于相同的输入参数,始终返回相同的结果。 > - `NOT DETERMINISTIC`:表示该函数对于相同的输入参数,可能返回不同的结果 <br>(例如,使用了随机数、当前时间等)。 > - `NO SQL`:表示该函数不执行任何 SQL 语句。 > - `READS SQL DATA`:表示该函数读取了数据库中的数据,但不修改。 > - `MODIFIES SQL DATA`:表示该函数修改了数据库中的数据。 > > 或者其他方式:(两种方式): > > 1. 方式一:**更改全局设置** ——`SET GLOBAL log_bin_trust_function_creators=1`; > 2. 方式二:**更改配置文件 `my.cnf`**—— `log-bin-trust-function-creators=1` 基本关键字: - `DECALRE` 关键字声明变量; - `SET` 关键字声明变量赋值; - `RETURN` 关键字声明返回值; #### 创建存储函数 > [!NOTE] 存储函数的参数不支持默认值 ```sql CREATE FUNCTION 存储函数名称([参数列表]) RETURN 返回值类型 [函数属性] BEGIN 函数体内容 END; ``` 示例一: ```sql DELIMITER $ CREATE FUNCTION function_name (param1 INT, ...) RETURNS INT -- 声明返回值类型 DETERMINISTIC BEGIN DECLARE result INT; -- 定义局部变量 SET result = param1 * 2; RETURN result; -- 声明返回值 END$ DELIMITER ; ``` 示例二: ```sql DELIMITER $ CREATE FUNCTION avg_score(s VARCHAR(100)) RETURNS DOUBLE READS SQL DATA -- 声明函数属性: 读取数据但不修改. BEGIN DECLARE res DOUBLE; SET res = (SELECT AVG(score) FROM student_score WHERE subject = s); RETURN res; -- 等价语句: -- RETURN (SELECT AVG(score) FROM student_score WHERE subject = s); END $ DELIMITER ; ``` #### 查看存储函数 ```sql -- 查看存储函数定义 SHOW CREATE FUNCTION 存储函数名; ``` #### 删除存储函数 ```sql -- 删除存储函数 DROP FUNCTION 存储函数名; ``` <br> ### 函数逻辑 ##### 判断语句 ```sql -- 判断语句: IF 表达式 THEN 处理语句列表 [ELSEIF 表达式 THEN 处理语句列表] ... [ELSE 处理语句列表] END IF; -- 使用示例: DELIMITER $ CREATE FUNCTION condition_demo(i INT) RETURNS VARCHAR(10) DETERMINISTIC BEGIN DECLARE result VARCHAR(10); IF i = 1 THEN SET result = '结果是1'; ELSEIF i = 2 THEN SET result = '结果是2'; ELSEIF i = 3 THEN SET result = '结果是3'; ELSE SET result = '非法参数'; END IF; RETURN result; END$ DELIMITER ; ``` ##### 循环语句 while 循环语句: ```SQL -- while 循环语句 WHILE 表达式 do 处理语句列表 END WHILE; -- 使用示例: DELIMITER $ CREATE FUNCTION sum_all(n INT UNSIGNED) RETURNS INT DETERMINISTIC BEGIN DECLARE result INT DEFAULT 0; -- 定义局部变量, 默认值0 DECLARE i INT DEFAULT 1; -- 定义局部变量, 默认值1 WHILE i <= n DO SET result = result + i; SET i = i + 1; END WHILE; RETURN result; END $ DELIMITER ; ``` `REPEAT ... UNTIL ... END REPEAT; ` 循环语句: ```sql -- REPREAT 循环 REPEAT 处理语句列表 UNTIL 表达式 END REPEAT; -- 当表达式为真时, 停止循环 -- 使用示例: DELIMITER $ CREATE FUNCTION sum_all(n INT UNSIGNED) RETURNS INT DETERMINISTIC BEGIN DECLARE result INT DEFAULT 0; DECLARE i INT DEFAULT 1; REPEAT SET result = result + i; SET i = i + 1; UNTIL i > n END REPRET; -- 当 i > n 时, 跳出循环 RETURN result; END $ DELIMITER ; ``` loop 语句: 略 <br><br><br> # 游标 Cursor SQL 中的**游标(Cursor)** 是一种数据库对象,用于**在 "存储过程" 或 "存储函数" 中==逐行处理==查询结果集**。 > [!NOTE] > > - 游标是 **==只读==** 的,不能通过游标直接修改数据,需使用额外 SQL 语句如 `UPDATE` > - 存储过程结束后,若未显式关闭游标(`close`),则 MySQL 会自动关闭。 ### 游标的使用 使用游标涉及几个步骤:**声明/创建游标**、**打开游标**、**获取数据**、**关闭游标**。 ###### 创建游标 ```sql DECLARE 游标名 CURSOR FOR 查询语句; ``` ###### 打开&关闭游标 打开" 意味着**执行查询语句**;"关闭" 意味**释放游标相关资源** ```sql OPEN 游标名; -- "打开"意味着执行查询语句, 获取结果集, 游标指向第一条记录 CLOSE 游标名; ``` ###### 获取数据 ```sql -- 将当前游标所指记录中的各列值依次赋给 `INTO` 之后的各个变量, FETCH 游标名 INTO 变量1, 变量2, ... 变量n ``` ### 示例 示例一: ```sql DELIMITER $ CREATE PROCEDURE cursor_demo() BEGIN DECLARE m_value INT; DECLARE n_value CHAR(1); DECLARE record_count INT; DECLARE i INT DEFAULT 0; -- 创建游标——获取t1表中m1, n1两列的值. DECLARE cur CURSOR FOR SELECT m1, n1 FROM t1; SELECT COUNT(*) FROM t1 INTO record_count; -- 打开游标: 执行查询语句 OPEN record_count; WHILE i < record_count DO FETCH cur INTO m_value, n_value -- 获取数据 SELECT m_value, n_value; -- 显示执行结果. SET i = i + 1; END WHILE; -- 关闭游标 CLOSE cur; END $ DELIMITER ; ``` 示例二: ```sql DELIMITER $ CREATE PROCEDURE process_employee_salareis() BEGIN -- 声明变量 DECLARE done INT DEFAULT FALSE; DECLARE emp_id INT; DECLARE emp_salary DECIMAL(10, 2); -- 声明游标 DECLARE cur CURSOR FOR SELECT employee_id, salary FROM employees; -- 声明处理结束条件(句柄) DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; -- 打开游标 OPEN cur; -- 循环读取游标中的数据 read_loop: LOOP FETCH cur INTO emp_id, emp_salary; IF done THEN LEAVE read_loop; END IF; -- 在这里处理每行数据, 更新员工薪资. UPDATE employees SET salary = emp_salary * 1.10 WHERE employee_id = emp_id; END LOOP; -- 关闭游标 CLOSE cur; END $ DELIMITER ; ``` 示例三: ```sql DELIMITER $ CREATE PROCEDURE processorders() BEGIN -- 声明变量 DECLARE done BOOLEAN DEFAULT 0; -- 用于控制循环 DECLARE o INT; -- 订单号 DECLARE t DECIMAL(8, 2); -- 订单总额 -- 声明游标 DECLARE ordernumbers CURSOR FOR SELECT order_num FROM orders; -- 声明句柄 DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; -- 创建表, 存储结果 CREATE TABLE IF NOT EXISTS ordertotals ( order_num INT, total DECIMAL(8, 2) ); -- 打开游标 OPEN ordernumbers; -- 循环遍历: 为每一笔订单, 计算其订单总金额, 存入ordertotals表中. REPEAT -- 获取数据 FETCH ordernumbers INTO o; -- 调用存储程序, 计算订单总额 CALL ordertotal(o, 1, t); -- 插入指定表 INSERT INTO ordertotals(order_num, total) VALUES (o, t); UNTIL done END REPEAT; -- 关闭游标 CLOSE ordernumbers; END $ DELIMITER ; ``` <br><br><br> # 触发器 Trigger 触发器是 MySQL 的一种数据库对象,用以设置在**指定表发生数据修改(`INSERT`、`UPDATE`、`DELETE`操作)时**,**自动触发并执行一段预定义的 SQL 代码**。 特点:**==数据操作触发== & ==自动调用==**:在对指定表执行 `INSERT`、`UPDATE`、`DELETE` 操作时自动触发。 使用场景: - **数据校验**:在插入或更新数据时自动验证输入的有效性。 - **日志审计**:自动记录对数据表的变更,如记录插入、更新、删除的操作日志。 - **数据同步**:当主表发生变动时,自动更新同步相关的从表 > [!caution] 触发器的限制: > > 1. **每个表的每种操作只能有一个触发器**:<br>MySQL 限制**一个表的同一操作只能有一个 `BEFORE` 和一个 `AFTER` 触发器**。 > 2. **不支持触发器的嵌套调用**:触发器的执行不能再触发另一个触发器。 > 3. **性能问题**:在高并发或大规模操作时,复杂的触发器可能会导致性能问题,因此不建议在大型应用中频繁使用复杂的触发器逻辑。 > > > [!caution] 视图不支持触发器,只有真实表才支持。 #### 创建触发器 创建触发器需给出的四个信息: - 触发器名(唯一) - 触发器**何时执行**:`BEFORE` 或 `AFTER` - 触发器**响应的操作**:`INSERT` 或 `UPDATA` 或`DELETE` - 触发器**关联的表** ```sql CEREATE TRIGGER 触发器名 <BEFORE|AFTER> -- 定义触发器在"操作执行前"还是"执行后"执行触发器 <INSERT|DELETE|UPDATE> -- 指定触发器响应的操作 ON 表名 -- 触发器绑定的表 FOR EACH ROW BEGIN 触发器逻辑 END; ``` > [!NOTE] 触发器中的 `NEW` 与 `OLD` > ![[_attachment/02-开发笔记/08-数据库/SQL 语句.assets/IMG-SQL 语句-8D7D567B4E5145879D9A6E27967F1A41.png|561]] > - `NEW` 代表将插入、或者将修改覆盖的新记录,其中各列的值可被修改; > - `OLD` 代表修改或删除前的值,不能被修改,只读。 > [!caution] 触发器中不允许返回结果集!(即使用 `SELECT`) 示例一: ```sql -- 使用触发器, 自动记录日志 DELIMITER $ CREATE TRIGGER before_employee_insert BEFORE INSERT ON employees FOR EACH ROW BEGIN INSERT INTO employees_log (action, employee_name, log_time) VALUES ('Insert', NEW.name, NOW()); END $ DELIMITER ; ``` 示例二: ```sql -- 使用触发器, 纠正插入值的范围只能是[1, 10]. DELIMITER $ CREATE TRIGGER bi_t1 BEFORE INSERT ON t1 FOR EACH ROW BEGIN IF NEW.m1 < 1 THEN SET NEW.m1 = 1; ELSEIF NEW.m1 > 10 THEN SET NEW.m1 = 10; END IF; END $ DELIMITER ; ``` 示例三: ```sql DELIMITER $ CREATE TRIGGER deleteorder BEFORE DELETE ON order FOR EACH ROW BEGIN INSERT INTO archive_orders(order_num, order_date, cust_id) VALUES(OLD.order_num, OLD.order_date, OLD.cust_id); -- 引用OLD, 其代表将被删除的行. END $ DELIMITER ; ``` #### 查看触发器 ```sql -- 查看所有触发器的语句 SHOW TRIGGERS; -- 查看指定触发器的定义语句 SHOW CREATE TRIGGER 触发器名; ``` #### 删除触发器 ```sql DROP TRIGGER 触发器名; ``` <br><br><br> # 事件 Event MySQL 中的事件(Event) 是一种**定时调度任务**,用于**在指定的时间或定期执行某些 SQL 语句**。 类似于操作系统中的 **Cron 作业** 或 **定时任务**。 事件支持两种自动执行方式: - **在某个==确定时间点==执行**(一次性) - **每==隔一段时间==执行一次**(周期性) > [!caution] MySQL 默认未开启事件,可通过设置下列 "系统变量" 开启: > > - `SET GLOBAL event_scheduler = ON` #### 创建事件 ```sql CREATE EVENT 事件名 ON SCHEDULE <AT timestamp | EVERY interval [STARTS datetime][END datetime]> DO BEGIN 具体语句 END; ``` 示例二:创建定时事件 ```sql DELIMITER $ CREATE EVENT insert ON SCHEDULE AT '2019-09-04 15:48:54' DO BEGIN INSERT INTO t1(m1, n1) VALUES(6, 'f'); END$ DELIMITER ; ``` 示例二:创建周期性事件 ```sql DELIMITER $ CREATE EVENT insert_t1 ON SCHEDULE -- 若未指定事件开始/结束时间, 则将无限制地周期性执行 EVERY 1 HOUR [STARTS '2019-09-04 15:48:54'] [ENDS '2019-09-16 15:45:54'] DO BEGIN INSERT INTO t1(m1, n1) VALUES(6, 'f'); END$ DELIMITER ; ``` #### 查看事件 ```sql -- 查看当前数据库(指定数据库)中定义的所有事件 SHOW EVENTS [FROM database_name]; -- 查看指定事件的具体定义 SHOW CREATE EVENTS; ``` #### 删除事件 ```sql -- 删除事件 DROP EVENT 事件名; ``` <br><br> # 参考资料 # Footnotes [^1]: 《**MySQL 必知必会**》