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
) whereevent
can 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
。
下表总结了哪种类型的触发器可用于表、视图和外部表
当 | 事件 | 行级 | 语句级 |
---|---|---|---|
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
触发器的难题提供可行的解决方案。
有关触发器的详细信息,请参见 第 37 章。
名称
要赋予新触发器的名称。此名称必须区别于同一表上的任何其他触发器的名称。名称不能限定架构——触发器将继承其表的架构。对于约束触发器,它也是修改使用 SET CONSTRAINTS
修改触发器行为时要使用的名称。
BEFORE
AFTER
INSTEAD OF
确定函数是在事件之前、之后调用还是替代事件调用。约束触发器只能指定为 AFTER
。
事件
如果 INSERT
、UPDATE
、DELETE
或 TRUNCATE
之一,指定将触发触发器的事件。可以使用 OR
指定多个事件,除非请求转换关系。
对于 UPDATE
事件,可以使用此语法指定列列表
UPDATE OFcolumn_name1
[,column_name2
... ]
如果列出的列中至少有一列被提及为 UPDATE
命令的目标,或者列出的列之一是生成的列,而该列取决于 UPDATE
目标的列,则触发器将只触发。
INSTEAD OF UPDATE
事件不允许列列表。请求转换关系时也不能指定列列表。
table_name
触发器适用的表、视图或外部表的名称(可选架构限定)。
referenced_table_name
约束引用的另一个表(可能限定架构)的名称。此选项用于外键约束,不建议一般使用。此选项仅对约束触发器有效。
DEFERRABLE
NOT DEFERRABLE
INITIALLY IMMEDIATE
INITIALLY DEFERRED
触发器的默认时机。有关这些约束选项的详细信息,请参阅 CREATE TABLE 文档。此选项仅对约束触发器有效。
REFERENCING
此关键字紧邻一个或两个关系名称的声明,这些关系名称提供对触发语句的转换关系的访问。
OLD TABLE
NEW TABLE
此子句指示以下关系名称是针对映像转换关系还是后映像转换关系的。
transition_relation_name
此转换关系触发器中要使用的(不合格的)名称。
FOR EACH ROW
FOR EACH STATEMENT
这指定触发器函数是否应针对受触发器事件影响的每一行触发一次,或者是否只针对每条 SQL 语句触发一次。如果未指定任何一项,则 FOR EACH STATEMENT
为默认值。约束触发器只能指定 FOR EACH ROW
。
condition
决定是否实际执行触发器函数的布尔表达式。如果指定 WHEN
,仅当 condition
返回 true
时,才会调用函数。在 FOR EACH ROW
触发器中,WHEN
条件可以通过分别编写 OLD.
或 column_name
NEW.
来引用旧行和/或新行值列。当然,column_name
INSERT
触发器不能引用 OLD
,DELETE
触发器不能引用 NEW
。
INSTEAD OF
触发器不支持 WHEN
条件。
当前,WHEN
表达式不能包含子查询。
请注意,对于约束触发器,WHEN
条件的评估不会延迟,而是在执行行更新操作后立即发生。如果条件未评估为 true,则触发器不会排队以进行延迟执行。
function_name
用户提供的未声明为采用任何参数并返回类型为 trigger
的函数,在触发器触发时执行此函数。
在 CREATE TRIGGER
的语法中,FUNCTION
和 PROCEDURE
关键字是等效的,但引用的函数在任何情况下都必须是函数,而不是过程。此处使用 PROCEDURE
关键字是从前留下的,且不推荐使用。
arguments
当执行触发器时提供给函数的参数的可选逗号分隔列表。参数是字符串常量。在此处还可以编写简单名称和数字常量,但所有这些都会转换为字符串。请查看触发器函数的实现语言的描述,以了解如何在函数中访问这些参数;它可能不同于普通函数参数。
要创建或替换表上的触发器,用户必须对表具有 TRIGGER
权限。用户还必须对触发器函数具有 EXECUTE
权限。
使用 DROP TRIGGER
删除触发器。
在分区表上创建行级别触发器将导致每在其现有分区上创建相同的“克隆”触发器;并且稍后创建或附加的任意分区也将具有相同的触发器。如果一个子分区上已有冲突命名的触发器,那么将发生错误,除非使用CREATE OR REPLACE TRIGGER
,在这种情况下将用一个克隆触发器替换那个触发器。当一个分区从其父分区分离后,将删除其克隆触发器。
当一个列的任意部分在UPDATE
命令的SET
列表中作为目标列时,会触发一个特定于列的触发器(一个使用UPDATE OF
语法定义的触发器)。即使没有触发触发器,列的值仍然可能改变,原因是column_name
BEFORE UPDATE
触发器对行内容所做的更改不被考虑。相反,诸如UPDATE ... SET x = x ...
之类的命令将触发列x
上的一个触发器,即使该列的值没有改变。
在BEFORE
触发器中,WHEN
条件会在函数被执行或将被执行之前立即被评估,因此使用WHEN
从本质上来说与在触发器函数开始时测试相同条件没有什么不同。特别注意条件所看到的NEW
行是当前值,而且可能被更早的触发器所修改。另外,BEFORE
触发器的WHEN
条件不被允许检查NEW
行的系统列(例如ctid
),因为这些列尚未被设置。
在AFTER
触发器中,WHEN
条件会在行更新发生之后立即被评估,并且它会确定在语句结束时是否对一个事件排队来触发触发器。因此,当AFTER
触发器的WHEN
条件不返回 true 时,就不必对事件排队或在语句结束时重新获取行。如果触发器只需要为几行触发,那么这可以在修改多行的语句中产生明显的加速。
在某些情况下,一个 SQL 命令可以激发多种类型的触发器。例如,带有 INSERT
和 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
选项。
不建议在已对触发器表执行更新操作的事务中替换现有的触发器。已做出的触发器触发决策或部分触发器触发决策将不被重新考虑,因此其影响可能会出乎意料。
有几个内置触发器功能可用于解决常见问题,而无需编写您自己的触发器代码;请参见 第 9.29 节。
每当 accounts
表的行即将被更新时,执行函数 check_account_update
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();
第 37.4 节 包含用 C 编写的触发器函数的完整示例。
PostgreSQL 中的 CREATE TRIGGER
语句实现了SQL标准的子集。目前缺少以下功能
尽管 AFTER
触发器的转换表名称是通过标准方式使用 REFERENCING
子句指定的,但在 FOR EACH ROW
中使用的行变量不能在 REFERENCING
子句中指定。它们的可用的方式取决于触发器函数所写的语言,但对任何一种语言而言都是固定的。一些语言有效地表现得好像有一个 REFERENCING
子句,其中包含 OLD ROW AS OLD NEW ROW AS NEW
。
标准允许将转换表与特定于列的 UPDATE
触发器一起使用,但转换表中应显示的那组行取决于该触发器的列列表。当前,PostgreSQL 没有实现这一功能。
PostgreSQL 只允许对触发操作执行用户定义的函数。而标准允许将多个其他 SQL 命令执行作为触发操作,例如 CREATE TABLE
。通过创建一个执行所需命令的用户定义函数,可以轻松解决此限制。
SQL 规定应按创建时间顺序触发 multiple triggers。 PostgreSQL 使用更方便的名称顺序。
SQL 规定,级联删除上的 BEFORE DELETE
触发器应在级联 DELETE
完成时 之后 触发PostgreSQL 的行为是 BEFORE DELETE
始终在删除操作之前触发,甚至在级联删除时也是如此。这被认为更一致。当 BEFORE
触发器修改行或因外键操作导致的更新中禁止更新时,也会出现非标准行为。这可能导致约束冲突或不遵守外键约束的存储数据。
使用 OR
为单个触发器指定多项操作的能力是 PostgreSQL 对 SQL 标准的扩展。
为 TRUNCATE
触发触发器的能力是 PostgreSQL 对 SQL 标准的扩展,同样地,为视图定义语句级触发器的能力也是对标准的扩展。
CREATE CONSTRAINT TRIGGER
是 PostgreSQL 对SQL标准的扩展。 OR REPLACE
选项也是如此。