CREATE TRIGGER
CREATE TRIGGER — 定义一个新触发器
大纲
CREATE [ CONSTRAINT ] TRIGGERname{ BEFORE | AFTER | INSTEAD OF } {event[ OR ... ] } ONtable_name[ FROMreferenced_table_name] [ NOT DEFERRABLE | [ DEFERRABLE ] { INITIALLY IMMEDIATE | INITIALLY DEFERRED } ] [ FOR [ EACH ] { ROW | STATEMENT } ] [ WHEN (condition) ] EXECUTE PROCEDUREfunction_name(arguments) 其中event为以下之一: INSERT UPDATE [ OFcolumn_name[, ... ] ] DELETE TRUNCATE
描述
CREATE TRIGGER创建一个新触发器。触发器会关联到指定的表、视图或外部表,并在发生某些事件时执行指定的函数function_name。
可以指定触发器在尝试对某一行执行该操作之前引发(即在检查约束以及尝试执行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强制尚待执行的延迟触发器立即触发。约束触发器应在其实现的约束被违反时引发异常。
SELECT 不会修改任何行,因此无法创建
SELECT 触发器。对于这类情形,规则和视图更为合适。
参见第 36 章以了解有关触发器的更多信息。
参数
name新触发器的名称。它必须不同于同一表上的任何其他触发器名称。该名称不能是模式限定名 — 触发器会继承其所属表的模式。对于约束触发器,这也是使用
SET CONSTRAINTS修改触发器行为时所使用的名称。BEFOREAFTERINSTEAD OF决定该函数是在事件之前、之后调用,还是取代该事件执行。约束触发器只能指定为
AFTER。eventINSERT、UPDATE、DELETE或TRUNCATE之一;它指定了哪个事件将引发该触发器。可以使用OR指定多个事件。对于
UPDATE事件,可以使用以下语法指定列列表:UPDATE OF
column_name1[,column_name2... ]只有当列出的列中至少有一列被列为
UPDATE命令的目标时,触发器才会触发。INSTEAD OF UPDATE事件不支持列列表。table_name该触发器所作用的表、视图或外部表的名称(可以是模式限定的)。
referenced_table_name约束所引用的另一张表的名称(可以是模式限定的)。此选项用于外键约束,不建议用于一般用途。它只能为约束触发器指定。
DEFERRABLENOT DEFERRABLEINITIALLY IMMEDIATEINITIALLY DEFERRED触发器的默认时机。有关这些约束选项的细节,请参见CREATE TABLE。它们只能为约束触发器指定。
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类型;当触发器被触发时,就会执行该函数。arguments一个可选的、以逗号分隔的参数列表,在执行触发器时会传给该函数。这些参数是字符串字面常量。这里也可以写简单名称和数字常量,但它们都会被转换成字符串。请查阅该触发器函数所用实现语言的说明,以了解如何在函数内部访问这些参数;这可能与普通函数参数不同。
注解
要在表上创建触发器,用户必须拥有该表的TRIGGER权限。用户还必须拥有触发器函数的EXECUTE权限。
使用DROP TRIGGER删除触发器。
列限定触发器(即使用 UPDATE OF
语法定义的触发器)会在其任一列被列为
column_nameUPDATE 命令的 SET 列表目标时引发。即便触发器没有引发,列值仍有可能发生变化,因为
BEFORE UPDATE 触发器对行内容所作的更改不会被考虑在内。反过来,诸如 UPDATE ... SET x = x ... 这样的命令会引发列
x 上的触发器,即使该列的值实际上并未变化。
在BEFORE触发器中,WHEN条件会在函数执行前(或者本应执行前)立即求值,因此使用WHEN与在触发器函数开头测试相同条件并无实质区别。特别要注意的是,该条件看到的NEW行是当前值,它可能已经被更早触发的触发器修改过。此外,BEFORE触发器的WHEN条件不允许检查NEW行的系统列(例如oid),因为这些列此时尚未被设置。
在 AFTER 触发器中,WHEN 条件会在行变更发生后立即求值,并决定是否将一个事件排入队列,以便在语句结束时引发该触发器。因此,当
AFTER 触发器的 WHEN 条件不返回真时,就没有必要将事件排队,也不需要在语句结束时重新获取该行。如果触发器只需针对少数几行引发,这能显著提升那些会修改很多行的语句的速度。
只有当视图上的动作由行级 INSTEAD OF 触发器处理时,视图上的语句级触发器才会引发。如果该动作由 INSTEAD 规则处理,那么规则发出的那些语句会代替原先引用该视图的语句执行,因此最终引发的是替代语句所引用表上的触发器。同样,如果该视图是自动可更新的,那么该动作会通过把语句自动重写为作用于视图基表的动作来处理,因而最终引发的是基表上的语句级触发器。
在PostgreSQL 7.3 之前的版本中,必须将触发器函数声明为返回占位类型opaque,而不是trigger。为了支持加载旧转储文件,CREATE TRIGGER将接受声明为返回opaque的函数,但会发出通知,并将该函数声明的返回类型更改为trigger。
示例
执行该函数check_account_update,每当表accounts中的一行即将被更新时调用它:
CREATE TRIGGER check_update
BEFORE UPDATE ON accounts
FOR EACH ROW
EXECUTE PROCEDURE check_account_update();相同,但仅当列balance被指定为UPDATE命令的目标时才执行该函数:
CREATE TRIGGER check_update
BEFORE UPDATE OF balance ON accounts
FOR EACH ROW
EXECUTE PROCEDURE check_account_update();这种形式仅在列balance的值确实发生变化时才执行该函数:
CREATE TRIGGER check_update
BEFORE UPDATE ON accounts
FOR EACH ROW
WHEN (OLD.balance IS DISTINCT FROM NEW.balance)
EXECUTE PROCEDURE check_account_update();
调用一个函数记录 accounts 的更新,但仅在确有内容发生变化时才调用:
CREATE TRIGGER log_update
AFTER UPDATE ON accounts
FOR EACH ROW
WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE PROCEDURE log_account_update();执行该函数view_insert_row,针对每一行将行插入视图底层的表中:
CREATE TRIGGER view_insert
INSTEAD OF INSERT ON my_view
FOR EACH ROW
EXECUTE PROCEDURE view_insert_row();
第 36.4 节包含一个用 C 编写的触发器函数的完整示例。
兼容性
PostgreSQL 中的 CREATE TRIGGER
语句实现了 SQL 标准的一个子集。当前仍缺少以下功能:
SQL 允许为在触发动作定义中使用的“旧”行或“新”行定义别名(例如
CREATE TRIGGER ... ON tablename REFERENCING OLD ROW AS somename NEW ROW AS othername ...)。由于 PostgreSQL 允许用任意多种用户定义的语言编写触发器过程,对数据的访问是以语言相关的方式处理的。PostgreSQL 不允许在语句级触发器中引用旧表和新表,即包含所有旧行和/或新行的表,它们在 SQL 标准中由
OLD TABLE和NEW TABLE子句引用。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标准的扩展。