pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
本章提供有关编写触发器函数的一般信息。触发器函数可以用大多数可用的过程语言编写,包括 PL/pgSQL(第 39 章)、 PL/Tcl(第 40 章)、 PL/Perl(第 41 章)和 PL/Python(第 42 章)。读完本章后,你还应查阅所用过程语言对应的章节,了解用该语言编写触发器的语言相关细节。
也可以用 C 编写触发器函数,不过大多数人会觉得使用某种过程语言更容易。目前还不能用普通 SQL 函数语言编写触发器函数。
触发器是这样一种规定:每当执行某种特定类型的操作时,数据库就 应自动执行一个特定的函数。触发器可以定义为在任何 INSERT、UPDATE 或 DELETE 操作之前或之后执行,执行方式既可以 是每个被修改的行一次,也可以是每条 SQL 语句一次。此外,UPDATE 触发器还可以设置为 仅当 UPDATE 语句的 SET 子句中提及特定列时才触发。触发器也可以由 TRUNCATE 语句触发。一旦触发器事件发生, 触发器函数就会在恰当的时机被调用以处理该事件。
必须先定义触发器函数,之后才能创建触发器本身。触发器函数必须声明为不接受参数并返回类型 trigger 的函数。(触发器函数通过特殊传入的 TriggerData 结构体接收输入,而不是通过普通函数参数。)
创建出合适的触发器函数后,就可以使用 CREATE TRIGGER 创建触发器。同一个触发器函数可以供多个触发器使用。
PostgreSQL 同时提供 每行 触发器和 每语句 触发器。对于每行触发器,触发它 的语句所影响的每一行都会调用一次触发器函数。相反,每语句 触发器只会在执行相应语句时调用一次,不管该语句影响了多少行。 特别是,即使某个语句一行也没有影响,仍会执行所有适用的每语句 触发器。这两类触发器有时也分别称为 行级 触发器和 语句级 触发器。 TRUNCATE 的触发器只能定义为语句级。
触发器还分为之前触发器和 之后触发器。语句级 BEFORE 触发器自然在 语句开始做任何事之前触发,而语句级 AFTER 触发器在语句的最终 末尾触发。行级 BEFORE 触发器在对某个特定行执行操作之前立即 触发,而行级 AFTER 触发器在语句末尾触发(但早于任何语句级 AFTER 触发器)。
由每语句触发器调用的触发器函数应始终返回 NULL。由每行触发器调用的触发器函数则可以根据 需要向调用它的执行器返回一个表行(即类型 HeapTuple 的值)。在操作之前触发的 行级触发器可以作出如下选择:
它可以返回 NULL,以跳过对当前行的操作。 这会指示执行器不要执行引发该触发器的行级操作(即对某个 具体表行的插入或修改)。
仅对于行级 INSERT 和 UPDATE 触发器,返回的行会成为将要插入 的行,或替换正在更新的行。这使得触发器函数可以修改即将 插入或更新的行。
不打算引发上述任何一种行为的行级 BEFORE 触发器,必须注意把 传入的行原样作为结果返回(也就是说,对于 INSERT 和 UPDATE 触发器 返回 NEW 行,对于 DELETE 触发器返回 OLD 行)。
对于在操作之后触发的行级触发器,其返回值会被忽略,因此它们可以返回 NULL。
如果为同一关系上的同一事件定义了多个触发器,它们会按触发器 名称的字母顺序触发。对于 BEFORE 触发器,每个触发器返回的、 可能已经被修改过的行都会成为下一个触发器的输入。如果任何 BEFORE 触发器返回 NULL,则会放弃对该行执行该操作, 并且后续触发器(针对该行)都不会再触发。
触发器定义还可以指定一个布尔 WHEN 条件, 用来测试是否应当触发该触发器。对于行级触发器, WHEN 条件可以检查该行各列的旧值和/或新值。 (语句级触发器也可以有 WHEN 条件,不过这个 特性对它们用处不大。)在 BEFORE 触发器中, WHEN 条件就在函数即将执行或本会执行之前 求值,因此使用 WHEN 与在触发器函数开头测试 同样的条件并无实质差别。不过,在 AFTER 触发器中, WHEN 条件会在行更改发生后立即求值,并决定 是否要将一个事件排入队列,以便在语句末尾触发该触发器。因此, 当 AFTER 触发器的 WHEN 条件不返回真时,就 不必排队该事件,也不必在语句末尾重新取出该行。如果触发器只 需要针对少数几行触发,这会让修改大量行的语句显著加快。
通常,行级 BEFORE 触发器用于检查或修改将要插入或更新的数据。 例如,BEFORE 触发器可以用来把当前时间写入某个 timestamp 列,或者检查该行的两个元素是否一致。 行级 AFTER 触发器最适合用于把更新传播到其他表,或对其他表 执行一致性检查。之所以这样分工,是因为 AFTER 触发器可以确定 自己看到的是该行的最终值,而 BEFORE 触发器不能;它之后可能 还会有其他 BEFORE 触发器触发。如果没有特定理由把触发器做成 BEFORE 或 AFTER,BEFORE 的情形效率更高,因为有关该操作的 信息不必保存到语句结束。
如果触发器函数执行 SQL 命令,那么这些命令可能会再次触发触发器。这就是所谓的级联触发器。对级联层数没有直接限制。级联还有可能导致同一个触发器被递归调用;例如,一个 INSERT 触发器可能执行一条向同一个表再插入一行的命令,从而导致该 INSERT 触发器再次触发。因此,在这种场景下,避免无限递归是触发器编写者自己的责任。
在定义触发器时,可以为它指定参数。 在触发器定义中包含参数,是为了让需求 相似的不同触发器能够调用同一个函数。举例来说,可以有一个 通用触发器函数,它接受两个列名作为参数,把当前用户写入其中 一个列,把当前时间戳写入另一个列。只要编写得当,这个触发器 函数就应独立于它所作用的具体表。因此,同一个函数可用于任何 具有适当列的表上的 INSERT 事件,例如自动 跟踪某个事务表中记录的创建。如果把它定义成 UPDATE 触发器,还可以用来跟踪最近更新 事件。 trigger.
每种支持触发器的编程语言都有自己的方法,使触发器输入数据可供触发器函数使用。这些输入数据包括触发器事件的类型(例如 INSERT 或 UPDATE),以及 CREATE TRIGGER 中列出的任何参数。对于行级触发器,输入数据还包括 INSERT 和 UPDATE 触发器的 NEW 行,以及/或者 UPDATE 和 DELETE 触发器的 OLD 行。语句级触发器目前无法检查该语句修改的各个行。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。