參考:《MySQL必知必會》Ben Forta著,第25章 使用觸發器
觸發器
考慮如下場景,
- 每當增加一個顧客到某個數據庫表時,都檢查其電話號碼格式是否正確,州的縮寫是否爲大寫;
- 每當訂購一個產品時,都從庫存數量中減去訂購的數量;
- 無論何時刪除一行,都在某個存檔表中保留一個副本。
上述例子的共同之處 => 需要在某個表發生更改時自動處理 => 觸發器
觸發器是MySQL響應以下任意語句而自動執行的一條MySQL語句(或位於BEGIN和END語句之間的一組語句):
DELETE
INSERT
UPDATE
其它MySQL語句不支持觸發器。
創建觸發器
創建觸發器時,需要給出4條信息:
- 唯一的觸發器名
- 觸發器關聯的表
- 觸發器應該響應的活動(DELETE、INSERT或UPDATE)
- 觸發器何時執行(處理之前或之後)
注意:
1.只有表才支持觸發器,視圖、臨時表不支持。
2.觸發器按每個表每個事件每次地定義 =>
每個表每個事件每次只允許一個觸發器(每個表最多支持6個觸發器)
單一觸發器不能與多個事件或多個表關聯
【例】每次往表products插入數據,執行SELECT 'Product added'
mysql> CREATE TRIGGER newproduct AFTER INSERT ON products
-> FOR EACH ROW SELECT 'Product added';
ERROR 1415 (0A000): Not allowed to return a result set from a trigger
mysql>
報錯原因:MYSQL5以後,不允許觸發器返回任何結果
解決方法:使用 INTO @變量名,將結果賦值到變量中,再用SELECT調用
mysql> CREATE TRIGGER newproduct AFTER INSERT ON products FOR EACH ROW SELECT 'Product added' INTO @prompt;
Query OK, 0 rows affected (0.06 sec)
mysql> SELECT @prompt;
+---------+
| @prompt |
+---------+
| NULL |
+---------+
1 row in set (0.00 sec)
mysql>
通過插入數據觸發觸發器,
mysql> DESC products;
+------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| prod_id | char(10) | NO | PRI | NULL | |
| vend_id | int(11) | NO | MUL | NULL | |
| prod_name | char(255) | NO | | NULL | |
| prod_price | decimal(8,2) | NO | | NULL | |
| prod_desc | text | YES | | NULL | |
+------------+--------------+------+-----+---------+-------+
5 rows in set (0.01 sec)
mysql> INSERT INTO products
-> VALUES('SAFE2',1003,'Safe',50.00,'Safe with combination lock');
Query OK, 1 row affected (0.02 sec)
mysql> SELECT @prompt;
+---------------+
| @prompt |
+---------------+
| Product added |
+---------------+
1 row in set (0.00 sec)
mysql> DELETE FROM products WHERE prod_id='SAFE2';
Query OK, 1 row affected (0.04 sec)
mysql>
刪除觸發器
DROP TRIGGER newproduct;
注意:觸發器不能更新或覆蓋。爲了修改一個觸發器,必須先刪除它,然後再重新創建。
使用觸發器
1. INSERT 觸發器
- 在INSERT觸發器代碼內,可引用一個名爲NEW的虛擬表,訪問被插入的行;
- 在BEFORE INSERT觸發器中,NEW中的值也可以被更新(允許更改被插入的值);
- 通常,將BEFORE用於數據驗證和淨化(目的是保證插入表中的數據確實是需要的數據),此項也適用於UPDATE觸發器。
【例】插入一個新訂單到orders表時,MySQL生成一個新訂單號並保存到order_num中,觸發器從NEW.order_num取得這個值
說明:此觸發器必須按照AFTER INSERT執行,因爲在BEFORE INSERT語句執行之前,新order_num還沒有生成。
mysql> CREATE TRIGGER neworder AFTER INSERT ON orders
-> FOR EACH ROW SELECT NEW.order_num INTO @insertmp;
Query OK, 0 rows affected (0.06 sec)
mysql> INSERT INTO orders(order_date,cust_id)
-> VALUES(Now(),10001);
Query OK, 1 row affected (0.02 sec)
mysql> SELECT @insertmp;
+-----------+
| @insertmp |
+-----------+
| 20010 |
+-----------+
1 row in set (0.00 sec)
mysql> INSERT INTO orders(order_date,cust_id) VALUES(Now(),10001);
Query OK, 1 row affected (0.06 sec)
mysql> SELECT @insertmp;
+-----------+
| @insertmp |
+-----------+
| 20011 |
+-----------+
1 row in set (0.00 sec)
mysql>
2. DELETE 觸發器
- 在DELETE觸發器代碼內,你可以引用一個名爲OLD的虛擬表,訪問被刪除的行;
- OLD中的值全都是隻讀的,不能更新。
- 使用BEFORE DELETE觸發器的優點是(相對於AFTER DELETE觸發器),如果由於某種原因,訂單不能存檔,DELETE本身將被放棄。
【例】orders上創建DELETE觸發器,將OLD中的值(要刪除的訂單)保存到存檔表archive_orders
首先要新建存檔表archive_orders,與表orders結構一樣
mysql> SHOW CREATE TABLE orders\G;
*************************** 1. row ***************************
Table: orders
Create Table: CREATE TABLE `orders` (
`order_num` int(11) NOT NULL AUTO_INCREMENT,
`order_date` datetime NOT NULL,
`cust_id` int(11) NOT NULL,
PRIMARY KEY (`order_num`),
KEY `fk_orders_customers` (`cust_id`),
CONSTRAINT `fk_orders_customers` FOREIGN KEY (`cust_id`) REFERENCES `customers` (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=20012 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
ERROR:
No query specified
mysql>
mysql> CREATE TABLE `archive_orders` (
-> `order_num` int(11) NOT NULL AUTO_INCREMENT,
-> `order_date` datetime NOT NULL,
-> `cust_id` int(11) NOT NULL,
-> PRIMARY KEY (`order_num`),
-> KEY `fk_orders_customers` (`cust_id`),
-> CONSTRAINT `fk_orders_customers` FOREIGN KEY (`cust_id`) REFERENCES `customers` (`cust_id`)
-> ) ENGINE=InnoDB AUTO_INCREMENT=20012 DEFAULT CHARSET=latin1;
ERROR 1022 (23000): Can't write; duplicate key in table 'archive_orders'
外鍵衝突報錯解決:換一個外鍵名,如下
mysql> CREATE TABLE `archive_orders` (
-> `order_num` int(11) NOT NULL AUTO_INCREMENT,
-> `order_date` datetime NOT NULL,
-> `cust_id` int(11) NOT NULL,
-> PRIMARY KEY (`order_num`),
-> KEY `fk_ordersbk_customers` (`cust_id`),
-> CONSTRAINT `fk_ordersbk_customers` FOREIGN KEY (`cust_id`) REFERENCES `customers` (`cust_id`)
-> ) ENGINE=InnoDB AUTO_INCREMENT=20012 DEFAULT CHARSET=latin1;
Query OK, 0 rows affected (0.22 sec)
mysql> select * from archive_orders;
Empty set (0.00 sec)
mysql>
然後,創建DELETE觸發器
mysql> DELIMITER //
mysql> CREATE TRIGGER deleteorder BEFORE DELETE ON orders
-> FOR EACH ROW
-> BEGIN
-> INSERT INTO archive_orders(order_num,order_date,cust_id)
-> VALUES(OLD.order_num, OLD.order_date, OLD.cust_id);
-> END //
Query OK, 0 rows affected (0.05 sec)
mysql> DELIMITER ;
驗證DELETE觸發器
mysql> DELETE FROM orders WHERE order_num=20011;
Query OK, 1 row affected (0.02 sec)
mysql> select * from archive_orders;
+-----------+---------------------+---------+
| order_num | order_date | cust_id |
+-----------+---------------------+---------+
| 20011 | 2020-03-29 15:15:56 | 10001 |
+-----------+---------------------+---------+
1 row in set (0.00 sec)
mysql>
mysql> DELETE FROM orders WHERE order_num=20010;
Query OK, 1 row affected (0.04 sec)
mysql> select * from archive_orders;
+-----------+---------------------+---------+
| order_num | order_date | cust_id |
+-----------+---------------------+---------+
| 20010 | 2020-03-29 15:15:18 | 10001 |
| 20011 | 2020-03-29 15:15:56 | 10001 |
+-----------+---------------------+---------+
2 rows in set (0.00 sec)
mysql>
3. UPDATE 觸發器
- 在UPDATE觸發器代碼中,引用虛擬表OLD訪問UPDATE語句前的值,引用虛擬表NEW訪問更新的值;
- 在BEFORE UPDATE觸發器中,NEW中的值可能也被更新(允許更改將要用於UPDATE語句中的值);
- OLD中的值全都是隻讀的,不能更新。
【例】保證州名縮寫總是大寫(不管UPDATE語句中給出的是大寫還是小寫)
mysql> CREATE TRIGGER updatevendor BEFORE UPDATE ON vendors
-> FOR EACH ROW SET NEW.vend_state = Upper(New.vend_state);
Query OK, 0 rows affected (0.05 sec)
mysql> UPDATE vendors SET vend_state='ca' WHERE vend_id=1004;
Query OK, 1 row affected (0.03 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM vendors WHERE vend_id=1004;;
+---------+--------------+-----------------+-----------+------------+----------+--------------+
| vend_id | vend_name | vend_address | vend_city | vend_state | vend_zip | vend_country |
+---------+--------------+-----------------+-----------+------------+----------+--------------+
| 1004 | Furball Inc. | 1000 5th Avenue | New York | CA | 11111 | USA |
+---------+--------------+-----------------+-----------+------------+----------+--------------+
1 row in set (0.00 sec)
ERROR:
No query specified
mysql> UPDATE vendors SET vend_state='ny' WHERE vend_id=1004;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM vendors WHERE vend_id=1004;;
+---------+--------------+-----------------+-----------+------------+----------+--------------+
| vend_id | vend_name | vend_address | vend_city | vend_state | vend_zip | vend_country |
+---------+--------------+-----------------+-----------+------------+----------+--------------+
| 1004 | Furball Inc. | 1000 5th Avenue | New York | NY | 11111 | USA |
+---------+--------------+-----------------+-----------+------------+----------+--------------+
1 row in set (0.00 sec)
ERROR:
No query specified
mysql>