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) 其中event可以是以下之一: 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。
下表汇总了哪些类型的触发器可用于表、视图和外部表:
| 时机 | 事件 | 行级 | 语句级 |
|---|---|---|---|
BEFORE |
INSERT/UPDATE/DELETE |
表和外部表 | 表、视图和外部表 |
TRUNCATE |
— | 表和外部表 | |
AFTER |
INSERT/UPDATE/DELETE |
表和外部表 | 表、视图和外部表 |
TRUNCATE |
— | 表和外部表 | |
INSTEAD OF |
INSERT/UPDATE/DELETE |
视图 | — |
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 命令可能会引发不止一种触发器。例如,带有 ON CONFLICT DO UPDATE 子句的 INSERT 可能同时导致插入和更新操作,因此会按需引发这两类触发器。 提供给触发器的过渡关系是按事件类型区分的;因此 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.28。
每当表 accounts 中的一行即将被更新时,执行函数 check_account_update:
CREATE TRIGGER check_update
BEFORE UPDATE ON accounts
FOR EACH ROW
EXECUTE FUNCTION check_account_update();
修改该触发器定义,使其仅在 UPDATE 命令中将列 balance 指定为目标时才执行该函数:
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 为单个触发器指定多个动作的能力,是 PostgreSQL 对 SQL 标准的扩展。
能够在 TRUNCATE 上引发触发器,是 PostgreSQL 对 SQL 标准的扩展; 能够在视图上定义语句级触发器也是如此。
CREATE CONSTRAINT TRIGGER 是 PostgreSQL 对 SQL 标准的扩展。 OR REPLACE 选项也是如此。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。