MySQL 8.4 參考手冊  /  ...  /  觸發器語法與範例

27.3.1 觸發器語法與範例

若要建立或刪除觸發器,請使用 CREATE TRIGGERDROP 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 作業會導致觸發器啟動。您也可以為 DELETEUPDATE 作業建立觸發器。

  • 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 後面指定一個子句,指出 FOLLOWSPRECEDES 以及也具有相同觸發事件和作用時間的現有觸發器的名稱。使用 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_transactionins_sum 類似,但會分別累加存款和提款。它有一個 PRECEDES 子句,會使其在 ins_sum 之前啟動;如果沒有該子句,它會因為是在 ins_sum 之後建立而會在 ins_sum 之後啟動。

在觸發器主體中,OLDNEW 關鍵字可讓您存取受觸發器影響的列中的欄位。OLDNEW 是 MySQL 對觸發器的擴充;它們不區分大小寫。

INSERT 觸發器中,只能使用 NEW.col_name;沒有舊的列。在 DELETE 觸發器中,只能使用 OLD.col_name;沒有新的列。在 UPDATE 觸發器中,您可以使用 OLD.col_name 來參照更新前列的欄位,並使用 NEW.col_name 來參照更新後列的欄位。

名稱為 OLD 的欄位是唯讀的。您可以參照它(如果您有 SELECT 權限),但不能修改它。如果您具有其 SELECT 權限,則可以參照名稱為 NEW 的欄位。在 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 的儲存程序。(儲存程序允許透過 OUTINOUT 參數將資料返回給觸發程序。)

  • 觸發程序不能使用明確或隱含地開始或結束交易的陳述式,例如 START TRANSACTIONCOMMITROLLBACK。(允許 ROLLBACK to SAVEPOINT,因為它不會結束交易。)。

另請參閱 第 27.8 節,「儲存程序的限制」

MySQL 在觸發程序執行期間會以下列方式處理錯誤

  • 如果 BEFORE 觸發程序失敗,則不會執行對應列上的操作。

  • BEFORE 觸發程序會因嘗試插入或修改列的 嘗試 而啟動,無論該嘗試是否隨後成功。

  • 只有在任何 BEFORE 觸發程序和列操作成功執行後,才會執行 AFTER 觸發程序。

  • BEFOREAFTER 觸發程序期間發生的錯誤會導致導致觸發程序調用的整個陳述式失敗。

  • 對於交易式表格,陳述式的失敗應導致回滾該陳述式執行的所有變更。觸發程序的失敗會導致陳述式失敗,因此觸發程序失敗也會導致回滾。對於非交易式表格,無法進行此類回滾,因此雖然陳述式失敗,但在錯誤發生點之前執行的任何變更仍會生效。

觸發程序可以包含按名稱直接引用表格,例如此範例中顯示的觸發程序名稱 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)