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 章。
name
为新触发器指定的名称。该名称必须与同一表上的任何其他触发器的名称不同。该名称不能包含模式限定符 — 触发器继承其表的模式。对于约束触发器,这也是使用 SET CONSTRAINTS
修改触发器行为时要使用的名称。
BEFORE
AFTER
INSTEAD OF
确定是在事件之前、之后还是代替事件调用函数。约束触发器只能指定为 AFTER
。
event
INSERT
、UPDATE
、DELETE
或 TRUNCATE
中的一个;这指定了触发触发器的事件。可以使用 OR
指定多个事件,除非请求了转换关系。
对于 UPDATE
事件,可以使用此语法指定列列表
UPDATE OFcolumn_name1
[,column_name2
... ]
仅当列中至少有一列被指定为 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 OF
语法定义的触发器)将在任何列被列为 column_name
UPDATE
命令的 SET
列表中的目标时触发。由于 BEFORE UPDATE
触发器对行内容所做的更改不被考虑,因此即使在触发器未被触发的情况下,列的值也可能发生更改。反之,像 UPDATE ... SET x = x ...
这样的命令将触发 x
列上的触发器,即使该列的值没有改变。
在 BEFORE
触发器中,WHEN
条件在函数将要或已经执行之前进行评估,因此使用 WHEN
与在触发器函数开头测试相同的条件在实质上没有区别。特别要注意的是,条件看到的 NEW
行是当前值,可能已被之前的触发器修改。此外,BEFORE
触发器的 WHEN
条件不允许检查 NEW
行的系统列(例如 ctid
),因为这些列尚未设置。
在 AFTER
触发器中,WHEN
条件在行更新发生后立即进行评估,并确定是否将一个事件排队以在语句结束时触发触发器。因此,当 AFTER
触发器的 WHEN
条件不返回 true 时,无需排队事件,也无需在语句结束时重新获取行。如果触发器只需要为少数几行触发,这可以显著加快修改许多行的语句的速度。
在某些情况下,单个 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
选项不支持约束触发器。
不建议在事务中替换现有触发器,该事务已对触发器表执行了更新操作。已经做出的触发器触发决策或触发决策的一部分将不会被重新考虑,因此结果可能会令人惊讶。
有一些内置触发器函数可以用于解决常见问题,而无需编写自己的触发器代码;请参阅 第 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
子句中指定。它们以一种依赖于触发器函数所用语言的方式提供,但对于任何一种语言都是固定的。某些语言实际上表现得好像存在一个包含 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
选项也是。
如果您在文档中看到任何不正确、不符合您对特定功能的体验或需要进一步澄清的内容,请使用 此表单 报告文档问题。