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
命令的目标,或者列出的列之一是依赖于 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
条件的求值不是延迟的,而是在执行行更新操作后立即进行。如果条件求值结果为 false,则不会将触发器排队以进行延迟执行。
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();
修改该触发器定义,使其仅在 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();
第 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
选项也是如此。
如果您发现文档中有任何不正确、与您使用特定功能的经验不符或需要进一步澄清的地方,请使用此表单报告文档问题。