若要建立或刪除觸發程序,請使用 CREATE TRIGGER
或 DROP TRIGGER
語句,詳述於 第 15.1.22 節,「CREATE TRIGGER 語句」 和 第 15.1.34 節,「DROP TRIGGER 語句」。
以下為將觸發程序與資料表關聯的簡單範例,以針對 INSERT
作業啟動。觸發程序會作為累積器,將插入資料表其中一個欄位中的值加總。
mysql> CREATE TABLE account (acct_num INT, amount DECIMAL(10,2));
Query OK, 0 rows affected (0.03 sec)
mysql> CREATE TRIGGER ins_sum BEFORE INSERT ON account
FOR EACH ROW SET @sum = @sum + NEW.amount;
Query OK, 0 rows affected (0.01 sec)
CREATE TRIGGER
語句會建立名為 ins_sum
的觸發程序,該觸發程序與 account
資料表相關聯。它也包含指定觸發程序動作時間、觸發事件以及觸發程序啟動時要執行的動作的子句。
關鍵字
BEFORE
表示觸發程序動作時間。在此情況下,觸發程序會在每次將列插入資料表之前啟動。此處允許的其他關鍵字為AFTER
。關鍵字
INSERT
表示觸發事件;也就是啟動觸發程序的操作類型。在此範例中,INSERT
作業會導致觸發程序啟動。您也可以為DELETE
和UPDATE
作業建立觸發程序。FOR EACH ROW
後面的語句會定義觸發程序主體;也就是每次觸發程序啟動時要執行的語句,每次觸發事件影響的每一列都會執行一次。在此範例中,觸發程序主體是一個簡單的SET
,可將插入amount
欄位中的值累積到使用者變數中。該語句將欄位稱為NEW.amount
,表示 「要插入新列的amount
欄位的值。」
若要使用觸發程序,請將累積器變數設為零,執行 INSERT
語句,然後查看變數之後的值。
mysql> SET @sum = 0;
mysql> INSERT INTO account VALUES(137,14.98),(141,1937.50),(97,-100.00);
mysql> SELECT @sum AS 'Total amount inserted';
+-----------------------+
| Total amount inserted |
+-----------------------+
| 1852.48 |
+-----------------------+
在此情況下,INSERT
語句執行後,@sum
的值為 14.98 + 1937.50 - 100
,即 1852.48
。
若要銷毀觸發程序,請使用 DROP TRIGGER
語句。如果觸發程序不在預設綱要中,您必須指定綱要名稱。
mysql> DROP TRIGGER test.ins_sum;
如果您刪除資料表,則也會刪除該資料表的所有觸發程序。
觸發程序名稱存在於綱要命名空間中,表示所有觸發程序在綱要內都必須具有唯一的名稱。不同綱要中的觸發程序可以具有相同的名稱。
可以為給定資料表定義多個具有相同觸發事件和動作時間的觸發程序。例如,您可以為資料表設定兩個 BEFORE UPDATE
觸發程序。依預設,具有相同觸發事件和動作時間的觸發程序會依其建立的順序啟動。若要影響觸發程序順序,請在 FOR EACH ROW
後指定一個子句,指示 FOLLOWS
或 PRECEDES
以及也具有相同觸發事件和動作時間的現有觸發程序名稱。使用 FOLLOWS
時,新的觸發程序會在現有的觸發程序之後啟動。使用 PRECEDES
時,新的觸發程序會在現有的觸發程序之前啟動。
例如,以下觸發程序定義為 account
資料表定義另一個 BEFORE INSERT
觸發程序。
mysql> CREATE TRIGGER ins_transaction BEFORE INSERT ON account
FOR EACH ROW PRECEDES ins_sum
SET
@deposits = @deposits + IF(NEW.amount>0,NEW.amount,0),
@withdrawals = @withdrawals + IF(NEW.amount<0,-NEW.amount,0);
Query OK, 0 rows affected (0.01 sec)
此觸發程序 ins_transaction
與 ins_sum
相似,但會分別累積存款和提款。它具有 PRECEDES
子句,使其在 ins_sum
之前啟動;如果沒有該子句,則會在 ins_sum
之後啟動,因為它是在 ins_sum
之後建立的。
在觸發程序主體中,OLD
和 NEW
關鍵字可讓您存取受觸發程序影響的列中的欄位。OLD
和 NEW
是 MySQL 對觸發程序的延伸;它們不區分大小寫。
在 INSERT
觸發程序中,只能使用 NEW.
;沒有舊列。在 col_name
DELETE
觸發程序中,只能使用 OLD.
;沒有新列。在 col_name
UPDATE
觸發程序中,您可以使用 OLD.
來參考更新前列的欄位,並使用 col_name
NEW.
來參考更新後列的欄位。col_name
以 OLD
命名的欄位為唯讀。您可以參考它(如果您具有 SELECT
權限),但不能修改它。如果您對以 NEW
命名的欄位具有 SELECT
權限,則可以參考它。在 BEFORE
觸發程序中,如果您對其具有 UPDATE
權限,您也可以使用 SET NEW.
變更其值。這表示您可以使用觸發程序來修改要插入新列或用於更新列的值。(這樣的 col_name
= value
SET
語句在 AFTER
觸發程序中沒有任何作用,因為列變更已發生。)
在 BEFORE
觸發程序中,AUTO_INCREMENT
資料行的 NEW
值為 0,而不是當新列實際插入時自動產生的序號。
透過使用 BEGIN ... END
結構,您可以定義一個執行多個語句的觸發程序。在 BEGIN
區塊中,您也可以使用儲存程序內允許的其他語法,例如條件式和迴圈。然而,如同儲存程序一樣,如果您使用 mysql 程式來定義執行多個語句的觸發程序,則必須重新定義 mysql 語句分隔符號,以便您可以在觸發程序定義中使用 ;
語句分隔符號。以下範例說明了這些重點。它定義一個 UPDATE
觸發程序,檢查用於更新每一列的新值,並修改該值使其在 0 到 100 的範圍內。這必須是一個 BEFORE
觸發程序,因為該值必須在用於更新列之前進行檢查。
mysql> delimiter //
mysql> CREATE TRIGGER upd_check BEFORE UPDATE ON account
FOR EACH ROW
BEGIN
IF NEW.amount < 0 THEN
SET NEW.amount = 0;
ELSEIF NEW.amount > 100 THEN
SET NEW.amount = 100;
END IF;
END;//
mysql> delimiter ;
單獨定義一個儲存程序,然後使用簡單的 CALL
語句從觸發程序中調用它可能會更容易。如果您想從多個觸發程序中執行相同的程式碼,這也很有優勢。
當觸發程序被啟動時,其執行的語句中可以出現的內容存在限制。
觸發程序不能使用
CALL
語句來調用會將資料返回給用戶端或使用動態 SQL 的儲存程序。(儲存程序允許通過OUT
或INOUT
參數將資料返回給觸發程序。)觸發程序不能使用明確或隱含地開始或結束交易的語句,例如
START TRANSACTION
、COMMIT
或ROLLBACK
。(允許使用ROLLBACK to SAVEPOINT
,因為它不會結束交易。)。
另請參閱 第 27.9 節,〈儲存程序的限制〉。
MySQL 在觸發程序執行期間處理錯誤的方式如下:
如果
BEFORE
觸發程序失敗,則不會執行對應列的操作。BEFORE
觸發程序會在嘗試插入或修改列時被啟動,無論該嘗試隨後是否成功。只有在任何
BEFORE
觸發程序和列操作成功執行後,才會執行AFTER
觸發程序。BEFORE
或AFTER
觸發程序中的錯誤都會導致觸發程序調用的整個語句失敗。對於交易表,語句的失敗應導致該語句所執行所有變更的回滾。觸發程序的失敗會導致語句失敗,因此觸發程序失敗也會導致回滾。對於非交易表,無法執行此類回滾,因此儘管語句失敗,但在錯誤點之前執行的任何變更仍然有效。
觸發程序可以包含對表名稱的直接參考,例如此範例中所示名為 testref
的觸發程序。
CREATE TABLE test1(a1 INT);
CREATE TABLE test2(a2 INT);
CREATE TABLE test3(a3 INT NOT NULL AUTO_INCREMENT PRIMARY KEY);
CREATE TABLE test4(
a4 INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
b4 INT DEFAULT 0
);
delimiter |
CREATE TRIGGER testref BEFORE INSERT ON test1
FOR EACH ROW
BEGIN
INSERT INTO test2 SET a2 = NEW.a1;
DELETE FROM test3 WHERE a3 = NEW.a1;
UPDATE test4 SET b4 = b4 + 1 WHERE a4 = NEW.a1;
END;
|
delimiter ;
INSERT INTO test3 (a3) VALUES
(NULL), (NULL), (NULL), (NULL), (NULL),
(NULL), (NULL), (NULL), (NULL), (NULL);
INSERT INTO test4 (a4) VALUES
(0), (0), (0), (0), (0), (0), (0), (0), (0), (0);
假設您將以下值插入到表 test1
中,如下所示:
mysql> INSERT INTO test1 VALUES
(1), (3), (1), (7), (1), (8), (4), (4);
Query OK, 8 rows affected (0.01 sec)
Records: 8 Duplicates: 0 Warnings: 0
因此,四個表包含以下資料:
mysql> SELECT * FROM test1;
+------+
| a1 |
+------+
| 1 |
| 3 |
| 1 |
| 7 |
| 1 |
| 8 |
| 4 |
| 4 |
+------+
8 rows in set (0.00 sec)
mysql> SELECT * FROM test2;
+------+
| a2 |
+------+
| 1 |
| 3 |
| 1 |
| 7 |
| 1 |
| 8 |
| 4 |
| 4 |
+------+
8 rows in set (0.00 sec)
mysql> SELECT * FROM test3;
+----+
| a3 |
+----+
| 2 |
| 5 |
| 6 |
| 9 |
| 10 |
+----+
5 rows in set (0.00 sec)
mysql> SELECT * FROM test4;
+----+------+
| a4 | b4 |
+----+------+
| 1 | 3 |
| 2 | 0 |
| 3 | 1 |
| 4 | 2 |
| 5 | 0 |
| 6 | 0 |
| 7 | 1 |
| 8 | 1 |
| 9 | 0 |
| 10 | 0 |
+----+------+
10 rows in set (0.00 sec)