文件首頁
MySQL 9.0 參考手冊
相關文件 下載本手冊
PDF (US Ltr) - 40.0Mb
PDF (A4) - 40.1Mb
Man Pages (TGZ) - 258.2Kb
Man Pages (Zip) - 365.3Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 9.0 參考手冊  /  ...  /  觸發程序語法和範例

27.4.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 權限),但不能修改它。如果您對以 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 的儲存程序。(儲存程序允許通過 OUTINOUT 參數將資料返回給觸發程序。)

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

另請參閱 第 27.9 節,〈儲存程序的限制〉

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)