MySQL 筆記8 觸發器

參考:《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>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章