CREATE TRIGGER — 定义一个新的触发器
CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGERname{ BEFORE | AFTER | INSTEAD OF } {event[ OR ... ] } ONtable_name[ FROMreferenced_table_name] [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ] [ REFERENCING { { OLD | NEW } TABLE [ AS ]transition_relation_name} [ ... ] ] [ FOR [ EACH ] { ROW | STATEMENT } ] [ WHEN (condition) ] EXECUTE { FUNCTION | PROCEDURE }function_name(arguments) whereeventcan be one of: INSERT UPDATE [ OFcolumn_name[, ... ] ] DELETE TRUNCATE
CREATE TRIGGER 创建一个新的触发器。CREATE OR REPLACE TRIGGER 会创建一个新的触发器,或者替换一个已有的触发器。该触发器将与指定的表、视图或外部表相关联,并且当对该表执行某些操作时,会执行指定的函数 function_name。
要替换一个已有触发器的当前定义,可以使用 CREATE OR REPLACE TRIGGER,并指定已有触发器的名称和父表。所有其他属性都会被替换。
可以将触发器指定为在尝试对行进行操作之前触发(在检查约束以及尝试 INSERT、UPDATE 或 DELETE 之前);或者在操作完成之后触发(在检查约束以及 INSERT、UPDATE 或 DELETE 完成之后);或者代替该操作(对于视图上的插入、更新或删除的情形)。如果触发器在事件之前或代替事件触发,则触发器可以为当前行跳过该操作,或者更改正在被插入的行(仅适用于 INSERT 和 UPDATE 操作)。如果触发器在事件之后触发,则所有更改(包括其他触发器的影响)对该触发器都是“可见”的。
被标记为 FOR EACH ROW 的触发器会为该操作修改的每一行调用一次。例如,一个影响 10 行的 DELETE 会使目标关系上任何 ON DELETE 触发器被调用 10 次,每删除一行调用一次。相比之下,被标记为 FOR EACH STATEMENT 的触发器对于任何给定的操作只执行一次,无论它修改了多少行(特别地,一个修改零行的操作仍会导致所有适用的 FOR EACH STATEMENT 触发器被执行)。
被指定为以 INSTEAD OF 方式代替触发器事件而触发的触发器必须被标记为 FOR EACH ROW,并且只能定义在视图上。视图上的 BEFORE 和 AFTER 触发器必须被标记为 FOR EACH STATEMENT。
此外,也可以定义针对 TRUNCATE 触发的触发器,但只能是 FOR EACH STATEMENT。
下表总结了哪些类型的触发器可以用于表、视图和外部表:
| When | Event | Row-level | Statement-level |
|---|---|---|---|
BEFORE | INSERT/UPDATE/DELETE | 表和外部表 | 表、视图和外部表 |
TRUNCATE | — | 表和外部表 | |
AFTER | INSERT/UPDATE/DELETE | 表和外部表 | 表、视图和外部表 |
TRUNCATE | — | 表和外部表 | |
INSTEAD OF | INSERT/UPDATE/DELETE | Views | — |
TRUNCATE | — | — |
此外,触发器定义还可以指定一个布尔型 WHEN 条件,系统会测试该条件以确定是否应当触发触发器。在行级触发器中,WHEN 条件可以检查该行各列的旧值和/或新值。语句级触发器也可以有 WHEN 条件,但该特性对它们用处不大,因为条件无法引用表中的任何值。
如果为同一事件定义了多个同类触发器,它们将按名称的字母顺序被触发。
当指定了 CONSTRAINT 选项时,该命令会创建一个约束触发器。 它与普通触发器相同,不同之处在于触发器触发的时机可以使用 SET CONSTRAINTS 来调整。约束触发器只能是普通表(而非外部表)上的 AFTER ROW 触发器。它们既可以在引发触发事件的那条语句结束时触发,也可以在包含它的事务结束时触发;在后一种情况下,它们被称为延迟的。通过使用 SET CONSTRAINTS,也可以强制让一个待处理的延迟触发器触发立即发生。当约束触发器所实现的约束被违反时,它应当抛出一个异常。
REFERENCING 选项支持收集过渡关系,即包含当前 SQL 语句插入、删除或修改的所有行的行集合。该特性让触发器能够看到该语句所做操作的全局视图,而不是一次只看到一行。此选项仅允许用于普通表(而非外部表)上的 AFTER 触发器。该触发器不应当是约束触发器。此外,如果该触发器是 UPDATE 触发器,那么在使用此选项时它不能指定 column_name 列表。OLD TABLE 只能被指定一次,并且只能用于可以在 UPDATE 或 DELETE 时触发的触发器;它会创建一个过渡关系,其中包含该语句更新或删除的所有行的前像。类似地,NEW TABLE 只能被指定一次,并且只能用于可以在 UPDATE 或 INSERT 时触发的触发器;它会创建一个过渡关系,其中包含该语句更新或插入的所有行的后像。
SELECT 不会修改任何行,因此无法创建 SELECT 触发器。规则和视图可能为那些看似需要 SELECT 触发器的问题提供可行的解决方案。
有关触发器的更多信息,请参阅 Chapter 37。
name 新触发器的名称。它必须与同一表上任何其他触发器的名称不同。该名称不能带模式限定 — 触发器会继承其表的模式。对于约束触发器,这也是使用 SET CONSTRAINTS 修改触发器行为时所用的名称。
BEFOREAFTERINSTEAD OF 决定该函数是在事件之前、之后还是代替事件被调用。约束触发器只能被指定为 AFTER。
event INSERT、UPDATE、DELETE 或 TRUNCATE 之一;它指定将触发该触发器的事件。可以使用 OR 指定多个事件,但在请求过渡关系时除外。
对于 UPDATE 事件,可以使用以下语法指定一个列列表:
UPDATE OFcolumn_name1[,column_name2... ]
只有当所列出的列中至少有一列被用作 UPDATE 命令的目标,或者所列出的列中有一列是依赖于 UPDATE 目标列的生成列时,该触发器才会触发。
INSTEAD OF UPDATE 事件不允许列列表。在请求过渡关系时同样不能指定列列表。
table_name该触发器所属的表、视图或外部表的名称(可以选择带模式限定)。
referenced_table_name被该约束引用的另一个表的名称(可能带模式限定)。此选项用于外键约束,不建议用于一般用途。只能为约束触发器指定此选项。
DEFERRABLENOT DEFERRABLEINITIALLY IMMEDIATEINITIALLY DEFERRED该触发器的默认触发时机。有关这些约束选项的详细信息,请参阅 CREATE TABLE 文档。只能为约束触发器指定此选项。
REFERENCING该关键字位于一个或两个关系名的声明之前,这些关系名提供对触发语句的过渡关系的访问。
OLD TABLENEW TABLE该子句指明其后的关系名是用于前像过渡关系还是后像过渡关系。
transition_relation_name在触发器内部用于此过渡关系的(不带限定的)名称。
FOR EACH ROWFOR EACH STATEMENT 这指定触发器函数是为受触发器事件影响的每一行触发一次,还是每条 SQL 语句只触发一次。如果两者都未指定,默认为 FOR EACH STATEMENT。约束触发器只能被指定为 FOR EACH ROW。
condition 一个布尔表达式,用于决定触发器函数是否会被真正执行。如果指定了 WHEN,则只有当 condition 返回 true 时才会调用该函数。在 FOR EACH ROW 触发器中,WHEN 条件可以分别通过书写 OLD. 或 column_nameNEW. 来引用旧行值和/或新行值的列。当然,column_nameINSERT 触发器不能引用 OLD,而 DELETE 触发器不能引用 NEW。
INSTEAD OF 触发器不支持 WHEN 条件。
目前,WHEN 表达式不能包含子查询。
请注意,对于约束触发器,WHEN 条件的求值不会被延迟,而是在行更新操作执行之后立即进行。如果该条件的求值结果不为真,则该触发器不会被加入延迟执行队列。
function_name 一个用户提供的函数,它被声明为不带参数且返回类型为 trigger,当触发器触发时会执行该函数。
在 CREATE TRIGGER 的语法中,关键字 FUNCTION 和 PROCEDURE 是等价的,但所引用的函数无论如何都必须是一个函数,而不是一个过程。这里使用关键字 PROCEDURE 是历史遗留,已被弃用。
arguments一个可选的、以逗号分隔的参数列表,在触发器执行时会提供给该函数。这些参数是字面字符串常量。这里也可以写简单名称和数字常量,但它们都会被转换为字符串。请查阅触发器函数实现语言的说明,以了解在函数内部如何访问这些参数;其访问方式可能与普通函数参数不同。
要在一个表上创建或替换触发器,用户必须在该表上拥有 TRIGGER 特权。用户还必须在该触发器函数上拥有 EXECUTE 特权。
使用 DROP TRIGGER 移除触发器。
在分区表上创建行级触发器会在其每个现有分区上创建一个相同的“克隆”触发器;以后创建或附加的任何分区也会有一个相同的触发器。如果子分区上已经存在一个名称冲突的触发器,则会发生错误,除非使用 CREATE OR REPLACE TRIGGER,在这种情况下该触发器会被克隆触发器替换。当一个分区从其父表分离时,其克隆触发器会被移除。
特定列触发器(使用 UPDATE OF 语法定义的触发器)会在其任何列被列为 column_nameUPDATE 命令的 SET 列表中的目标时触发。即使触发器没有触发,列的值也可能发生变化,因为 BEFORE UPDATE 触发器对行内容所做的更改不会被考虑在内。反之,诸如 UPDATE ... SET x = x ... 这样的命令会在列 x 上触发触发器,即使该列的值没有改变。
在 BEFORE 触发器中,WHEN 条件恰好在函数将要执行(或本应执行)之前求值,因此使用 WHEN 与在触发器函数开头测试相同的条件没有实质区别。特别要注意,该条件看到的 NEW 行是当前值,可能已被先前的触发器修改过。此外,BEFORE 触发器的 WHEN 条件不允许检查 NEW 行的系统列(例如 ctid),因为这些列此时还未被设置。
在 AFTER 触发器中,WHEN 条件恰好在行更新发生之后求值,它决定是否将一个事件加入队列以便在语句结束时触发该触发器。因此,当 AFTER 触发器的 WHEN 条件不返回真时,就不需要将事件加入队列,也不需要在语句结束时重新获取该行。如果该触发器只需要为少数几行触发,这在修改许多行的语句中可以带来显著的加速。
在某些情况下,一条 SQL 命令有可能触发不止一种类型的触发器。例如,一个 INSERT 带有 ON CONFLICT DO UPDATE 子句,可能会同时导致插入和更新操作,因此它会根据需要触发这两种类型的触发器。提供给触发器的过渡关系是特定于其事件类型的;因此 INSERT 触发器只会看到被插入的行,而 UPDATE 触发器只会看到被更新的行。
由外键强制操作引起的行更新或删除(例如 ON UPDATE CASCADE 或 ON DELETE SET NULL)被视为引发它们的 SQL 命令的一部分(注意这类操作从不被延迟)。受影响表上的相关触发器将会被触发,因此这提供了 SQL 命令可能触发与其类型不直接匹配的触发器的另一种方式。在简单的情况下,请求过渡关系的触发器会把单个原始 SQL 命令在其表上造成的所有更改视为一个单一的过渡关系。然而,在某些情况下,请求过渡关系的 AFTER ROW 触发器的存在会导致由单个 SQL 命令引发的外键强制操作被拆分为多个步骤,每个步骤都有自己的过渡关系。在这种情况下,任何存在的语句级触发器都会在每次创建过渡关系集合时被触发一次,从而确保触发器在过渡关系中看到每个受影响的行恰好一次。
只有当对视图的操作由行级 INSTEAD OF 触发器处理时,视图上的语句级触发器才会被触发。如果该操作由 INSTEAD 规则处理,则该规则发出的任何语句都会取代命名该视图的原始语句被执行,因此将被触发的是替换语句中提到的表上的触发器。类似地,如果该视图是自动可更新的,则该操作通过自动将语句重写为对视图基表的操作来处理,因此被触发的是基表上的语句级触发器。
修改一个分区表或带有继承子表的表时,会触发显式命名的表上附加的语句级触发器,但不会触发其分区或子表的语句级触发器。相反,行级触发器会在受影响分区或子表中的行上触发,即使它们没有在查询中被显式命名。如果一个语句级触发器在定义时通过 REFERENCING 子句命名了过渡关系,那么所有受影响分区或子表的行前像和后像都是可见的。对于继承子表的情况,行镜像仅包括触发器所附加到的表中存在的那些列。
目前,带过渡关系的行级触发器不能定义在分区表或继承子表上。此外,分区表上的触发器不能是 INSTEAD OF 触发器。
目前,约束触发器不支持 OR REPLACE 选项。
不建议在一个已经对触发器所在表执行过更新操作的事务中替换已有的触发器。已经做出的触发器触发决策或决策的部分内容不会被重新考虑,因此其效果可能令人意外。
有一些内置的触发器函数可以用来解决常见问题,而无需编写自己的触发器代码;请参阅 Section 9.29。
执行函数 check_account_update,每当表 accounts 中的一行即将被更新时:
CREATE TRIGGER check_update
BEFORE UPDATE ON accounts
FOR EACH ROW
EXECUTE FUNCTION check_account_update();
修改该触发器定义,使得只有当列 balance 被指定为 UPDATE 命令中的目标时才执行该函数:
CREATE OR REPLACE TRIGGER check_update
BEFORE UPDATE OF balance ON accounts
FOR EACH ROW
EXECUTE FUNCTION check_account_update();
该形式只有在列 balance 的值确实发生了变化时才执行该函数:
CREATE TRIGGER check_update
BEFORE UPDATE ON accounts
FOR EACH ROW
WHEN (OLD.balance IS DISTINCT FROM NEW.balance)
EXECUTE FUNCTION check_account_update();
调用一个函数来记录 accounts 的更新,但仅在某些内容发生变化时才记录:
CREATE TRIGGER log_update
AFTER UPDATE ON accounts
FOR EACH ROW
WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE FUNCTION log_account_update();
为每一行执行函数 view_insert_row,以便把行插入到视图底层的表中:
CREATE TRIGGER view_insert
INSTEAD OF INSERT ON my_view
FOR EACH ROW
EXECUTE FUNCTION view_insert_row();
为每条语句执行函数 check_transfer_balances_to_zero,以确认 transfer 各行相互抵消后的净值为零:
CREATE TRIGGER transfer_insert
AFTER INSERT ON transfer
REFERENCING NEW TABLE AS inserted
FOR EACH STATEMENT
EXECUTE FUNCTION check_transfer_balances_to_zero();
为每一行执行函数 check_matching_pairs,以确认对匹配对的更改是同时(由同一条语句)进行的:
CREATE TRIGGER paired_items_update
AFTER UPDATE ON paired_items
REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab
FOR EACH ROW
EXECUTE FUNCTION check_matching_pairs();
Section 37.4 包含一个用 C 语言编写的触发器函数的完整示例。
PostgreSQL 中的 CREATE TRIGGER 语句实现了 SQL 标准的一个子集。当前缺少以下功能:
虽然 AFTER 触发器的过渡表名可以用 REFERENCING 子句按标准方式指定,但 FOR EACH ROW 触发器中使用的行变量不能在 REFERENCING 子句中指定。它们以取决于触发函数所用语言的方式提供,但对任何一种语言来说是固定的。某些语言实际上表现得就像存在一个包含 OLD ROW AS OLD NEW ROW AS NEW 的 REFERENCING 子句一样。
标准允许过渡表与特定列的 UPDATE 触发器一起使用,但此时过渡表中应当可见的行集合取决于该触发器的列列表。PostgreSQL 目前尚未实现这一点。
PostgreSQL 只允许执行用户定义的函数作为被触发动作。标准允许执行许多其他 SQL 命令(例如 CREATE TABLE)作为被触发动作。通过创建一个执行所需命令的用户定义函数,不难绕过这一限制。
SQL 规定多个触发器应按创建时间的顺序触发。PostgreSQL 采用名称顺序,这被认为是更方便的做法。
SQL 规定,级联删除上的 BEFORE DELETE 触发器要在之后、也就是级联 DELETE 完成时才触发。PostgreSQL 的行为是 BEFORE DELETE 总是在删除操作(即使是级联的删除操作)之前触发。这被认为更加一致。此外,在由引用操作引发的更新期间,如果 BEFORE 触发器修改行或阻止更新,也会出现非标准行为。这可能导致违反约束,或者存储的数据不遵守引用约束。
使用 OR 为单个触发器指定多个操作的能力是对 SQL 标准的 PostgreSQL 扩展。
为 TRUNCATE 触发触发器的能力是对 SQL 标准的 PostgreSQL 扩展,在视图上定义语句级触发器的能力也是如此。
CREATE CONSTRAINT TRIGGER 是 PostgreSQL 对 SQL 标准的一个扩展。OR REPLACE 选项也是如此。