PostgreSQL 9.4.4 中文手册
上一页 | 上一级 | 章 36. 触发器 | 下一页

# 36.1. 触发器行为概述

一个触发器是一种声明，告诉数据库应该在执行特定的操作的时候执行特定的函数。 触发器可以附加到表、视图和外部表上。

在表或外部表上，触发器可以定义在一个`INSERT`, `UPDATE`, 或 `DELETE`命令之前或者之后执行， 要么是对每行执行一次，要么是对每条SQL语句执行一次。 如果某列在`UPDATE` 语句的`SET`子句中被提及，则`UPDATE`触发器再次被触发。 触发器可以为`TRUNCATE`语句触发。 如果发生触发器事件，那么将在合适的时刻调用触发器函数以处理该事件。 外部表不支持TRUNCATE语句。

在视图上，触发器可以被定义执行而不是`INSERT`, `UPDATE`或者 `DELETE`操作。为了需要在视图中修改的每一行触发`INSTEAD OF`触发器。 这是触发器函数在基表下执行必要修改的责任，并且在适当情况下，返回在视图中出现的修改的行。 在执行每个SQL语句， `INSERT`, `UPDATE`或者`DELETE` 操作之前或之后也可以定义视图上的触发器。

触发器函数必须在创建触发器之前，作为一个没有参数并且返回`trigger`类型的函数定义。 触发器函数通过特殊的`TriggerData`结构接收其输入， 而不是用普通的函数参数方式。

一旦创建了一个合适的触发器函数， 就可以用CREATE TRIGGER创建触发器。 同一个触发器函数可以用于多个触发器。

PostgreSQL提供按行与按语句触发的触发器。 按行触发的触发器函数为触发语句影响的每一行执行一次；相比之下， 按语句触发的触发器函数为每条触发语句执行一次，而不管影响的行数。 特别是，一个影响零行的语句将仍然导致按语句触发的触发器执行。 这两种类型的触发器有时候分别叫做行级触发器和语句级触发器。 在`TRUNCATE`上的触发器可能只能在语句级别定义。 触发之前或之后的视图，触发器只能在语句级别定义， 然而非`INSERT`, `UPDATE`或者 `DELETE`触发的触发器在行级别定义。

触发器通常按照触发的before和after， 或者instead of操作进行分类。 这些分别被称为`BEFORE`触发器，`AFTER`触发器， `INSTEAD OF`触发器。 语句级别的`BEFORE`触发器通常在语句开始做任何事情之前触发， 而语句级别的`AFTER`触发器在语句结束时触发。 触发器的这些类型可以在表或者视图上定义。 行级别的`BEFORE`触发器在对特定行进行操作之前触发， 而行级别的`AFTER`触发器在语句结束的时候触发 (但是在任何语句级别的`AFTER`触发器之前)。 触发器的这些类型可能只在表和外部表上定义。 行级别`INSTEAD OF`触发器可能只在视图上定义， 并且立刻触发作为视图上的每一行被标识为需要的操作。

按语句触发的触发器应该总是返回`NULL`。 如果必要，按行触发的触发器函数可以给调用它的执行者返回一行数据 (一个类型为`HeapTuple`的数值)， 那些在操作之前触发的触发器有以下选择：

- 它可以返回`NULL`以忽略对当前行的操作。 这就指示执行器不要执行调用该触发器的行级别操作(对特定行的插入或者更改)。

- 只用于`INSERT`和`UPDATE`行触发器： 返回的行将成为被插入的行或者是成为将要更新的行。 这样就允许触发器函数修改将要被插入或者更新的行。

一个无意导致任何这类行为的在操作之前触发的行级触发器必须仔细返回那个被当作新行传进来的行。 也就是说，对于`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`行。 语句级别的触发器目前没有任何方法检查该语句修改的独立行。

上一页 | 起始页 | 下一页
触发器 | 上一级 | 数据改变的可视性