触发器是一种规范,它指定当执行某种类型的操作时,数据库应自动执行特定的函数。触发器可以附加到表(分区或未分区)、视图和外部表。
在表和外部表上,可以将触发器定义为在任何 INSERT
、UPDATE
或 DELETE
操作之前或之后执行,可以是每个修改的行执行一次,也可以每个SQL语句执行一次。UPDATE
触发器还可以设置为仅当 UPDATE
语句的 SET
子句中提到某些列时才触发。触发器也可以为 TRUNCATE
语句触发。如果发生触发器事件,则会在适当的时间调用触发器的函数来处理该事件。
在视图上,可以将触发器定义为代替 INSERT
、UPDATE
或 DELETE
操作执行。此类 INSTEAD OF
触发器会为视图中需要修改的每一行触发一次。触发器函数的责任是执行对视图的底层基表的必要修改,并在适当情况下返回修改后的行,就像它将出现在视图中一样。视图上的触发器也可以定义为在 INSERT
、UPDATE
或 DELETE
操作之前或之后,每个SQL语句执行一次。但是,只有当视图上也有 INSTEAD OF
触发器时,才会触发此类触发器。否则,任何针对视图的语句都必须重写为影响其底层基表的语句,然后将触发的触发器是附加到基表上的触发器。
必须先定义触发器函数,然后才能创建触发器本身。触发器函数必须声明为不带任何参数并返回 trigger
类型的函数。(触发器函数通过特殊传递的 TriggerData
结构接收其输入,而不是以普通函数参数的形式接收。)
创建合适的触发器函数后,使用 CREATE TRIGGER 建立触发器。同一个触发器函数可以用于多个触发器。
PostgreSQL 提供每行触发器和每语句触发器。对于每行触发器,对于受触发该触发器的语句影响的每一行,都会调用一次触发器函数。相反,当执行适当的语句时,无论受该语句影响的行数是多少,都会仅调用一次每语句触发器。特别是,影响零行的语句仍将导致执行任何适用的每语句触发器。这两种类型的触发器有时分别称为行级触发器和语句级触发器。TRUNCATE
上的触发器只能在语句级别定义,而不能按行定义。
触发器还根据它们是在操作之前、之后还是代替操作触发进行分类。它们分别称为 BEFORE
触发器、AFTER
触发器和 INSTEAD OF
触发器。语句级 BEFORE
触发器自然会在语句开始执行任何操作之前触发,而语句级 AFTER
触发器会在语句的末尾触发。这些类型的触发器可以在表、视图或外部表上定义。行级 BEFORE
触发器会在操作特定行之前立即触发,而行级 AFTER
触发器会在语句结束时触发(但在任何语句级 AFTER
触发器之前)。这些类型的触发器只能在表和外部表上定义,而不能在视图上定义。INSTEAD OF
触发器只能在视图上定义,并且只能在行级别定义;当视图中的每一行被标识为需要操作时,它们会立即触发。
如果将 AFTER
触发器定义为约束触发器,则可以将其执行延迟到事务结束而不是语句结束。在所有情况下,触发器都作为触发它的语句的同一事务的一部分执行,因此如果语句或触发器导致错误,则两者都会回滚。
如果 INSERT
包含 ON CONFLICT DO UPDATE
子句,则可以在触发的行上执行行级 BEFORE
INSERT
,然后执行 BEFORE
UPDATE
触发器。如果触发器不是幂等的,则此类交互可能会很复杂,因为 BEFORE
INSERT
触发器所做的更改将被 BEFORE
UPDATE
触发器看到,包括对 EXCLUDED
列的更改。
请注意,当指定 ON CONFLICT DO UPDATE
时,无论 UPDATE
是否影响任何行(以及无论是否使用了备用的 UPDATE
路径),都会执行语句级 UPDATE
触发器。带有 ON CONFLICT DO UPDATE
子句的 INSERT
将首先执行语句级 BEFORE
INSERT
触发器,然后执行语句级 BEFORE
UPDATE
触发器,然后执行语句级 AFTER
UPDATE
触发器,最后执行语句级 AFTER
INSERT
触发器。
针对继承或分区层次结构中的父表的语句不会触发受影响的子表的语句级触发器;仅触发父表的语句级触发器。但是,将触发任何受影响的子表的行级触发器。
如果分区表上的 UPDATE
导致行移动到另一个分区,则会执行为从原始分区的 DELETE
,后跟将 INSERT
到新分区。在这种情况下,会在原始分区上触发所有行级 BEFORE
UPDATE
触发器和所有行级 BEFORE
DELETE
触发器。然后在目标分区上触发所有行级 BEFORE
INSERT
触发器。当所有这些触发器影响正在移动的行时,应考虑出现意外结果的可能性。就 AFTER ROW
触发器而言,将应用 AFTER
DELETE
和 AFTER
INSERT
触发器;但不会应用 AFTER
UPDATE
触发器,因为 UPDATE
已转换为 DELETE
和 INSERT
。就语句级触发器而言,即使发生行移动,也不会触发任何 DELETE
或 INSERT
触发器;仅会触发在 UPDATE
语句中使用的目标表上定义的 UPDATE
触发器。
没有为 MERGE
定义单独的触发器。相反,根据(对于语句级触发器)MERGE
查询中指定的操作以及(对于行级触发器)执行的操作,会触发语句级或行级 UPDATE
、DELETE
和 INSERT
触发器。
在运行 MERGE
命令时,语句级别的 BEFORE
和 AFTER
触发器会为 MERGE
命令操作中指定的事件触发,无论该操作最终是否执行。这与 UPDATE
语句更新零行的情况相同,但语句级别的触发器仍然会被触发。行级别的触发器仅在实际更新、插入或删除行时才会被触发。因此,在某些类型的操作中触发语句级别的触发器,而同一类型的操作不触发行级别的触发器是完全合法的。
由每个语句触发器调用的触发器函数应始终返回 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
触发器之后和 AFTER
触发器之前计算。因此,可以在 AFTER
触发器中检查生成的值。在 BEFORE
触发器中,OLD
行包含旧的生成值,正如人们所预期的那样,但 NEW
行尚未包含新的生成值,不应访问。在 C 语言接口中,此时列的内容未定义;更高级别的编程语言应防止在 BEFORE
触发器中访问 NEW
行中的存储生成列。对 BEFORE
触发器中生成的列的值的更改将被忽略并被覆盖。
如果为同一关系上的同一事件定义了多个触发器,则触发器将按触发器名称的字母顺序触发。在 BEFORE
和 INSTEAD OF
触发器的情况下,每个触发器返回的可能修改的行将成为下一个触发器的输入。如果任何 BEFORE
或 INSTEAD OF
触发器返回 NULL
,则该行的操作将被放弃,并且不会触发后续触发器(对于该行)。
触发器定义还可以指定一个布尔值 WHEN
条件,该条件将被测试以确定是否应触发触发器。在行级触发器中,WHEN
条件可以检查行的列的旧值和/或新值。(语句级触发器也可以具有 WHEN
条件,尽管该功能对它们来说不是那么有用。)在 BEFORE
触发器中,WHEN
条件在函数即将或将要执行之前进行评估,因此使用 WHEN
与在触发器函数的开头测试相同条件没有实质区别。但是,在 AFTER
触发器中,WHEN
条件在行更新发生后立即进行评估,并且它确定是否将事件排队以在语句末尾触发触发器。因此,当 AFTER
触发器的 WHEN
条件不返回 true 时,无需对事件进行排队,也不必在语句末尾重新获取该行。如果触发器只需要为少数行触发,这可以显着加快修改多行的语句的速度。INSTEAD OF
触发器不支持 WHEN
条件。
通常,行级 BEFORE
触发器用于检查或修改将要插入或更新的数据。例如,BEFORE
触发器可用于将当前时间插入 timestamp
列,或检查行的两个元素是否一致。行级 AFTER
触发器最合理地用于将更新传播到其他表,或对其他表进行一致性检查。这种分工的原因是,AFTER
触发器可以确定它正在查看行的最终值,而 BEFORE
触发器则不能;在其之后可能还会触发其他 BEFORE
触发器。如果您没有特定的理由使触发器成为 BEFORE
或 AFTER
,则 BEFORE
情况更有效,因为关于操作的信息不必保存到语句末尾。
如果触发器函数执行 SQL 命令,则这些命令可能会再次触发触发器。这被称为级联触发器。对级联级别的数量没有直接限制。级联可能会导致同一触发器的递归调用;例如,INSERT
触发器可能会执行一个命令,该命令将其他行插入到同一表中,从而导致再次触发 INSERT
触发器。触发器程序员有责任避免在这种情况下发生无限递归。
如果外键约束指定了引用操作(即级联更新或删除),则这些操作将通过引用表上的普通 SQL 更新或删除命令来执行。特别是,引用表上存在的任何触发器都将为这些更改触发。如果此类触发器修改或阻止了其中一个命令的效果,则最终结果可能会破坏引用完整性。触发器程序员有责任避免这种情况。
定义触发器时,可以为其指定参数。在触发器定义中包含参数的目的是允许具有相似要求的不同触发器调用相同的函数。例如,可以有一个通用的触发器函数,该函数将两个列名称作为参数,并将当前用户放入一个列中,将当前时间戳放入另一个列中。如果编写得当,此触发器函数将独立于它所触发的特定表。因此,可以将相同的函数用于任何具有合适列的表上的 INSERT
事件,例如,在事务表中自动跟踪记录的创建。如果定义为 UPDATE
触发器,则它也可以用于跟踪最后更新事件。
每个支持触发器的编程语言都有其自己的方法,使触发器输入数据可用于触发器函数。此输入数据包括触发器事件的类型(例如,INSERT
或 UPDATE
)以及 CREATE TRIGGER
中列出的任何参数。对于行级触发器,输入数据还包括 INSERT
和 UPDATE
触发器的 NEW
行,以及/或 UPDATE
和 DELETE
触发器的 OLD
行。
默认情况下,语句级触发器没有任何方法来检查语句修改的各个行。但是,AFTER STATEMENT
触发器可以请求创建过渡表,以使受影响的行集可用于触发器。AFTER ROW
触发器也可以请求过渡表,以便他们可以看到表中的总更改以及他们当前正在触发的单个行的更改。检查过渡表的方法再次取决于正在使用的编程语言,但是典型的方法是使过渡表充当只读临时表,可以通过触发器函数中发出的 SQL 命令访问这些表。
如果您在文档中发现任何不正确、与您对特定功能的体验不符或需要进一步澄清的内容,请使用此表单来报告文档问题。