板凳-------Mysql cookbook学习 (十二--------1)

第9章 存储例程,触发器和计划事件 326

9.0 概述 326

9.1 创建复合语句对象 329

sql 复制代码
mysql> -- 恢复默认分隔符
mysql> DELIMITER ;
mysql>

mysql> DROP FUNCTION IF EXISTS avg_mail_size;
Query OK, 0 rows affected (0.02 sec)

mysql> DELIMITER $$
mysql> CREATE FUNCTION avg_mail_size(user VARCHAR(8))
    -> RETURNS FLOAT READS SQL DATA
    -> BEGIN
    ->     IF user IS NULL THEN
    ->         RETURN (SELECT AVG(size) FROM mail);
    ->     ELSE
    ->         RETURN (SELECT AVG(size) FROM mail WHERE srcuser = user);
    ->     END IF;
    -> END$$
Query OK, 0 rows affected (0.02 sec)

mysql> DELIMITER ;
mysql> select avg_mail_size(null), avg_mail_size('barb');
+---------------------+-----------------------+
| avg_mail_size(null) | avg_mail_size('barb') |
+---------------------+-----------------------+
|              237387 |                 52232 |
+---------------------+-----------------------+

9.2 利用存储函数封装计算 331

sql 复制代码
DROP FUNCTION IF EXISTS sales_tax_rate;

DELIMITER $$
CREATE FUNCTION sales_tax_rate(state_code char(2))
RETURNS decimal(3, 2) READS SQL DATA
BEGIN
    DECLARE rate decimal(3, 2);
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET rate = 0;
    SELECT tax_rate INTO rate FROM sales_tax_rate WHERE state = state_code;
    RETURN rate;
END$$
DELIMITER ;

mysql> DELIMITER ;
mysql> select sales_tax_rate('VT'), sales_tax_rate('NY');
+----------------------+----------------------+
| sales_tax_rate('VT') | sales_tax_rate('NY') |
+----------------------+----------------------+
|                 0.00 |                 0.09 |
+----------------------+----------------------+
1 row in set (0.02 sec)

mysql>  select sales_tax_rate('ZZ');
+----------------------+
| sales_tax_rate('ZZ') |
+----------------------+
|                 0.00 |
+----------------------+
1 row in set (0.00 sec)

mysql> select 150*sales_tax_rate('VT'),150* sales_tax_rate('NY');
+--------------------------+---------------------------+
| 150*sales_tax_rate('VT') | 150* sales_tax_rate('NY') |
+--------------------------+---------------------------+
|                     0.00 |                     13.50 |
+--------------------------+---------------------------+
1 row in set (0.01 sec)

mysql> DROP FUNCTION IF EXISTS sales_tax;
Query OK, 0 rows affected (0.02 sec)

mysql>
mysql> DELIMITER $$
mysql> CREATE FUNCTION sales_tax(state_code CHAR(2), sales_amount DECIMAL(10, 2))
    -> RETURNS DECIMAL(10, 2) READS SQL DATA
    -> BEGIN
    ->     RETURN sales_amount * sales_tax_rate(state_code);
    -> END$$
Query OK, 0 rows affected (0.02 sec)

mysql> DELIMITER ;

mysql> select sales_tax('VT', 150), sales_tax('NY', 150);
+----------------------+----------------------+
| sales_tax('VT', 150) | sales_tax('NY', 150) |
+----------------------+----------------------+
|                 0.00 |                13.50 |
+----------------------+----------------------+
1 row in set (0.00 sec)

9.3 通过存储过程返回多个值 333

sql 复制代码
mysql> DELIMITER $$
mysql>
mysql> DROP PROCEDURE IF EXISTS mail_sender_stats$$
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql>
mysql> CREATE PROCEDURE mail_sender_stats(
    ->     IN sender VARCHAR(64),
    ->     OUT messages INT,
    ->     OUT total_size INT,
    ->     OUT avg_size INT
    -> )
    -> BEGIN
    ->     -- Count messages
    ->     SELECT COUNT(*) INTO messages FROM mail WHERE srcuser = sender;
    ->
    ->     -- Calculate total size
    ->     SELECT SUM(size) INTO total_size FROM mail WHERE srcuser = sender;
    ->
    ->     -- Calculate average size (avoid division by zero)
    ->     IF messages > 0 THEN
    ->         SET avg_size = total_size / messages;
    ->     ELSE
    ->         SET avg_size = 0;
    ->     END IF;
    -> END$$
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> DELIMITER ;
mysql>
mysql> -- Now call the procedure
mysql> CALL mail_sender_stats('barb', @messages, @total_size, @avg_size);
Query OK, 1 row affected (0.00 sec)

