觸發器是一種規格,指定資料庫應在執行特定類型的操作時自動執行特定的函式。 觸發器可以附加到表(分割或未分割)、檢視表和外部表。
在表和外部表上,可以定義觸發器以在任何 INSERT
、UPDATE
或 DELETE
操作之前或之後執行,可以是針對每個修改的列執行一次,或針對每個SQL陳述式執行一次。UPDATE
觸發器還可以設定為僅在 UPDATE
陳述式的 SET
子句中提及某些欄位時觸發。 觸發器也可以觸發 TRUNCATE
陳述式。 如果發生觸發事件,則會在適當的時間呼叫觸發器的函式以處理該事件。
在檢視表上,可以定義觸發器以執行來取代 INSERT
、UPDATE
或 DELETE
操作。 這種 INSTEAD OF
觸發器會針對需要在檢視表中修改的每一列觸發一次。 觸發器的函式有責任對檢視表的底層基底表執行必要的修改,並在適當的情況下,傳回修改後的列,使其顯示在檢視表中。 檢視表上的觸發器也可以定義為針對每個SQL陳述式,在 INSERT
、UPDATE
或 DELETE
操作之前或之後執行。 然而,只有在檢視表上也有 INSTEAD OF
觸發器時,才會觸發此類觸發器。 否則,任何針對檢視表的陳述式都必須重寫為影響其底層基底表的陳述式,然後將觸發的觸發器是附加到基底表的那些。
必須先定義觸發器函式,然後才能建立觸發器本身。 觸發器函式必須宣告為不帶任何引數且傳回類型為 trigger
的函式。 (觸發器函式透過特別傳遞的 TriggerData
結構接收其輸入,而不是以普通函式引數的形式接收。)
建立合適的觸發器函式後,使用 CREATE TRIGGER 建立觸發器。 同一個觸發器函式可以用於多個觸發器。
PostgreSQL 提供 每一列觸發器和每一陳述式觸發器。 使用每一列觸發器,觸發器函式會針對受觸發陳述式影響的每一列呼叫一次。 相反地,每一陳述式觸發器僅在執行適當的陳述式時呼叫一次,無論受該陳述式影響的列數是多少。 特別是,影響零列的陳述式仍然會導致執行任何適用的每一陳述式觸發器。 這兩種觸發器有時分別稱為列級別觸發器和陳述式級別觸發器。 TRUNCATE
上的觸發器只能在陳述式級別定義,而不能在每一列定義。
觸發器也根據它們在操作之前、之後或取代時觸發進行分類。 這些分別稱為 BEFORE
觸發器、AFTER
觸發器和 INSTEAD OF
觸發器。 陳述式級別 BEFORE
觸發器自然會在陳述式開始執行任何操作之前觸發,而陳述式級別 AFTER
觸發器會在陳述式的最後觸發。 這些類型的觸發器可以在表、檢視表或外部表上定義。 列級別 BEFORE
觸發器會在對特定列進行操作之前立即觸發,而列級別 AFTER
觸發器會在陳述式的最後觸發(但在任何陳述式級別 AFTER
觸發器之前)。 這些類型的觸發器只能在表和外部表上定義,而不能在檢視表上定義。 INSTEAD OF
觸發器只能在檢視表上定義,並且只能在列級別定義;它們會在識別檢視表中的每一列需要操作時立即觸發。
如果 AFTER
觸發器定義為約束觸發器,則可以將其執行延遲到交易結束,而不是陳述式結束。 在所有情況下,觸發器都會作為觸發它的陳述式同一交易的一部分執行,因此如果陳述式或觸發器導致錯誤,則兩者的影響都將回滾。
如果 INSERT
包含 ON CONFLICT DO UPDATE
子句,則可以在觸發的列上執行列級別 BEFORE
INSERT
和 BEFORE
UPDATE
觸發器。 如果觸發器不是等冪的,這種互動可能會很複雜,因為 BEFORE
INSERT
觸發器所做的變更將被 BEFORE
UPDATE
觸發器看到,包括對 EXCLUDED
欄位的變更。
請注意,無論 UPDATE
影響了多少資料列(以及無論是否曾經採用替代的 UPDATE
路徑),只要指定了 ON CONFLICT DO UPDATE
,就會執行陳述式層級的 UPDATE
觸發程序。帶有 ON CONFLICT DO UPDATE
子句的 INSERT
會先執行陳述式層級的 BEFORE
INSERT
觸發程序,然後執行陳述式層級的 BEFORE
UPDATE
觸發程序,接著執行陳述式層級的 AFTER
UPDATE
觸發程序,最後執行陳述式層級的 AFTER
INSERT
觸發程序。
針對繼承或分割階層中的父資料表執行的陳述式,不會觸發受影響子資料表的陳述式層級觸發程序;只會觸發父資料表的陳述式層級觸發程序。但是,任何受影響子資料表的資料列層級觸發程序都會被觸發。
如果分割資料表上的 UPDATE
導致資料列移動到另一個分割區,它將執行為從原始分割區 DELETE
,然後 INSERT
到新分割區。在這種情況下,所有資料列層級的 BEFORE
UPDATE
觸發程序和所有資料列層級的 BEFORE
DELETE
觸發程序都會在原始分割區上觸發。然後,所有資料列層級的 BEFORE
INSERT
觸發程序都會在目標分割區上觸發。當所有這些觸發程序影響到正在移動的資料列時,應該考慮到可能發生的意外結果。就 AFTER ROW
觸發程序而言,會套用 AFTER
DELETE
和 AFTER
INSERT
觸發程序;但是不會套用 AFTER
UPDATE
觸發程序,因為 UPDATE
已轉換為 DELETE
和 INSERT
。就陳述式層級的觸發程序而言,即使發生資料列移動,也不會觸發任何 DELETE
或 INSERT
觸發程序;只會觸發在 UPDATE
陳述式中使用的目標資料表上定義的 UPDATE
觸發程序。
沒有為 MERGE
定義單獨的觸發程序。相反,會根據 MERGE
查詢中指定的操作(對於陳述式層級的觸發程序)和執行的操作(對於資料列層級的觸發程序)觸發陳述式層級或資料列層級的 UPDATE
、DELETE
和 INSERT
觸發程序。
執行 MERGE
命令時,無論操作最終是否執行,都會針對 MERGE
命令的操作中指定的事件觸發陳述式層級的 BEFORE
和 AFTER
觸發程序。這與更新零資料列的 UPDATE
陳述式相同,但仍會觸發陳述式層級的觸發程序。只有在實際更新、插入或刪除資料列時,才會觸發資料列層級的觸發程序。因此,為某些類型的操作觸發陳述式層級的觸發程序,而沒有為相同類型的操作觸發資料列層級的觸發程序是完全合法的。
由陳述式觸發程序呼叫的觸發程序函數應始終返回 NULL
。由資料列觸發程序呼叫的觸發程序函數可以選擇將一個資料表列(類型為 HeapTuple
的值)返回給呼叫的執行器。在操作之前觸發的資料列層級觸發程序有以下選擇:
它可以返回 NULL
以跳過當前資料列的操作。這指示執行器不要執行呼叫觸發程序的資料列層級操作(特定資料表列的插入、修改或刪除)。
僅對於資料列層級的 INSERT
和 UPDATE
觸發程序,返回的資料列會成為將被插入或將替換正在更新的資料列的資料列。這允許觸發程序函數修改正在插入或更新的資料列。
不打算引起這兩種行為的資料列層級 BEFORE
觸發程序必須小心地將傳入的相同資料列作為其結果返回(也就是說,對於 INSERT
和 UPDATE
觸發程序,是 NEW
資料列;對於 DELETE
觸發程序,是 OLD
資料列)。
一個資料列層級的 INSTEAD OF
觸發程序,要嘛返回 NULL
以指示它沒有修改檢視表底層的任何基礎資料表資料,要嘛返回傳入的檢視表資料列(對於 INSERT
和 UPDATE
操作,是 NEW
資料列;對於 DELETE
操作,是 OLD
資料列)。非空的返回值用於表示觸發程序在檢視表中執行了必要的資料修改。這將導致受該命令影響的資料列計數遞增。僅對於 INSERT
和 UPDATE
操作,觸發程序可以在返回 NEW
資料列之前對其進行修改。這將更改 INSERT RETURNING
或 UPDATE RETURNING
返回的資料,並且當檢視表不會顯示與提供的資料完全相同的資料時,這非常有用。
對於在操作後觸發的資料列層級觸發程序,返回值會被忽略,因此它們可以返回 NULL
。
某些考量適用於產生的欄位。 儲存的產生欄位在 BEFORE
觸發程序之後和 AFTER
觸發程序之前計算。因此,可以在 AFTER
觸發程序中檢查產生的值。在 BEFORE
觸發程序中,OLD
資料列包含舊的產生值,正如人們所期望的那樣,但 NEW
資料列尚未包含新的產生值,不應存取。在 C 語言介面中,此時欄位的內容是未定義的;較高層級的程式語言應防止在 BEFORE
觸發程序中存取 NEW
資料列中的儲存產生欄位。在 BEFORE
觸發程序中對產生欄位值的變更會被忽略並覆寫。
如果在同一關係上的同一事件定義了多個觸發程序,則觸發程序將按觸發程序名稱的字母順序觸發。對於 BEFORE
和 INSTEAD OF
觸發程序,每個觸發程序返回的可能已修改的資料列將成為下一個觸發程序的輸入。如果任何 BEFORE
或 INSTEAD OF
觸發程序返回 NULL
,則該操作將被放棄,並且不會觸發後續的觸發程序(對於該資料列)。
觸發程序定義也能夠指定一個布林值 WHEN
條件,這個條件會被測試,以判斷是否應該觸發該觸發程序。在資料列層級的觸發程序中,WHEN
條件可以檢查資料列的欄位的舊值和/或新值。(語句層級的觸發程序也可以有 WHEN
條件,儘管這個功能對它們來說沒有那麼有用。)在 BEFORE
觸發程序中,WHEN
條件會在函式即將或將被執行之前進行評估,因此使用 WHEN
與在觸發程序函式的一開始測試相同的條件沒有實質上的區別。然而,在 AFTER
觸發程序中,WHEN
條件會在資料列更新發生之後立即進行評估,並且它會決定是否將一個事件放入佇列,以便在語句結束時觸發該觸發程序。所以當一個 AFTER
觸發程序的 WHEN
條件沒有返回 true 時,就不需要將一個事件放入佇列,也不需要在語句結束時重新抓取該資料列。如果觸發程序只需要為少數資料列觸發,這可以在修改許多資料列的語句中帶來顯著的速度提升。INSTEAD OF
觸發程序不支援 WHEN
條件。
通常,資料列層級的 BEFORE
觸發程序會被用於檢查或修改將被插入或更新的資料。例如,BEFORE
觸發程序可能被用於將目前時間插入到 timestamp
欄位中,或檢查資料列的兩個元素是否一致。資料列層級的 AFTER
觸發程序最適合被用於將更新傳播到其他資料表,或對其他資料表進行一致性檢查。這種分工的原因是 AFTER
觸發程序可以確定它看到的是資料列的最終值,而 BEFORE
觸發程序則不能;可能會有其他 BEFORE
觸發程序在其之後觸發。如果您沒有特定的理由將觸發程序設為 BEFORE
或 AFTER
,那麼 BEFORE
情況會更有效率,因為關於操作的資訊不需要儲存到語句結束。
如果一個觸發程序函式執行 SQL 指令,那麼這些指令可能會再次觸發觸發程序。這被稱為串聯觸發程序(cascading triggers)。對於串聯層級的數量沒有直接的限制。串聯可能會導致同一個觸發程序的遞迴呼叫;例如,一個 INSERT
觸發程序可能會執行一個指令,該指令會在同一個資料表中插入一個額外的資料列,導致 INSERT
觸發程序再次被觸發。避免在這種情況下出現無限遞迴是觸發程序程式設計師的責任。
如果一個 foreign key 限制指定了參考動作(referential actions,也就是串聯更新或刪除),那麼這些動作會透過對參考資料表執行普通的 SQL 更新或刪除指令來完成。特別是,存在於參考資料表上的任何觸發程序都會為這些變更而被觸發。如果這樣一個觸發程序修改或阻止了其中一個指令的效果,最終結果可能會破壞參考完整性。避免這種情況是觸發程序程式設計師的責任。
定義觸發程序時,可以為其指定引數。在觸發程序定義中包含引數的目的是允許具有相似需求的不同觸發程序呼叫同一個函式。舉例來說,可能有一個廣義的觸發程序函式,它將兩個欄位名稱作為引數,並將當前使用者放入一個欄位,將當前時間戳記放入另一個欄位。如果編寫得當,這個觸發程序函式將獨立於它所觸發的特定資料表。因此,同一個函式可以被用於任何具有合適欄位的資料表上的 INSERT
事件,例如,自動追蹤交易資料表中記錄的建立。如果定義為 UPDATE
觸發程序,它也可以被用於追蹤上次更新事件。
每種支援觸發程序的程式語言都有其自己的方法,使觸發程序的輸入資料可供觸發程序函式使用。此輸入資料包括觸發程序事件的類型(例如,INSERT
或 UPDATE
),以及在 CREATE TRIGGER
中列出的任何引數。對於資料列層級的觸發程序,輸入資料還包括 INSERT
和 UPDATE
觸發程序的 NEW
資料列,以及/或 UPDATE
和 DELETE
觸發程序的 OLD
資料列。
預設情況下,語句層級的觸發程序無法檢查語句修改的個別資料列。但是 AFTER STATEMENT
觸發程序可以請求建立轉換資料表(transition tables),以使受影響的資料列集合可供觸發程序使用。AFTER ROW
觸發程序也可以請求轉換資料表,以便它們可以查看資料表中的總變更,以及它們目前正在觸發的個別資料列中的變更。檢查轉換資料表的方法再次取決於所使用的程式語言,但典型的方法是使轉換資料表像唯讀的暫存資料表一樣,可以透過在觸發程序函式中發出的 SQL 指令來存取。
如果您在文件中發現任何不正確、與您使用特定功能時的體驗不符或需要進一步說明的地方,請使用此表單來報告文件問題。