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