mysql> SELECT @messages, @total_size, @avg_size;
+-----------+-------------+-----------+
| @messages | @total_size | @avg_size |
+-----------+-------------+-----------+
|         3 |      156696 |     52232 |
+-----------+-------------+-----------+
1 row in set (0.00 sec)

9.4 利用触发器动态设置列的默认值 334

sql 复制代码
DELIMITER $$

-- 先删除已存在的存储过程(虽然你命名的是表)
DROP PROCEDURE IF EXISTS cust_invoice$$

-- 创建表(单独执行)
CREATE TABLE cust_invoice(
   id  INT NOT NULL AUTO_INCREMENT,
   state CHAR(2),
   amount DECIMAL(10, 2),
   tax_rate DECIMAL(3, 2),
   PRIMARY KEY (id)
$$

-- 然后创建触发器(单独执行)
CREATE TRIGGER bi_cust_invoice 
BEFORE INSERT ON cust_invoice
FOR EACH ROW
BEGIN
    SET NEW.tax_rate = sales_tax_rate(NEW.state);
END$$

DELIMITER ;
关键修正点:
分开执行:CREATE TABLE 和 CREATE TRIGGER 是两个独立操作,不能嵌套

语法结构:触发器定义需要完整的 BEGIN...END 块

依赖关系:确保 sales_tax_rate() 函数已存在

mysql> insert into cust_invoice(state, amount) values('NY', 100);
Query OK, 1 row affected (0.02 sec)

mysql> select * from cust_invoice where id= last_insert_id();
+----+-------+--------+----------+
| id | state | amount | tax_rate |
+----+-------+--------+----------+
|  1 | NY    | 100.00 |     0.09 |
+----+-------+--------+----------+
1 row in set (0.00 sec)

9.5 利用触发器模拟基于函数的索引 336

sql 复制代码
-- 先创建表(已完成)
mysql> CREATE TABLE expdata(
    ->     id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    ->     value FLOAT,
    ->     log10_value FLOAT,
    ->     INDEX(value),
    ->     INDEX(log10_value)
    -> );
    ->
    -> -- 设置分隔符
    -> DELIMITER $$
    ->
    -> -- 创建 INSERT 触发器
    -> CREATE TRIGGER bi_expdata
    -> BEFORE INSERT ON expdata
    -> FOR EACH ROW
    -> BEGIN
    ->     SET NEW.log10_value = LOG10(NEW.value);
    -> END$$
    ->
    -> -- 创建 UPDATE 触发器
    -> CREATE TRIGGER bu_expdata
    -> BEFORE UPDATE ON expdata
    -> FOR EACH ROW
    -> BEGIN
    ->     SET NEW.log10_value = LOG10(NEW.value);
    -> END$$
    ->
    -> -- 恢复分隔符
    -> DELIMITER ;
    
mysql> insert into expdata(value) values (.01), (.1), (1), (10), (100);
Query OK, 5 rows affected (0.01 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from expdata;
+----+-------+-------------+
| id | value | log10_value |
+----+-------+-------------+
|  1 |  0.01 |          -2 |
|  2 |   0.1 |          -1 |
|  3 |     1 |           0 |
|  4 |    10 |           1 |
|  5 |   100 |           2 |
+----+-------+-------------+
5 rows in set (0.00 sec)

mysql> update expdata set value = value * 10;
Query OK, 5 rows affected (0.01 sec)
Rows matched: 5  Changed: 5  Warnings: 0

mysql> select * from expdata;
+----+-------+-------------+
| id | value | log10_value |
+----+-------+-------------+
|  1 |   0.1 |          -1 |
|  2 |     1 |           0 |
|  3 |    10 |           1 |
|  4 |   100 |           2 |
|  5 |  1000 |           3 |
+----+-------+-------------+
5 rows in set (0.00 sec)

9.6 在其他日期和时间类型中模拟TIMESTAMP属性 339

sql 复制代码
mysql> select * from ts_emulate;
+---------+------------+----------+---------------------+
| data    | d          | t        | dt                  |
+---------+------------+----------+---------------------+
| axolot1 | 2025-07-15 | 21:40:48 | 2025-07-15 21:40:48 |
| dog     | 2025-07-15 | 21:40:59 | 2025-07-15 21:40:59 |
+---------+------------+----------+---------------------+
2 rows in set (0.01 sec)

mysql> update ts_emulate set data = data;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 2  Changed: 0  Warnings: 0

mysql> select * from ts_emulate;
+---------+------------+----------+---------------------+
| data    | d          | t        | dt                  |
+---------+------------+----------+---------------------+
| axolot1 | 2025-07-15 | 21:40:48 | 2025-07-15 21:40:48 |
| dog     | 2025-07-15 | 21:40:59 | 2025-07-15 21:40:59 |
+---------+------------+----------+---------------------+
2 rows in set (0.00 sec)

9.7 利用触发器记录变更 341

sql 复制代码
mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                1 |
+------------------+
1 row in set (0.00 sec)

mysql> update auction set bid = 7.50 where id = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update auction set bid = 9.00 where id = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update auction set bid = 10.00 where id = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> delete from auction where id = 1;
Query OK, 1 row affected (0.01 sec)

mysql> select * from auction_log where id = 1 order by ts;
+--------+----+---------------------+----------------+-------+
| action | id | ts                  | item           | bid   |
+--------+----+---------------------+----------------+-------+
| delete |  1 | NULL                | chintz pillows | 10.00 |
| create |  1 | 2025-07-16 20:33:13 | chintz pillows |  5.00 |
| update |  1 | 2025-07-16 20:37:51 | chintz pillows |  7.50 |
| update |  1 | 2025-07-16 20:37:51 | chintz pillows |  9.00 |
| update |  1 | 2025-07-16 20:37:51 | chintz pillows | 10.00 |
+--------+----+---------------------+----------------+-------+
5 rows in set (0.00 sec)

9.8 使用事件来计划数据库操作 344

sql 复制代码
mysql> DELIMITER $$
mysql>
mysql> DROP PROCEDURE IF EXISTS exec_stmt$$
Query OK, 0 rows affected (0.02 sec)

mysql>
mysql> CREATE PROCEDURE exec_stmt(IN stmt_str TEXT)
    -> BEGIN
    ->     SET @_stmt_str = stmt_str;
    ->     PREPARE stmt FROM @_stmt_str;
    ->     EXECUTE stmt;
    ->     DEALLOCATE PREPARE stmt;
    -> END$$
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> DELIMITER ;
mysql> -- 设置变量
mysql> SET @tb1_name = 'test_table';
Query OK, 0 rows affected (0.00 sec)

mysql> SET @val = 123;  -- 设置要插入的值
Query OK, 0 rows affected (0.00 sec)

mysql>
mysql> -- 创建表
mysql> CALL exec_stmt(CONCAT('CREATE TABLE IF NOT EXISTS ', @tb1_name, '(i INT)'));
Query OK, 0 rows affected (0.06 sec)

mysql>
mysql> -- 插入数据(注意正确的INSERT语法)
mysql> CALL exec_stmt(CONCAT('INSERT INTO ', @tb1_name, '(i) VALUES(', @val, ')'));
Query OK, 0 rows affected (0.01 sec)
注意事项
1.	安全性:这种动态SQL容易受SQL注入攻击,不要用于用户输入
2.	错误处理:建议在存储过程中添加错误处理
3.	调试:可以先SELECT查看生成的SQL语句是否正确

9.9 编写辅助例程动态执行SQL 346

sql 复制代码
```sql
mysql> DROP FUNCTION IF EXISTS quote_identifier;
Query OK, 0 rows affected (0.02 sec)

mysql>
mysql> DELIMITER $$
mysql> CREATE FUNCTION quote_identifier(id TEXT)
    -> RETURNS TEXT DETERMINISTIC
    -> RETURN CONCAT('`', REPLACE(id, '`', '``'), '`')$$
Query OK, 0 rows affected (0.01 sec)

mysql> DELIMITER ;
mysql> DELIMITER $$
mysql> DROP PROCEDURE IF EXISTS create_and_insert$$
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql>
mysql> CREATE PROCEDURE create_and_insert(
    ->     IN tb_name TEXT,
    ->     IN val INT
    -> )
    -> BEGIN
    ->     -- 引用标识符
    ->     SET @quoted_tb_name = quote_identifier(tb_name);
    ->     SET @quoted_val = QUOTE(val);
    ->
    ->     -- 创建表
    ->     SET @create_sql = CONCAT('CREATE TABLE IF NOT EXISTS ', @quoted_tb_name, ' (i INT)');
    ->     CALL exec_stmt(@create_sql);
    ->
    ->     -- 插入数据
    ->     SET @insert_sql = CONCAT('INSERT INTO ', @quoted_tb_name, ' (i) VALUES (', val, ')');
    ->     CALL exec_stmt(@insert_sql);
    -> END$$
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> DELIMITER ;
mysql> -- 设置变量
mysql> SET @tb_name = 'test_table';
Query OK, 0 rows affected (0.00 sec)

mysql> SET @val = 123;
Query OK, 0 rows affected (0.00 sec)

mysql>
mysql> -- 调用存储过程
mysql> CALL create_and_insert(@tb_name, @val);
Query OK, 0 rows affected (0.02 sec)
关键修正点
将函数和过程分开定义

使用参数传递而不是会话变量

修正INSERT语句语法(使用INTO而不是TABLE)

添加IF NOT EXISTS防止表已存在错误

移除了不必要的QUOTE()调用(数值不需要引号)

9.10. 处理存储程序中的错误 347

sql 复制代码
mysql> DROP procedure IF EXISTS us_population$$
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql>
mysql> CREATE procedure us_population()
    -> BEGIN
    ->    declare done boolean default false;
    ->    declare state_pop, total_pop bigint default 0;
    ->    declare cur cursor for select pop from states;
    ->    declare continue handler for not found set done= true;
    ->
    -> open cur;
    -> fetch_loop: loop
    -> fetch cur into state_pop;
    -> if done then
    -> leave fetch_loop;
    -> end if;
    -> set total_pop  = total_pop + state_pop;
    -> end loop;
    -> close cur;
    -> select total_pop as 'total U.S. Population';
    -> END$$
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> DELIMITER ;
mysql> call us_population();
+-----------------------+
| total U.S. Population |
+-----------------------+
|             331223695 |
+-----------------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.01 sec)

mysql> select sum(pop) as 'total U.S.Population' from states;
+----------------------+
| total U.S.Population |
+----------------------+
|            331223695 |
+----------------------+
1 row in set (0.00 sec)

mysql> DELIMITER $$
mysql>
mysql> DROP PROCEDURE IF EXISTS drop_user$$
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql>
mysql> CREATE PROCEDURE drop_user(
    ->     IN username VARCHAR(60),
    ->     IN hostname VARCHAR(60)
    -> )
    -> BEGIN
    ->     -- 检查用户是否存在
    ->     IF EXISTS (SELECT 1 FROM mysql.user WHERE user = username AND host = hostname) THEN
    ->         -- 动态生成并执行DROP USER语句
    ->         SET @sql = CONCAT('DROP USER ''', username, '''@''', hostname, '''');
    ->         PREPARE stmt FROM @sql;
    ->         EXECUTE stmt;
    ->         DEALLOCATE PREPARE stmt;
    ->         SELECT CONCAT('用户 ''', username, '''@''', hostname, ''' 已删除') AS message;
    ->     ELSE
    ->         SELECT CONCAT('用户 ''', username, '''@''', hostname, ''' 不存在') AS message;
    ->     END IF;
    -> END$$
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> DELIMITER ;

mysql> CALL drop_user('bad_user', 'localhost');
+------------------------------------+
| message                            |
+------------------------------------+
| 用户 'bad_user'@'localhost' 不存在 |
+------------------------------------+
1 row in set (0.01 sec)

Query OK, 0 rows affected (0.01 sec)

mysql> DELIMITER $$
mysql>
mysql> DROP PROCEDURE IF EXISTS drop_user_warn$$
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql>
mysql> CREATE PROCEDURE drop_user_warn(
    ->     IN username VARCHAR(60),
    ->     IN hostname VARCHAR(60)
    -> )
    -> BEGIN
    ->     DECLARE user_exists INT DEFAULT 0;
    ->
    ->     -- 检查用户是否存在
    ->     SELECT COUNT(*) INTO user_exists
    ->     FROM mysql.user
    ->     WHERE user = username AND host = hostname;
    ->
    ->     -- 根据检查结果执行操作
    ->     IF user_exists > 0 THEN
    ->         SET @sql = CONCAT('DROP USER ''', username, '''@''', hostname, '''');
    ->         PREPARE stmt FROM @sql;
    ->         EXECUTE stmt;
    ->         DEALLOCATE PREPARE stmt;
    ->         SELECT CONCAT('用户 ''', username, '''@''', hostname, ''' 已成功删除') AS message;
    ->     ELSE
    ->         SELECT CONCAT('用户 ''', username, '''@''', hostname, ''' 不存在,无需删除') AS message;
    ->     END IF;
    -> END$$
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> DELIMITER ;
mysql> call drop_user_warn('bad_user', 'localhost');
+----------------------------------------------+
| message                                      |
+----------------------------------------------+
| 用户 'bad_user'@'localhost' 不存在,无需删除 |
+----------------------------------------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.02 sec)

mysql> show warnings;
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                   |
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Error | 1064 | You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'warning' at line 1 |
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
9.11 利用触发器预处理或拒绝数据 351
mysql> CREATE TABLE cookbook.contact_info (
    ->     id INT not null AUTO_INCREMENT ,
    ->     name VARCHAR(100) NOT NULL,
    ->     state  char(2),
    ->     email VARCHAR(100),
    ->     url   varchar(255),
    ->     phone VARCHAR(20),
    ->     primary key (id)
    -> );
Query OK, 0 rows affected (0.06 sec)

DELIMITER $$

DROP TRIGGER IF EXISTS bi_contact_info$$

CREATE TRIGGER bi_contact_info BEFORE INSERT ON contact_info
FOR EACH ROW
BEGIN
    -- 验证州代码
    IF (SELECT COUNT(*) FROM states WHERE abbrev = NEW.state) = 0 THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'Invalid state code';
    END IF;
    
    -- 验证电子邮件格式
    IF INSTR(NEW.email, '@') = 0 THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'Invalid email address';
    END IF;
    
    -- 清理URL
    SET NEW.url = TRIM(LEADING 'http://' FROM NEW.url);
END$$

DELIMITER ;
主要修正点:
1.	SQLSTATE 值:
o	将 hy000 改为标准的 45000(通用错误状态码)
o	注意拼写:sqlstae → SQLSTATE
2.	语法修正:
o	添加了缺失的分号 ; 在 END IF 后
o	修正了 message_text 拼写为 MESSAGE_TEXT
o	修正了 mysql_errno 为正确的 MYSQL_ERRNO(但通常不需要)
3.	逻辑调整:
o	移除了不必要的错误代码设置(1525)
o	使用标准错误消息格式

mysql> -- 只使用有效的美国州缩写
mysql> INSERT INTO contact_info (name, email, phone, state, url)
    -> VALUES
    ->     ('张三', 'zhangsan@example.com', '13800138001', 'NY', 'http://www.zhangsan.com'),
    ->     ('李四', 'lisi@example.com', '13900139002', 'CA', 'https://lisi.org'),
    ->     ('王五', 'wangwu@example.com', '13700137003', 'TX', 'http://wangwu.net'),
    ->     ('赵六', 'zhaoliu@example.com', '13600136004', 'FL', 'https://zhaoliu.me'),
    ->     ('钱七', 'qianqi@example.com', '13500135005', 'IL', 'http://qianqi.edu');
Query OK, 5 rows affected (0.01 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql>
mysql> -- 验证插入结果
mysql> SELECT * FROM contact_info;
+----+------+-------+----------------------+--------------------+-------------+
| id | name | state | email                | url                | phone       |
+----+------+-------+----------------------+--------------------+-------------+
|  6 | 张三 | NY    | zhangsan@example.com | www.zhangsan.com   | 13800138001 |
|  7 | 李四 | CA    | lisi@example.com     | https://lisi.org   | 13900139002 |
|  8 | 王五 | TX    | wangwu@example.com   | wangwu.net         | 13700137003 |
|  9 | 赵六 | FL    | zhaoliu@example.com  | https://zhaoliu.me | 13600136004 |
| 10 | 钱七 | IL    | qianqi@example.com   | qianqi.edu         | 13500135005 |
+----+------+-------+----------------------+--------------------+-------------+
5 rows in set (0.00 sec)
复制代码
相关推荐
知识分享小能手20 分钟前
React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)
前端·javascript·vue.js·学习·react.js·vue·react
luckys.one22 分钟前
第9篇:Freqtrade量化交易之config.json 基础入门与初始化
javascript·数据库·python·mysql·算法·json·区块链
茯苓gao3 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾3 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
jakeswang4 小时前
全解MySQL之死锁问题分析、事务隔离与锁机制的底层原理剖析
数据库·mysql
DKPT4 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
aaaweiaaaaaa4 小时前
HTML和CSS学习
前端·css·学习·html
一成码农4 小时前
MySQL问题7
数据库·mysql
看海天一色听风起雨落5 小时前
Python学习之装饰器
开发语言·python·学习
Kt&Rs6 小时前
MySQL复制技术的发展历程
数据库·mysql