触发器

DM 是一个具有主动特征的数据库管理系统,其主动特征包括约束机制和触发器机制。通过触发器机制,用户可以定义、删除和修改触发器。DM 自动管理和运行这些触发器,从而体现系统的主动性,方便用户使用。

触发器(Trigger)定义为当某些与数据库有关的事件发生时,数据库应该采取的操作。这些事件包括全局对象、数据库下某个模式、模式下某个基表上的 INSERT、DELETE 和 UPDATE 操作。触发器与存储模块类似,都是在服务器上保存并执行的一段 DMSQL 程序语句。不同的是:存储模块必须被显式地调用执行,而触发器是在相关的事件发生时由服务器自动地隐式地触发。触发器是语句的一个组成部分,即直到一个语句触发的所有触发器执行完成之后该语句才结束,而其中任何一个触发器执行的失败都将导致该语句的失败,触发器所做的任何工作都属于使该触发器启动执行的语句。

触发器为用户提供了一种自己扩展数据库功能的方法。关于触发器应用的例子有:

  1. 利用触发器实现表约束机制(如:PRIMARY KEY、FOREIGN KEY、CHECK 等)无法实现的复杂的引用完整性;
  2. 利用触发器实现复杂的事务规则(如:想确保薪水增加量不超过 25%);
  3. 利用触发器维护复杂的缺省值(如:条件缺省);
  4. 利用触发器实现复杂的审计功能;
  5. 利用触发器防止非法的操作。

触发器是应用程序分割技术的一个基本组成部分,它将事务规则从应用程序的代码中移到数据库中,从而可确保加强这些事务规则并提高它们的性能。触发器中可以定义变量,但是必须以 DECLARE 开头。

需要说明的是,在 DM 的数据守护环境下,备库上定义的触发器是不会被触发的。

在本章各例中,如不特别说明,各例均使用示例库 BOOKSHOP,用户均为建表者 SYSDBA。

14.1 触发器的定义

触发器分为表触发器、事件触发器和时间触发器。表触发器是对表里数据操作引发的数据库的触发;事件触发器是对数据库对象操作引起的数据库的触发;时间触发器是一种特殊的事件触发器。

14.1.1 表触发器

14.1.1.1 表触发器语法

用户可使用触发器定义语句(CREATE TRIGGER)在一张基表上创建触发器。下面是表触发器定义语句的语法。

语法格式

  CREATE [OR REPLACE] TRIGGER [<模式名>.]<触发器名> [WITH ENCRYPTION]
  <触发限制描述> [REFERENCING ][REFERENCING <trig_referencing_list>][FOR EACH {ROW | STATEMENT}] [<trigger_ordering_clause>] [WHEN (<条件表达式>)]<触发器体>
  <trig_referencing_list>::= <referencing_1>|<referencing_2>
  <referencing_1>::=OLD [ROW] [AS] <引用变量名> [ NEW [ROW] [AS] <引用变量名>]
  <referencing_2>::=NEW [ROW] [AS] <引用变量名> 
  <触发限制描述>::=<触发限制描述1> | <触发限制描述2>
  <触发限制描述1>::= <BEFORE|AFTER> <触发事件列表> [LOCAL] ON <触发表名>
  <触发限制描述2>::= INSTEAD OF <触发事件列表> [LOCAL] ON <触发视图名>
  <触发表名>::=[<模式名>.]<基表名>
  <触发事件>::=INSERT|DELETE|{UPDATE|{UPDATE OF<触发列清单>}}
  <触发事件列表>::=<触发事件> | {<触发事件列表> OR <触发事件>}
  <trigger_ordering_clause>::= FOLLOWS | PRECEDES [<模式名>.]<触发器名> {, [<模式名>.]<触发器名>}

参数

1.< 触发器名 > 指明被创建的触发器的名称;

2.BEFORE 指明触发器在执行触发语句之前启动;

3.AFTER 指明触发器在执行触发语句之后启动;

4.INSTEAD OF 指明触发器执行时替换原始操作;

5.< 触发事件 > 指明使触发器启动的事件。INSTEAD OF 中不支持 {UPDATE OF < 触发列清单 >};

6.< 基表名 > 指明被创建触发器的基表的名称;

7.WITH ENCRYPTION 选项,指定是否对触发器定义进行加密;

8.REFERENCING 子句 指明相关名称可以在元组级触发器的触发器体和 WHEN 子句中利用相关名称来访问当前行的新值或旧值,缺省的相关名称为 OLD 和 NEW;

9.< 引用变量名 > 标识符,指明行的新值或旧值的相关名称;

10.FOR EACH 子句 指明触发器为元组级或语句级触发器。FOR EACH ROW 表示为元组级触发器,它受被触发命令影响、且 WHEN 子句的表达式计算为真的每条记录执行一次。FOR EACH STATEMENT 为语句级触发器,它对每个触发命令执行一次。FOR EACH 子句缺省则为语句级触发器;

11.<trigger_ordering_clause> 指定触发器的触发顺序。FOLLOWS 表示当前触发器需要在指定触发器之后触发;PRECEDES 表示当前触发器需要在指定触发器之前触发。指定的触发器必须和当前创建的触发器类型完全一致,且不存在循环触发结构。最多支持指定 254 个触发器;

12.WHEN 子句 表触发器中只允许为元组级触发器指定 WHEN 子句,它包含一个布尔表达式,当表达式的值为 TRUE 时,执行触发器;否则,跳过该触发器;

13.< 触发器体 > 触发器被触发时执行的 SQL 过程语句块。

图例

表触发器

表触发器

功能

创建触发器,并使其处于允许状态。

使用说明

1.< 触发器名 > 是触发器的名称,它不能与模式内的其他模式级对象同名,并且不能使用“MTRG$_”作为前缀;

2.可以使用 OR REPLACE 选项来替换一个触发器,但是要注意被替换的触发器的触发表不能改变。如果要在同一模式内不同的表上重新创建一个同名的触发器,则必须先删除该触发器,然后再创建;

3.< 触发事件子句 > 说明使触发器启动的事件;< 触发器体 > 是触发器的执行代码;< 引用子句 > 用来引用正处于修改状态下的行中的数据。如果指定了 < 触发条件 > 子句,则首先对该条件表达式求值,< 触发器体 > 只有在该条件为真值时才运行。< 触发器体 > 是一个 DMSQL 程序语句块,它与存储模块定义语句中 < 模块体 > 的语法基本相同;

4.在一张基表上允许创建的表触发器的个数没有限制,一共允许有 12 种类型。它们分别是:BEFORE INSERT 元组级、BEFORE INSERT 语句级、AFTER INSERT 元组级、AFTER INSERT 语句级、BEFORE UPDATE 元组级、BEFORE UPDATE 语句级、AFTER UPDATE 元组级、AFTER UPDATE 语句级、BEFORE DELETE 元组级、BEFORE DELETE 语句级、AFTER DELETE 元组级和 AFTER DELETE 语句级;

5.触发器是在 DML 语句运行时触发的。执行 DML 语句的算法步骤如下:

  1. 如果有语句级前触发器的话,先运行该触发器;
  2. 对于受语句影响每一行:

a) 如果有元组级前触发器的话,运行该触发器;
b) 执行该语句本身;
c) 如果有元组级后触发器的话,运行该触发器。​

  1. 如果有语句级后触发器的话,运行该触发器。

6.INSTEAD OF 触发器仅允许建立在视图上,并且只支持元组级触发;

7.表触发器支持跨模式,创建跨模式触发器时创建者需要拥有 CREATE ANY TRIGGER 权限;触发器体中的操作根据触发器所在的用户检测权限。若创建表触发器时不指定模式名,则模式名默认为创建者所在的模式;

8.水平分区子表、HUGE 表不支持表触发器;

9.在 MPP 环境下,执行 LOCAL 类型触发器时,会话会被临时变为 LOCAL 类型,因此触发器体只会在本节点执行,不会产生节点间的数据交互,触发器体中只能包含表的值插入操作,如果插入数据的目标节点不是于本节点,则会报错,随机分布表没有此限制。

14.1.1.2 表触发器详解

下面对表触发器的触发动作、级别和时机进行详细介绍。

14.1.1.2.1 触发动作

使表触发器启动的触发动作是三种数据操作命令,即 INSERT、DELETE 和 UPDATE 操作。在触发器定义语句中用关键字 INSERT、DELETE 和 UPDATE 指明构成一个触发器事件的数据操作的类型,其中 UPDATE 触发器会依赖于所修改的列,在定义中可通过 UPDATE OF < 触发列清单 > 的形式来指定所修改的列,< 触发列清单 > 指定的字段数不能超过 128 个。

在 PERSON.PERSON 上建立触发器。如下例所示:

  SET SCHEMA PERSON;

  CREATE OR REPLACE TRIGGER TRG_UPD

  AFTER UPDATE OF NAME,PHONE ON PERSON.PERSON

  BEGIN

  PRINT 'UPDATE OPERATION ON COLUMNS NAME OR PHONE OF PERSON';

  END;

  SET SCHEMA SYSDBA;

当对表 PERSON 进行更新操作,并且更新的列中包括 NAME 或 PHONE 时,此例中定义的触发器 TRG_UPD 将被启动。

如果一个触发器的触发事件为 INSERT,则该触发器被称为 INSERT 触发器,同样也可以这样来定义 DELETE 触发器和 UPDATE 触发器。一个触发器的触发事件也可以是多个数据操作命令的组合,这时这个触发器可由多种数据操作命令触发。如下例所示:

  SET SCHEMA PERSON;

  CREATE OR REPLACE TRIGGER TRG_INS_DEL

  AFTER INSERT OR DELETE ON PERSON.PERSON

  BEGIN

	PRINT 'INSERT OR DELETE OPERATION ON PERSON';

  END;

  SET SCHEMA SYSDBA;

此例中的触发器 TRG_INS_DEL 既是 INSERT 触发器又是 DELETE 触发器,对表 PERSON 的 INSERT 和 DELETE 操作都会使该触发器启动。

14.1.1.2.2 触发级别

根据触发器的级别可分为元组级 (也称行级)和语句级。

元组级触发器,对触发命令所影响的每一条记录都触发一次。假如一个 DELETE 命令从表中删除了 1000 行记录,那么这个表上的元组级 DELETE 触发器将被执行 1000 次。元组级触发器常用于数据审计、完整性检查等应用中。元组级触发器是在触发器定义语句中通过 FOR EACH ROW 子句创建的。对于元组级触发器,可以用一个 WHEN 子句来限制针对当前记录是否执行该触发器。WHEN 子句包含一条布尔表达式,当它的值为 TRUE 时,执行触发器;否则,跳过该触发器。

语句级触发器,对每个触发命令执行一次。例如,对于一条将 500 行记录插入表 TABLE_1 中的 INSERT 语句,这个表上的语句级 INSERT 触发器只执行一次。语句级触发器一般用于对表上执行的操作类型引入附加的安全措施。语句级触发器是在触发器定义语句中通过 FOR EACH STATEMENT 子句创建的,该子句可缺省。

以下分别是元组级触发器和语句级触发器的例子。

  SET SCHEMA PERSON;

  CREATE OR REPLACE TRIGGER TRG_DEL_ROW
  BEFORE DELETE ON PERSON.PERSON
  FOR EACH ROW			//元组级:此子句一定不能省略
  BEGIN
	PRINT 'DELETE' || :OLD.NAME|| ' ON PERSON';
  END;

  CREATE OR REPLACE TRIGGER TRG_INS_ST
  AFTER INSERT ON PERSON.PERSON
  FOR EACH STATEMENT	//语句级:此子句可省略
  BEGIN
	PRINT 'AFTER INSERT ON PERSON';
  END;

SET SCHEMA SYSDBA;
14.1.1.2.3 触发时机

触发时机通过两种方式指定。一是通过指定 BEFORE 或 AFTER 关键字,选择在触发动作之前或之后运行触发器;二是通过指定 INSTEAD OF 关键字,选择在动作触发的时候,替换原始操作,INSTEAD OF 允许建立在视图上,并且只支持元组级触发。

在元组级触发器中可以引用当前修改的记录在修改前后的值,修改前的值称为旧值,修改后的值称为新值。对于插入操作不存在旧值,而对于删除操作则不存在新值。

对于新、旧值的访问请求常常决定一个触发器是 BEFORE 类型还是 AFTER 类型。如果需要通过触发器对插入的行设置列值,那么为了能设置新值,需要使用一个 BEFORE 触发器,因为在 AFTER 触发器中不允许用户设置已插入的值。在审计应用中则经常使用 AFTER 触发器,因为元组修改成功后才有必要运行触发器,而成功地完成修改意味着成功地通过了该表的引用完整性约束。

例 1 BEFORE 触发器和 AFTER 触发器的举例。

BEFORE 触发器示例

  SET SCHEMA OTHER;

  CREATE OR REPLACE TRIGGER TRG_INS_BEFORE

  BEFORE INSERT ON OTHER.READER

  FOR EACH ROW

  BEGIN

	:NEW.READER_ID:=:NEW.READER_ID+1;

  END;

  SET SCHEMA SYSDBA;

该触发器在插入一条记录前,将记录中 READER_ID 列的值加 1。

  CREATE TABLE T_TEMP(C1 INT,C2 CHAR(20));

新建表 T_TEMP。

  SET SCHEMA OTHER;

  CREATE OR REPLACE TRIGGER TRG_INS_AFTER

  AFTER INSERT ON OTHER.READER

  FOR EACH ROW

  BEGIN

	INSERT INTO SYSDBA.T_TEMP VALUES(:NEW.READER_ID, 'INSERT ON READER');

  END;

  SET SCHEMA SYSDBA;

该触发器在插入一条记录后,将插入的值以及操作类型记录到用于审计的表 T_TEMP 中。

例 2 INSTEAD OF 触发器举例。

  create table t1(a int,b int);

  insert into t1 values(10,10);

  insert into t1 values(11,11);

  create view v1 as select * from t1;

在视图 v1 上创建 INSTEAD OF 触发器。

  CREATE OR REPLACE TRIGGER tri1
  INSTEAD OF UPDATE ON v1
  BEGIN
	insert into t1 values(111,111);  //替换动作
  END;

当执行 UPDATE 动作时候,就会触发。将下面的动作替换成触发器里的动作。

  update v1 set a=100 where a=10;

查询结果如下:

A           B
----------- -----------
10          10
11          11
111         111

由上面的查询结果可以看出。更新操作并没有成功,而是被触发器中的替换动作替换了。这就是 INSTEAD OF 的妙用之处。

14.1.1.2.4 总结

综上所述,在一张基表上所允许的可能的合法表级触发器类型共有 12 种,如表 14.1.1 所示。

表14.1.1 表触发器类型
名 称 功 能
BEFORE INSERT 在一个 INSERT 处理前触发一次
AFTER INSERT 在一个 INSERT 处理后触发一次
BEFORE DELETE 在一个 DELETE 处理前触发一次
AFTER DELETE 在一个 DELETE 处理后触发一次
BEFORE UPDATE 在一个 UPDATE 处理前触发一次
AFTER UPDATE 在一个 UPDATE 处理后触发一次
BEFORE INSERT FOR EACH ROW 每条新记录插入前触发
AFTER INSERT FOR EACH ROW 每条新记录插入后触发
BEFORE DELETE FOR EACH ROW 每条记录被删除前触发
AFTER DELETE FOR EACH ROW 每条记录被删除后触发
BEFORE UPDATE FOR EACH ROW 每条记录被修改前触发
AFTER UPDATE FOR EACH ROW 每条记录被修改后触发

14.1.1.3 触发器启动顺序

下面是执行 DML 语句的算法步骤:

1.如果有语句级前触发器的话,先运行该触发器;

2.对于受语句影响每一行:

  1. 如果有元组级前触发器的话,运行该触发器;
  2. 执行该语句本身;
  3. 如果有元组级后触发器的话,运行该触发器。

3.如果有语句级后触发器的话,运行该触发器。

为了说明上面的算法,假设我们用 OTHER.READER 表为例,并在其上创建了所有四种 UPDATE 触发器,即之前、之后、元组级前和元组级后。其代码如下:

  SET SCHEMA OTHER;

  CREATE OR REPLACE TRIGGER Reader_Before_St

  BEFORE UPDATE ON OTHER.READER

  BEGIN

	PRINT 'BEFORE UPDATE TRIGGER FIRED';

  END;

  CREATE OR REPLACE TRIGGER Reader_After_St

  AFTER UPDATE ON OTHER.READER

  BEGIN

	PRINT 'AFTER UPDATE TRIGGER FIRED';

  END;

  CREATE OR REPLACE TRIGGER Reader_Before_Row

  BEFORE UPDATE ON OTHER.READER

  FOR EACH ROW

  BEGIN

	PRINT 'BEFORE UPDATE EACH ROW TRIGGER FIRED';

  END;

  CREATE OR REPLACE TRIGGER Reader_After_Row

  AFTER UPDATE ON OTHER.READER

  FOR EACH ROW

  BEGIN

	PRINT 'AFTER UPDATE EACH ROW TRIGGER FIRED';

  END;

  SET SCHEMA SYSDBA;

现在,执行更新语句:

  UPDATE OTHER.READER SET AGE=AGE+1;

该语句对三行有影响。语句级前触发器和语句级后触发器将各自运行一次,而元组级前触发器和元组级后触发器则各运行三次。因此,服务器返回的打印消息应为:

  BEFORE UPDATE TRIGGER FIRED

  BEFORE UPDATE EACH ROW TRIGGER FIRED

  AFTER UPDATE EACH ROW TRIGGER FIRED

  BEFORE UPDATE EACH ROW TRIGGER FIRED

  AFTER UPDATE EACH ROW TRIGGER FIRED

  BEFORE UPDATE EACH ROW TRIGGER FIRED

  AFTER UPDATE EACH ROW TRIGGER FIRED

  AFTER UPDATE TRIGGER FIRED

同类型触发器的启动顺序可以通过 FOLLOWS 或 PRECEDES 关键字来指定,具体请参考 14.1.1.1 表触发器语法

14.1.1.4 新、旧行值的引用

前面曾经提到,在元组级触发器内部,可以访问正在处理中的记录的数据,这种访问是通过两个引用变量:OLD 和:NEW 实现的。:OLD 表示记录被处理前的值,:NEW 表示记录被处理后的值,标识符前面的冒号说明它们是宿主变量意义上的连接变量,而不是一般的 DMSQL 程序变量。我们还可以通过引用子句为这两个行值重新命名。

引用变量与其它变量不在同一个命名空间,所以变量可以与引用变量同名。在触发器体中使用引用变量时,必须采用下列形式:

  :引用变量名.列名

其中,列名必须是触发表中存在的列,否则编译器将报错。

下表总结了标识符:OLD 和:NEW 的含义。

表14.1.2 标识符:OLD和:NEW的含义
触发语句 标识符:OLD 标识符:NEW
INSERT 无定义,所有字段都为 NULL 该语句结束时将插入的值
UPDATE 更新前行的旧值 该语句结束时将更新的值
DELETE 行删除前的旧值 无定义,所有字段都为 NULL

:OLD 引用变量只能读取,不能赋值(因为设置这个值是没有任何意义的);而:NEW 引用变量则既可读取,又可赋值(当然必须在 BEFORE 类型的触发器中,因为数据操作完成后再设置这个值也是没有意义的)。通过修改:NEW 引用变量的值,我们可以影响插入或修改的数据。

注意:对于 INSERT 操作,引用变量:OLD 无意义;而对于 DELETE 操作,引用变量:NEW 无意义。如果在 INSERT 触发器体中引用:OLD,或者在 DELETE 触发器体中引用:NEW,不会产生编译错误。但是在执行时,对于 INSERT 操作,:OLD 引用变量的值为空值;对于 DELETE 操作,:NEW 引用变量的值为空值,且不允许被赋值。

例 1 下例中触发器 GenerateValue 使用了:OLD 引用变量。该触发器是一个 UPDATE 前触发器,其目的是做更新操作时不论是否更新表中某列的值,该列的值保持原值不变。这里使用 PRODUCTION.PRODUCT 表为例。

  SET SCHEMA PRODUCTION;

  CREATE OR REPLACE TRIGGER GenerateValue

  BEFORE UPDATE ON PRODUCTION.PRODUCT

  FOR EACH ROW

  BEGIN

  :new.NOWPRICE:=:old.NOWPRICE;

  END;

  SET SCHEMA SYSDBA;

当执行一个 UPDATE 语句时,无论用户是否给定 NOWPRICE 字段的值,触发器都将自动保持原来的 NOWPRICE 值不变。例如,用户查询 PRODUCTID=1 的一行数据的 NOWPRICE 值。

  SELECT NOWPRICE FROM PRODUCTION.PRODUCT WHERE PRODUCTID=1;

可得到结果为 NOWPRICE=15.2000;

用户可以执行如下所示的 UPDATE 语句。

  UPDATE PRODUCTION.PRODUCT SET NOWPRICE =1.0000 WHERE PRODUCTID=1;

再次执行查询语句。

  SELECT NOWPRICE FROM PRODUCTION.PRODUCT WHERE PRODUCTID=1;

可发现结果仍然为 NOWPRICE=15.2000,而非修改的 1.0000。

例 2 下例中触发器 GenerateValue 使用了:NEW 引用变量。该触发器是一个 INSERT 前触发器,其目的是自动生成一些字段值。这里使用 PRODUCTION.PRODUCT 表为例。

  SET SCHEMA PRODUCTION;

  CREATE OR REPLACE TRIGGER GenerateValue

  BEFORE INSERT ON PRODUCTION.PRODUCT

  FOR EACH ROW

  BEGIN

  :new.NOWPRICE:=:new.ORIGINALPRICE*:new.DISCOUNT;

  END;

  SET SCHEMA SYSDBA;

触发器 GenerateValue 实际上是修改引用变量:NEW 的值,这就是:NEW 引用变量的用途之一。当执行一个 INSERT 语句时,无论用户是否给定 NOWPRICE 字段的值,触发器都将自动利用 ORIGINALPRICE 字段和 DISCOUNT 字段来计算 NOWPRICE。例如,用户可以执行如下所示的 INSERT 语句。

  INSERT INTO PRODUCTION.PRODUCT

  (NAME,AUTHOR,PUBLISHER,PUBLISHTIME,PRODUCTNO,PRODUCT_SUBCATEGORYID,

  SATETYSTOCKLEVEL,ORIGINALPRICE,DISCOUNT,SELLSTARTTIME)

  VALUES('老人与海','海明威','上海出版社','2006-8-1','9787532740088',1,'10','100','7.5','2008-1-10');

即使为 NOWPRICE 字段指定了值,该值也会被忽略,因为触发器将改变该值。

  INSERT INTO PRODUCTION.PRODUCT

  (NAME,AUTHOR,PUBLISHER,PUBLISHTIME,PRODUCTNO,PRODUCT_SUBCATEGORYID,

  SATETYSTOCKLEVEL,ORIGINALPRICE,DISCOUNT,NOWPRICE,SELLSTARTTIME)

  VALUES('老人与海','海明威','上海出版社','2006-8-1','9787532740089',1,'10','100','7.5','88','2008-1-10');

新插入元组的 NOWPRICE 字段将被触发器修改为 750.0000,而不是语句中的 88。

14.1.1.5 触发器谓词

如前面介绍的,触发事件可以是多个数据操作的组合,即一个触发器可能既是 INSERT 触发器,又是 DELETE 或 UPDATE 触发器。当一个触发器可以为多个 DML 语句触发时,在这种触发器体内部可以使用三个谓词:INSERTING、DELETING 和 UPDATING 来确定当前执行的是何种操作。这三个谓词的含义如下表所示。

表14.1.3 触发器谓词
谓 词 状 态
INSERTING 当触发语句为 INSERT 时为真,否则为假
DELETING 当触发语句为 DELETE 时为真,否则为假
UPDATING[(< 列名 >)] 未指定列名时,当触发语句为 UPDATE 时为真,否则为假;指定某一列名时,当触发语句为对该列的 UPDATE 时为真,否则为假

虽然在其他 DMSQL 程序语句块中也可以使用这三个谓词,但这时它们的值都为假。

下例中的触发器 LogChanges 使用这三个谓词来记录表 OTHER.READER 发生的所有变化。除了记录这些信息外,它还记录对表进行变更的用户名。该触发器的记录存放在表 OTHER.READERAUDIT 中。

触发器 LogChanges 的创建语句如下:

  SET SCHEMA OTHER;

  CREATE OR REPLACE TRIGGER LogChanges
  AFTER INSERT OR DELETE OR UPDATE ON OTHER.READER
  FOR EACH ROW
  DECLARE
  v_ChangeType CHAR(1);
  BEGIN
  //‘I’表示INSERT操作,‘D’表示DELETE操作,‘U’表示UPDATE操作
  IF INSERTING THEN
  v_ChangeType := 'I';
  ELSIF UPDATING THEN
  v_ChangeType := 'U';
  ELSE
  v_ChangeType := 'D';
  END IF;
  //记录对Reader做的所有修改到表ReaderAudit中,包括修改人和修改时间
  INSERT INTO OTHER.READERAUDIT
  VALUES
  (v_ChangeType, USER, SYSDATE,
  :old.reader_id, :old.name, :old.age, :old.gender, :old.major,
  :new.reader_id, :new.name, :new.age, :new.gender, :new.major);
  END;

  SET SCHEMA SYSDBA;

14.1.2 事件触发器

14.1.2.1 事件触发器语法

用户可使用触发器定义语句(CREATE TRIGGER)在数据库全局对象上创建触发器。下面是触发器定义语句的语法:

语法格式

  CREATE [OR REPLACE] TRIGGER [<模式名>.]<触发器名> [WITH ENCRYPTION]
  <BEFORE| AFTER> <触发事件子句> ON <触发对象名> [EXECUTE AT <触发的RAFT组名><DMDSC节点号>][WHEN <条件表达式>]<触发器体>
  <触发事件子句>:=<DDL事件子句>| <系统事件子句>
  <DDL事件子句>:=<DDL事件>{OR <DDL事件>}
  <DDL事件>:=DDL|<CREATE|ALTER|DROP|GRANT|REVOKE|TRUNCATE|COMMENT>
  <系统事件子句>:=<系统事件>{OR <系统事件>}
  <系统事件>:= LOGIN|LOGOUT|SERERR|<BACKUP DATABASE>|<RESTORE DATABASE>|AUDIT|NOAUDIT|TIMER|STARTUP|SHUTDOWN|CHECKPOINT
  <触发对象名>:=[<模式名>.]SCHEMA|
              DATABASE

参数

1.< 模式名 > 指明被创建的触发器的所在的模式名称或触发事件发生的对象所在的模式名,缺省为当前模式;

2.< 触发器名 > 指明被创建的触发器的名称;

3.BEFORE 指明触发器在执行触发语句之前启动;

4.AFTER 指明触发器在执行触发语句之后启动;

5.< 触发的 RAFT 组名 > 专门用于 DMDPC(非 DPC 环境直接忽略指定值),用于指定在 RAFT 组中的节点触发,不涉及的节点上不触发。其中,多副本的 MP RAFT 只在主库触发。缺省 < 触发的 RAFT 组名 > 时,由于 TIMER 触发器数量与复杂程度均不可控,默认选择 ID 最小的 SP 执行(如果无活动 SP 则选择 ID 最小的 BS),以减小对 MP 自身事务管理任务的干扰。< 触发的 RAFT 组名 > 指定为“RAFT_AT_ALL_SP$”时,MP 将触发器动态分发到各个 SP 执行,实现负载相对均衡。对确定在 MP 执行的触发器(如 DDL)忽略指定的 RAFT。BP 模式的节点上不触发任何触发器;

6.<DDL 触发事件子句 > 指明使触发器启动的 DDL 事件,可以是 DDL 或 CREATE、ALTER、DROP、GRANT、REVOKE、TRUNCATE、COMMENT 等;

7.< 系统事件子句 > LOGIN/LOGON、LOGOUT/LOGOFF、SERERR、BACKUP DATABASE、RESTORE DATABASE、AUDIT、NOAUDIT、TIMER、STARTUP、SHUTDOWN、CHECKPOINT;

8.WITH ENCRYPTION 选项,指定是否对触发器定义进行加密;

9.WHEN 子句 WHEN 子句包含一个布尔表达式,当表达式的值为 TRUE 时,执行触发器;否则,跳过该触发器;

10.< 触发器体 > 触发器被触发时执行的 SQL 过程语句块;

11.<DMDSC 节点号 > 指定 DMDSC 环境下触发器执行的节点号,仅 CHECKPOINT 事件触发器支持指定 <DMDSC 节点号 >。

图例

事件触发器

事件触发器.png

DDL 事件

DDL 事件

系统事件

图 14.4 系统事件.png

使用说明

1.< 触发器名 > 是触发器的名称,它不能与模式内的其他模式级对象同名,并且不能使用“MTRG$_”作为前缀;

2.可以使用 OR REPLACE 选项来替换一个触发器,但是要注意被替换的触发器的触发对象名不能改变。如果要在模式中不同的对象上重新创建一个同名的触发器,则必须先删除该触发器,然后再创建;

3.< 触发事件子句 > 说明使触发器启动的事件,DDL 事件以及系统事件。DDL 事件包括数据库和模式上的 DDL 操作;系统事件包括数据库上的除 DDL 操作以外系统事件;以上事件可以有多个,用 OR 列出。触发事件按照兼容性可以分为以下几个集合:

{CREATE, ALTER, DROP, TRUNCATE,COMMENT }、{ GRANT, REVOKE }、{ LOGIN/LOGON, LOGOUT/LOGOFF }、{ SERERR }、{ BACKUP DATABASE, RESTORE DATABASE }、{AUDIT, NOAUDIT}、{TIMER}、{ STARTUP, SHUTDOWN }、{CHECKPOINT}。

只有同一个集合中,不同名的事件,才能在创建语句中并列出现。

DDL 事件中,DDL 关键字的作用相当于 CREATE OR DROP OR ALTER OR TRUNCATE OR COMMENT。

4.< 触发对象名 > 是触发事件发生的对象,DATABASE 和 < 模式名 > 只对 DDL 事件有效, < 模式名 > 可以缺省;

5.在一个数据库或模式上创建的事件触发器个数没有限制,可以有以下类型:CREATE、ALTER、DROP、GRANT、REVOKE、TRUNCATE、COMMENT、LOGIN/LOGON、LOGOUT/LOGOFF、SERERR、BACKUP DATABASE、STARTUP、SHUTDOWN,且仅表示指该类操作,不涉及到具体数据库对象如 CREATE/ALTER/DROP TABLE,只要能引起任何数据字典表中的数据对象变化,都可以启动相应触发器,触发时间分为 BEFORE 和 AFTER;所有 DDL 事件触发器都可以设置 BEFORE 或 AFTER 的触发时机,但系统事件中 LOGOUT 和 SHUTDOWN 仅能设置为 BEFORE,而其它则只能设置为 AFTER。模式级触发器不能是 LOGIN/LOGON、LOGOUT/LOGOFF、SERERR、BACKUP DATABASE、RESTORE DATABASE、CHECKPOINT、STARTUP 和 SHUTDOWN 事件触发器。

6.通过系统存储过程 SP_ENABLE_EVT_TRIGGER 和 SP_ENABLE_ALL_EVT_TRIGGER 可以禁用/启用指定的事件触发器或所有的事件触发器。

7.事件操作说明如下:

对于事件触发器,所有的事件信息都通过伪变量 :EVENTINFO 来取得。

下面对每种事件可以获得的信息进行详细说明:

1)CREATE:添加新的数据库对象(包括用户、基表、视图等)到数据字典时触发;

  对象类型描述::eventinfo.objecttype
  指明事件对象的类型,类型为VARCHAR(128),对于不同的类型其值如下:
  用户:	'USER'
  表:	'TABLE'
  视图:	'VIEW'
  索引:	'INDEX'
  过程:	'PROCEDURE'
  函数:	'FUNCTION'
  角色:	'ROLE'
  模式:	'SCHEMA'
  序列:	'SEQUENCE'
  触发器:	'TRIGGER'
  同义词:	'SYNONYM'
  包:	 'PACKAGE'
  类:	 'CLASS'
  类型:	'TYPE'
  包体:	'PACKAGEBODY'
  类体:	'CLASSBODY'
  类型体:	'TYPEBODY'
  表空间:	'TABLESPACE'
  域:	 'DOMAIN'
  目录:	'DIRECTORY'
  外部链接:'LINK'
  对象名称:	:eventinfo.objectname
  	  指明事件对象的名称,类型为VARCHAR(128)
  所属模式:	 :eventinfo.schemaname
  	  指明事件对象所属的模式名,类型为VARCHAR(128),针对不同类型的对象有可能为空
  所属数据库: :eventinfo.databasename
  	 指明事件对象所属的数据库名,类型为VARCHAR(128),针对不同类型的对象有可能为空
  操作类型:	 :eventinfo.optype
 	  指明事件的操作类型,类型为VARCHAR(20),其值为“CREATE”
  操作用户名: :eventinfo.opuser
  	  指明事件操作者的用户名,类型为VARCHAR(128)
  事件发生时间::eventinfo.optime
  	  指明事件发生的时间,类型为DATETIME

2)ALTER:只要 ALTER 修改了数据字典中的数据对象(包括用户、基表、视图等),就激活触发器;

  对象类型描述: :eventinfo.objecttype
  指明事件对象的类型,类型为CHAR(1),对于不同的类型其值如下:
  用户:	'USER'
  表:	'TABLE'
  视图:	'VIEW'
  索引:	'INDEX'
  过程:	'PROCEDURE'
  函数:	'FUNCTION'
  序列:	'SEQUENCE'
  触发器:	'TRIGGER'
  表空间:	'TABLESPACE'
  对象名称::eventinfo.objectname
  	  指明事件对象的名称,类型为VARCHAR(128)
  所属模式::eventinfo.schemaname
  	  指明事件对象所属的模式名,类型为VARCHAR(128),针对不同类型的对象有可能为空
  所属数据库::eventinfo.databasename
  	  指明事件对象所属的数据库名,类型为VARCHAR(128),针对不同类型的对象有可能为空
  操作类型::eventinfo.optype
  	  指明事件的操作类型,类型为VARCHAR(20),其值为“ALTER”
  操作用户名::eventinfo.opuser
  	  指明事件操作者的用户名,类型为VARCHAR(128)
  事件发生时间::eventinfo.optime
  	  指明事件发生的时间,类型为DATETIME

3)DROP:从数据字典删除数据库对象(包括用户、登录、基表、视图等)时触发;

  对象类型描述: :eventinfo.objecttype
  指明事件对象的类型,类型为VARCHAR(128),对于不同的类型其值如下:
  用户:	 'USER'
  表:	 'TABLE'
  视图:	 'VIEW'
  索引:	 'INDEX'
  过程:	 'PROCEDURE'
  函数:	 'FUNCTION'
  角色:	 'ROLE'
  模式:	 'SCHEMA'
  序列:	 'SEQUENCE'
  触发器:	'TRIGGER'
  同义词:	 'SYNONYM'
  包:	  'PACKAGE'
  类:	  'CLASS'
  类型:	 'TYPE'
  表空间:	 'TABLESPACE'
  域:	  'DOMAIN'
  目录:	 'DIRECTORY'
  外部链接: 'LINK'
  对象名称::eventinfo.objectname
     指明事件对象的名称,类型为VARCHAR(128)
  所属模式::eventinfo.schemaname
     指明事件对象所属的模式名,类型为VARCHAR(128),针对不同类型的对象有可能为空
  所属数据库::eventinfo.databasename
	 指明事件对象所属的数据库名,类型为VARCHAR(128),针对不同类型的对象有可能为空
  操作类型::eventinfo.optype
	 指明事件的操作类型,类型为VARCHAR(20),其值为“DROP”
  操作用户名::eventinfo.opuser
	 指明事件操作者的用户名,类型为VARCHAR(128)
  事件发生时间::eventinfo.optime
	 指明事件发生的时间,类型为DATETIME

4)GRANT:执行 GRANT 命令时触发;

  权限类型描述: :eventinfo.granttype,对于不同的类型其值如下:

	  对象权限:'OBJECT_PRIV'

	  系统权限:'SYSTEM_PRIV'

	  角色权限:'ROLE_PRIV'

	  指明授予权限的类型,类型为varchar(256)

  授予权限对象的用户名: :eventinfo.grantee

	  指明授予权限的对象用户,类型为varchar(256)

	  对象名称:	 :eventinfo.objectname

	  对象权限有效,指明事件对象的名称,类型为VARCHAR(128)

	  所属模式:	 :eventinfo.schemaname

	  对象权限有效,指明事件对象所属的模式名,类型为VARCHAR(128),针对不同类型的对象有可能为空

  所属数据库:  	:eventinfo.databasename

  	  指明事件对象所属的数据库名,类型为VARCHAR(256),针对不同类型的对象有可能为空

  操作用户名:  	:eventinfo.opuser

	  指明事件操作者的用户名,类型为VARCHAR(256)

  事件发生时间:	:eventinfo.optime

	  指明事件发生的时间,类型为DATETIME

  对象名称:	 	:eventinfo.objectname

	  指明事件对象的名称,类型为VARCHAR(128)

  所属模式:	 	:eventinfo.schemaname

	  指明事件对象所属的模式名,类型为VARCHAR(128),针对不同类型的对象有可能为空

5)REVOKE:执行 REVOKE 命令时触发;

  权限类型描述: :eventinfo.granttype,对于不同的类型其值如下:

  	  对象权限:'OBJECT_PRIV'

  	  系统权限:'SYSTEM_PRIV'

      角色权限:'ROLE_PRIV'

  	  指明回收权限的类型,类型为varchar(256)

  授予权限对象的用户名: :eventinfo.grantee

  	  指明回收权限的对象用户,类型为varchar(256)

  对象名称:		:eventinfo.objectname

	  对象权限有效,指明事件对象的名称,类型为VARCHAR(128)

  所属模式:		:eventinfo.schemaname

	  对象权限有效,指明事件对象所属的模式名,类型为VARCHAR(128),针对不同类型的对象有可能为空

  操作用户名: 	:eventinfo.opuser

	  指明事件操作者的用户名,类型为VARCHAR(256)

  所属数据库: 	:eventinfo.databasename

	  指明事件对象所属的数据库名,类型为VARCHAR(256),针对不同类型的对象有可能为空

  事件发生时间:	:eventinfo.optime

	  指明事件发生的时间,类型为DATETIME

  对象名称:		 :eventinfo.objectname

	  指明事件对象的名称,类型为VARCHAR(128)

  所属模式: 	:eventinfo.schemaname

	  指明事件对象所属的模式名,类型为VARCHAR(128),针对不同类型的对象有可能为空

6)TRUNCATE:执行 TRUNCATE 命令时触发;

  对象名称: 	:eventinfo.objectname,对于不同的类型其值如下:

  	  表: 'TABLE'

	  指明事件对象的名称,类型为VARCHAR(256)

  所属模式:	 	:eventinfo.schemaname

	  指明事件对象所属的模式名,类型为VARCHAR(256),针对不同类型的对象有可能为空

  所属数据库: 	:eventinfo.databasename

	  指明事件对象所属的数据库名,类型为VARCHAR(256),针对不同类型的对象有可能为空

  操作类型: 	:eventinfo.optype

	  指明事件的操作类型,类型为VARCHAR(20),其值为“TRUNCATE”

  操作用户名: 	:eventinfo.opuser

	  指明事件操作者的用户名,类型为VARCHAR(256)

  事件发生时间:	:eventinfo.optime

	  指明事件发生的时间,类型为DATETIME

7)LOGIN/LOGON:登录时触发;

  登录名: 		:eventinfo.loginname

	  指明登录时的用户名,类型为VARCHAR(256)

  事件发生时间: :eventinfo.optime

	  指明事件发生的时间,类型为DATETIME

8)LOGOUT/LOGOFF:退出时触发;

  登录名: 		:eventinfo.loginname

	  指明退出时的用户名,类型为VARCHAR(256)

  事件发生时间: :eventinfo.optime

	  指明事件发生的时间,类型为DATETIME

9)BACKUP DATABASE:备份数据库时触发;

  备份的数据库: :eventinfo.databasename

	  指明事件对象所属的数据库名,类型为VARCHAR(256),针对不同类型的对象有可能为空。

  备份名: 		:eventinfo.backuname

 	  指明的备份名,类型为VARCHAR(256)

  操作用户名:   :eventinfo.opuser

	  指明事件操作者的用户名,类型为VARCHAR(256)

  事件发生时间: :eventinfo.optime

	  指明事件发生的时间,类型为DATETIME

10)RESTORE DATABASE:还原数据库时触发;

  还原的数据库: :eventinfo.databasename

	  指明事件对象所属的数据库名,类型为VARCHAR(256),针对不同类型的对象有可能为空。

  还原的备份名: :eventinfo.backuname

	  指明的备份名,类型为VARCHAR(256)

  操作用户名:  :eventinfo.opuser

	  指明事件操作者的用户名,类型为VARCHAR(256)

  事件发生时间: :eventinfo.optime

	  指明事件发生的时间,类型为DATETIME

11)SERERR:只要服务器记录了错误消息就触发;

  错误号: 		:eventinfo. ERRCODE

	  指明错误的错误号,类型为INT

  错误信息: 	:eventinfo.errmsg

	  指明错误的错误信息,类型为VARCHAR(256)

  事件发生时间:	:eventinfo.optime

	  指明事件发生的时间,类型为DATETIME

12)COMMENT ON DATABASE/SCHEMA:执行 COMMENT 命令时触发;

  操作类型: :eventinfo.objecttype

	  指明事件对象类型,类型为VARCHAR(20)

  对象名称: :eventinfo.objectname

	  指明事件对象的名称,类型为VARCHAR(128)

  所属模式: :eventinfo.schemaname

	  指明事件对象所属的模式名,类型为VARCHAR(128),针对不同类型的对象有可能为空

  所属数据库: :eventinfo.databasename

	  指明事件对象所属的数据库名,类型为VARCHAR(256),针对不同类型的对象有可能为空

  操作类型: :eventinfo.optype

	  指明事件的操作类型,类型为VARCHAR(20),其值为“COMMENT”

  操作用户名: :eventinfo.opuser

	  指明事件操作者的用户名,类型为VARCHAR(128)

  事件发生时间::eventinfo.optime

	  指明事件发生的时间,类型为DATETIME

13)AUDIT:进行审计时触发(用于收集,处理审计信息);

14)NOAUDIT:不审计时触发;

15)TIMER:定时触发。见下文时间触发器;

16)STARTUP:服务器启动后触发,只能 AFTER STARTUP。

  SHUTDOWN:服务器关闭前触发,只能 BEFORE SHUTDOWN。SHUTDOWN 触发,不要执行花费时间多于 5 秒的操作。

8.< 触发器体 > 是触发器的执行代码,是一个 DMSQL 程序语句块,语句块与存储模块定义语句中 < 模块体 > 的语法基本相同。有关详细语法,可参考第 10 章 外部函数的相关部分。< 引用子句 > 用来引用正处于修改状态下表中行的数据。如果指定了 < 触发条件 > 子句,则首先对该条件表达式求值,< 触发器体 > 只有在该条件为真值时才运行;

9.创建模式触发器时,触发对象名直接用 SCHEMA;

10.创建的触发器可以分为以下几类:

1)在自己拥有的模式中创建自己模式的对象上的触发器或创建自己模式上的触发器;

2)在任意模式中创建任意模式的对象上的触发器或创建其他用户模式上(. SCHEMA)的触发器,即支持跨模式的触发器,表现为 < 触发器名 > 和 < 触发对象名 > 的 < 模式名 > 不同;

3)创建数据库上(DATABASE)的触发器。

11.触发器的创建者必须拥有 CREATE TRIGGER 数据库权限并具有触发器定义中引用对象的权限;

12.DDL 触发事件的用户必须拥有对模式或数据库上相应对象的 DDL 权限;系统触发事件的用户必须有 DBA 权限;

13.如果触发器执行 SQL 语句或调用过程或函数,那么触发器的拥有者必须拥有执行这些操作所必需的权限。这些权限必须直接授予触发器拥有者,而不是通过角色授予,这与存储模块或函数的限制一致;

14.如果触发器同时也是触发事件对象,则该触发器不会被触发,例如:当删除触发器本身被删除时不会触发 DROP 触发器。

权限

用户必须是基表的拥有者,或者具有 DBA 权限。

需要强调的是,由于触发器是使其启动执行的语句的一个组成部分,为保证语句的原子性,在 < 触发器体 > 以及 < 触发器体 > 调用的存储模块中不允许使用可能导致事务提交或回滚的 SQL 语句,如:COMMIT、ROLLBACK。

具体地说,在触发器中允许的 SQL 语句有: SELECT、INSERT、DELETE、UPDATE、DECLARE CURSOR、OPEN、FETCH、CLOSE 语句等。

每张基表上的可创建的触发器的个数没有限制,但是触发器的个数越多,处理 DML 语句所需的时间就越长,这是显而易见的。注意,不存在触发器的执行权限,因为用户不能主动“调用”某个触发器,是否启动一个触发器是由系统来决定的。

事件触发器(DDL 触发事件)使用示例如下:

CREATE TABLE T01_TRI_10000(OBJECTTYPE VARCHAR(500),OBJECTNAME VARCHAR(500), SCHEMANAME VARCHAR(500),DATABASENAME VARCHAR(500),OPUSER VARCHAR(500), OPTIME VARCHAR(500));

CREATE TABLE T02_TRI_10000 (C1 INT,C2 VARCHAR(10));
INSERT INTO T02_TRI_10000 VALUES (1,'ABCD');
CREATE TRIGGER TRI01_TRI_10000 BEFORE CREATE ON DATABASE BEGIN INSERT INTO T01_TRI_10000 VALUES(:EVENTINFO.OBJECTTYPE,:EVENTINFO.OBJECTNAME,:EVENTINFO.SCHEMANAME,:EVENTINFO.DATABASENAME,:EVENTINFO.OPUSER, :EVENTINFO.OPTIME); END;

CREATE USER L01_TRI_10000 IDENTIFIED BY DMsys_123;
CREATE TABLE T03_TRI_10000(C1 INT);
CREATE VIEW V01_TRI_10000 AS SELECT * FROM T01_TRI_10000;
CREATE INDEX I01_TRI_10000 ON T01_TRI_10000(OBJECTTYPE);
CREATE OR REPLACE PROCEDURE P01_TRI_10000 AS BEGIN SELECT * FROM T02_TRI_10000; END;

CREATE FUNCTION F01_TRI_10000 RETURN VARCHAR(30) AS A1 VARCHAR(30); BEGIN SELECT C2 INTO A1 FROM T02_TRI_10000 WHERE C1=1; PRINT A1; RETURN A1; END;
CREATE ROLE R01_TRI_10000;
CREATE SEQUENCE S01_TRI_10000 INCREMENT BY 10;
CREATE TRIGGER TRI02_TRI_10000 AFTER CREATE ON DATABASE BEGIN PRINT 'SUCCESS';END;

SELECT OBJECTTYPE, OBJECTNAME, SCHEMANAME, DATABASENAME, OPUSER FROM T01_TRI_10000;

查询结果如下:

行号   OBJECTTYPE	    OBJECTNAME	   SCHEMANAME	DATABASENAME  OPUSER
-----  -----------   ------------   ------------- ------------  ------
  1 	USER 		L01_TRI_10000 	NULL 		DAMENG 		  SYSDBA
  2 	TABLE 		T03_TRI_10000 	SYSDBA 		DAMENG 		  SYSDBA
  3 	VIEW 		V01_TRI_10000 	SYSDBA 		DAMENG 		  SYSDBA
  4 	INDEX 		I01_TRI_10000 	SYSDBA 		DAMENG 		  SYSDBA
  5 	PROCEDURE 	P01_TRI_10000 	SYSDBA 		DAMENG 		  SYSDBA
  6 	FUNCTION 	F01_TRI_10000 	SYSDBA 		DAMENG 		  SYSDBA
  7 	ROLE 		R01_TRI_10000 	NULL 		DAMENG 		 SYSDBA
  8 	SEQUENCE 	S01_TRI_10000 	SYSDBA 		DAMENG 		  SYSDBA
  9 	TRIGGER 	TRI02_TRI_10000	SYSDBA 		DAMENG 		  SYSDBA

事件触发器(系统触发事件)使用示例如下:

create or replace trigger test_trigger after LOGIN on database begin print'SUCCESS'; end;
//只要一登录,服务器就会打印出SUCCESS

14.1.2.2 事件属性函数用法

当事件触发器被触发时,可以通过这些事件属性函数获取当前事件的属性。

针对用户设置的数据库事件(DDL 语句执行),获取事件触发时的相关属性。事件属性函数如下:

1、DM_DICT_OBJ_NAME,无参数,返回事件对象名;

2、DM_DICT_OBJ_TYPE,无参数,返回事件对象类型;

3、DM_DICT_OBJ_OWNER,无参数,返回事件对象所在模式;

4、DM_SQL_TXT,有 1 个输出参数,参数类型为 DM_NAME_LIST_T,返回值为 DDL 语句占用的嵌套表单元个数。DM_SQL_TXT 帮助用户获取事件被触发时正在执行的 DDL 语句,用于存储获取到的 DDL 语句。DM_NAME_LIST_T 为元素类型为 varchar(64)的嵌套表。因此如果 DDL 语句过长会导致分片存储,用户在获取 DDL 语句的时候,尤其要注意根据返回值来循环读取嵌套表以获取完整的语句。

使用说明

1、系统内部 DDL 将不触发事件触发器

2、MPP 从节点的 DDL 将不触发事件触发器

下面用一个具体的例子来说明事件属性函数如何使用。

CREATE TABLE T_EAF(
	N INT,
	SQLTEXT VARCHAR,
	OBJECTNAME VARCHAR(128),
	OBJECTTYPE VARCHAR(128),
	OBJECTOWNER VARCHAR(128)
);

CREATE OR REPLACE TRIGGER TRIG_EAF_01 BEFORE DDL ON DATABASE
	DECLARE
		N NUMBER;
		STR_STMT VARCHAR;
		SQL_TEXT DM_NAME_LIST_T;
	BEGIN
		N := DM_SQL_TXT(SQL_TEXT); 				//N为占用嵌套表单元个数
		  FOR I IN 1..N
		LOOP
		  STR_STMT := STR_STMT || SQL_TEXT(I);  //STR_STMT为获取的DDL语句
		END LOOP;
		INSERT INTO T_EAF VALUES(N,STR_STMT,DM_DICT_OBJ_NAME, DM_DICT_OBJ_TYPE, DM_DICT_OBJ_OWNER);
END;
/

执行建模式建表语句。

create schema systest;

create table T_systest(c1 int);

然后,可以在 T_EAF 中查询到相关的建模式、建表语句。

SELECT * FROM T_EAF;

查询结果:

N   SQLTEXT                        OBJECTNAME OBJECTTYPE OBJECTOWNER
--- ------------------------------ ---------- ---------- -----------
1  create schema systest;          SYSTEST    SCHEMA     SYSTEST
1  create table T_systest(c1 int); T_SYSTEST  TABLE      SYSTEST

14.1.3 时间触发器

时间触发器属于一种特殊的事件触发器,它使得用户可以定义一些有规律性执行的、定点执行的任务,比如在晚上服务器负荷轻的时候通过时间触发器做一些更新统计信息的操作、自动备份操作等等,因此时间触发器是非常有用的。

语法格式

CREATE [OR REPLACE] TRIGGER [<模式名>.]<触发器名>[WITH ENCRYPTION]
AFTER TIMER ON DATABASE[EXECUTE AT <触发的RAFT组名>] <{FOR ONCE AT DATETIME [<时间表达式>]<exec_ep_seqno>}|{{<month_rate>|<week_rate>|<day_rate>} {<once_in_day>|<times_in_day>}{<during_date>}<exec_ep_seqno>}>  [WHEN <条件表达式>] <触发器体>
<month_rate>:= {FOR EACH <整型变量> MONTH {<day_in_month>}}| {FOR EACH <整型变量> MONTH { <day_in_month_week>}}
<day_in_month>:= DAY <整型变量>
<day_in_month_week>:= {DAY <整型变量> OF WEEK<整型变量>}|{DAY <整型变量> OF WEEK LAST}
<week_rate>:=FOR EACH <整型变量> WEEK {<day_of_week_list>}
<day_of_week_list >:= {<整型变量>}|{, <整型变量>}
<day_rate>: =FOR EACH <整型变量> DAY
<once_in_day >:= AT TIME <时间表达式>
<times_in_day >:={ <duaring_time> } FOR EACH <整型变量> <freq_sub_type>
<freq_sub_type>:= MINUTE | SECOND
<duaring_time>:={NULL}|{FROM TIME <时间表达式>}|{FROM TIME <时间表达式> TO TIME <时间表达式>}
<duaring_date>:={NULL}|{FROM DATETIME <日期时间表达式>}|{FROM DATETIME <日期时间表达式> TO DATETIME <日期时间表达式>}  
<exec_ep_seqno>:=EXECUTE AT <DMDSC节点号>

参数

1.< 模式名 > 指明被创建的触发器的所在的模式名称或触发事件发生的对象所在的模式名,缺省为当前模式;

2.< 触发器名 > 指明被创建的触发器的名称;

3.< 触发的 RAFT 组名 > 专门用于 DMDPC,用于指定在 RAFT 组中的节点触发,不涉及的节点上不触发。和事件触发器的 < 触发的 RAFT 组名 > 用法相同,详细介绍请参考事件触发器 < 触发的 RAFT 组名 >;

4.WHEN 子句 包含一个布尔表达式,当表达式的值为 TRUE 时,执行触发器;否则,跳过该触发器;

5.< 触发器体 > 触发器被触发时执行的 SQL 过程语句块;

6.<exec_ep_seqno> 指定 DMDSC 环境下触发器执行所在的节点号;

7.<freq_sub_type> 指定触发器按分钟间隔或者秒间隔触发。其中,按秒间隔触发需设置 INI 参数 TIMER_TRIG_CHECK_INTERVAL 为 1。

使用说明

1.< 触发器名 > 是触发器的名称,它不能与模式内的其他模式级对象同名,并且不能使用“MTRG$_”作为前缀;

2.时间触发器的最低时间频率精确到秒级,定义很灵活,完全可以实现数据库中的代理功能,只要通过定义一个相应的时间触发器即可。在触发器体中定义要做的工作,可以定义操作的包括执行一段 SQL 语句、执行数据库备份、执行重组 B 树、执行更新统计信息、执行数据迁移(DTS)。

下面的简单例子在屏幕上每隔一分钟输出一行“HELLO WORLD”。

  CREATE OR REPLACE TRIGGER timer2

  AFTER TIMER on database

  for each 1 day for each 1 minute

  BEGIN

		print 'HELLO WORLD';

  END;

/

关闭时间触发器和普通触发器是一样的,这里不再叙述。

14.2 触发器替换

在定义触发器的语法中,“OR REPLACE”选项用于替换一个已存在的同名触发器。当触发器替换是以下情况之一时,DM 会报错“替换触发器属性不一致”。

  1. 表触发器和事件触发器之间的替换;
  2. 表触发器所基于的表或视图发生变化时;
  3. 事件触发器的触发对象名(SCHEMA 或 DATABASE)发生变化时;
  4. 事件触发器的可触发的模式发生变化时;
  5. 事件触发器对应的事件类型发生变化时,事件类型分为以下几类:

DDL:CREATE、ALTER、DROP、GRANT、REVOKE、TRUNCATE 等

AUDIT:AUDIT、NOAUDIT

PRIV:GRANT、REVOKE

LOGIN: LOGIN/LOGON、LOGOUT/LOGOFF

SERVER:SERERR

BACK:BACKUP DATABASE、RESTORE DATABASE

TIMER:TIMER

STARTUP:STARTUP、SHUTDOWN

14.3 设计触发器的原则

在应用中使用触发器功能时,应遵循以下设计原则,以确保程序的正确和高效:

1.如果希望保证一个操作能引起一系列相关动作的执行,请使用触发器;

2.不要用触发器来重复实现 DM 中已有的功能。例如,如果用约束机制能完成希望的完整性检查,就不要使用触发器;

3.避免递归触发。所谓递归触发,就是触发器体内的语句又会使该触发器启动,导致语句的执行无法终止。例如,在表 T1 上创建 BEFORE UPDATE 触发器,而该触发器中又有对表 T1 的 UPDATE 语句;

4.合理地控制触发器的大小和数目。要知道,一旦触发器被创建,任何用户在任何时间执行的相应操作都会导致触发器的执行,这将是一笔不小的开销。

14.4 触发器的删除

当用户需要从数据库中删除一个触发器时,可以使用触发器删除语句。其语法如下:

语法格式

  DROP TRIGGER [IF EXISTS] [<模式名>.]<触发器名>;

参数

1.< 模式名 > 指明被删除触发器所属的模式;

2.< 触发器名 > 指明被删除的触发器的名字。

图例

触发器的删除

触发器的删除

使用说明

  1. 删除不存在的触发器会报错。若指定 IF EXISTS 关键字,删除不存在的触发器,不会报错;
  2. 当触发器的触发表被删除时,表上的触发器将被自动地删除;
  3. 除了 DBA 用户外,其他用户必须是该触发器所属基表的拥有者才能删除触发器。

权限

执行该操作的用户必须是具有 DBA 角色(三权分立)的用户,或该触发器的拥有者(且具有 DROP TRIGGER 权限)。

举例说明

例 1 删除触发器 TRG1。

  DROP TRIGGER TRG1;

例 2 删除模式 SYSDBA 下的触发器 TRG2。

  DROP TRIGGER SYSDBA.TRG2;

14.5 禁止和允许触发器

每个触发器创建成功后都自动处于允许状态(ENABLE),只要基表被修改,触发器就会被触发。但是在某些情况下,例如:

1.触发器体内引用的某个对象暂时不可用;

2.载入大量数据时,希望屏蔽触发器以提高执行速度;

3.重新载入数据。

用户可能希望触发器暂时不被触发,但是又不想删除这个触发器。这时,可将其设置为禁止状态(DISABLE)。

当触发器处于允许状态时,只要执行相应的 DML 语句,且触发条件计算为真,触发器体的代码就会被执行;当触发器处于禁止状态时,则在任何情况下触发器都不会被触发。根据不同的应用需要,用户可以使用触发器修改语句将触发器的状态设置为允许或禁止状态。其语法如下:

语法格式

  ALTER TRIGGER [<模式名>.]<触发器名> <DISABLE | ENABLE>;

参数

1.< 模式名 > 指明被修改的触发器所属的模式;

2.< 触发器名 > 指明被修改的触发器的名字;

3.DISABLE 指明将触发器设置为禁止状态。当触发器处于禁止状态时,在任何情况下触发器都不会被触发;

4.ENABLE 指明将触发器设置为允许状态。当触发器处于允许状态时,只要执行相应的 DML 语句,且触发条件计算为真,触发器就会被触发。

图例

禁止和允许触发器

禁止和允许触发器

权限

执行该操作的用户必须是具有 DBA 角色(三权分立)的用户,或该触发器的拥有者(且具有 ALTER TRIGGER 权限),或具有 ALTER ANY TRIGGER 权限的用户。

14.6 触发器的重编

重新对触发器进行编译,如果重新编译失败,则将触发器置为禁止状态。

重编功能主要用于检验触发器的正确性。

语法格式

  ALTER TRIGGER [<模式名>.]<触发器名> COMPILE [DEBUG];

参数

1.< 模式名 > 指明被重编译的触发器所属的模式;

2.< 触发器名 > 指明被重编译的触发器的名字;

3.[DEBUG] 可忽略。

图例

触发器重编

触发器重编

权限

执行该操作的用户必须是具有 DBA 角色(三权分立)的用户,或该触发器的拥有者(且具有 ALTER TRIGGER 权限),或具有 ALTER ANY TRIGGER 权限的用户。

举例说明

例 重新编译触发器

  ALTER TRIGGER OTHER.TRG_AI_ACCOUNT COMPILE;

14.7 触发器应用举例

正如我们在本章所介绍的,触发器是 DM 系统提供的重要机制。我们可以使用该机制来加强比正常的审计机制、完整性约束机制、安全机制等所能提供的功能更复杂的事务规则。为帮助用户更好地使用该机制,我们提供了一些触发器应用的例子供用户参考。

14.7.1 使用触发器实现审计功能

尽管 DM 系统本身已经提供了审计机制,但是在许多情况下我们还是可以利用触发器完成条件更加复杂的审计。与内置的审计机制相比,采用触发器实现的审计有如下优点:

1.使用触发器可针对更复杂的条件进行审计;

2.使用触发器不仅可以记录操作语句本身的信息,还可以记录被该语句修改的数据的具体值;

3.内置的审计机制将所有审计信息集中存放,而触发器实现的审计可针对不同的操作对象分别存放审计信息,便于分析。

虽然如此,触发器并不能取代内置的审计机制。因为内置审计机制的某些功能触发器是无法做到的。例如:

  1. 内置审计机制可审计的类型更多。触发器只能审计表上的 DML 操作,而内置审计机制可以针对各种操作、对象和用户进行审计;
  2. 触发器只能审计成功的操作,而内置审计机制能审计失败的操作;
  3. 内置审计机制使用起来更简单,并且其正确性更有保障。

用于审计的触发器通常都是 AFTER 类型。关于审计的实例,请参考《DM8 安全管理》。

14.7.2 使用触发器维护数据完整性

触发器与完整性约束机制都可以用于维护数据的完整性,但是二者之间存在着显著的区别。一般情况下,如果使用完整性约束机制可以完成约束检查,我们不建议用户使用触发器。这是因为:

1.完整性约束机制能保证表上所有数据符合约束,即使是约束创建前存在的数据也必须如此;而触发器只保证其创建后的数据满足约束,但之前存在数据的完整性则得不到保证;

2.完整性约束机制使用起来更简单,并且其正确性更有保障。

触发器通常用来实现完整性约束机制无法完成的约束检查和维护,例如:

1.引用完整性维护

删除被引用表中的数据时,级联删除引用表中引用该数据的记录;更新被引用表中的数据时,更新引用表中引用该数据的记录的相应字段。下例中,表 OTHER.DEPTTAB 为被引用表,其主关键字为 Deptno;表 OTHER.EMPTAB 为引用表。其结构如下:

  SET SCHEMA OTHER;

  CREATE OR REPLACE TRIGGER Dept_del_upd_cascade

  AFTER DELETE OR UPDATE ON OTHER.DEPTTAB FOR EACH ROW

  BEGIN

	IF DELETING THEN

	  DELETE FROM OTHER.EMPTAB

		WHERE Deptno = :old.Deptno;

	ELSE

	  UPDATE OTHER.EMPTAB SET Deptno = :new.Deptno

		WHERE Deptno = :old.Deptno;

	END IF;

  END;

  SET SCHEMA SYSDBA;

2.CHECK 规则检查

增加新员工或者调整员工工资时,保证其工资不超过规定的范围,并且涨幅不超过 25%。该例中,表 OTHER.EMPTAB 记录员工信息;表 OTHER.SALGRADE 记录各个工种的工资范围,其结构如下:

  SET SCHEMA OTHER;

  CREATE OR REPLACE TRIGGER Salary_check
  BEFORE INSERT OR UPDATE ON OTHER.EMPTAB
  FOR EACH ROW
  DECLARE
	Minsal  FLOAT;
	Maxsal  FLOAT;
	Salary_out_of_range EXCEPTION FOR -20002;
  BEGIN
  //取该员工所属工种的工资范围
  SELECT Losal, Hisal INTO Minsal, Maxsal FROM OTHER.SALGRADE
  WHERE Job_classification = :new.Job;
  //如果工资超出工资范围,报告异常
  IF (:new.Sal < Minsal OR :new.Sal > Maxsal) THEN
		RAISE Salary_out_of_range;
  END IF;
  //如果工资涨幅超出25%,报告异常
  IF UPDATING AND (:new.Sal - :old.Sal) / :old.Sal > 0.25 THEN
		RAISE Salary_out_of_range;
  END IF;
  END;

  SET SCHEMA SYSDBA;

14.7.3 使用触发器保障数据安全性

在复杂的条件下,可以使用触发器来保障数据的安全性。同样,要注意不要用触发器来实现 DM 安全机制已提供的功能。使用触发器进行安全控制时,应使用语句级 BEFORE 类型的触发器,其优点如下:

1.在执行触发事件之前进行安全检查,可以避免系统在触发语句不能通过安全检查的情况下做不必要的工作;

2.使用语句级触发器,安全检查只需要对每个触发语句进行一次,而不必对语句影响的每一行都执行一次。

下面这个例子显示如何用触发器禁止在非工作时间内修改表 OTHER.EMPTAB 中的工资(Sal)栏。非工作时间包括周末、公司规定的节假日以及下班后的时间。为此,我们用表 OTHER.C OMPANYHOLIDAYS 来记录公司规定的节假日。其结构如下:

  SET SCHEMA OTHER;

  CREATE OR REPLACE TRIGGER Emp_permit_changes
  BEFORE INSERT OR DELETE OR UPDATE
  ON OTHER.EMPTAB
  DECLARE
	Dummy  INTEGER;
	Invalid_Operate_time EXCEPTION FOR -20002;
  BEGIN
  	//检查是否周末
	IF (DAYNAME(Sysdate) = 'Saturday' OR
  	  DAYNAME(Sysdate) = 'Sunday') THEN
  	  RAISE Invalid_Operate_time;
  	END IF;
  	//检查是否节假日
  	SELECT COUNT(*) INTO Dummy FROM OTHER.COMPANYHOLIDAYS
	  WHERE Holiday= Current_date;
	IF dummy > 0 THEN
	  RAISE Invalid_Operate_time;
	END IF;
	//检查是否上班时间
	IF (EXTRACT(HOUR FROM Current_time) < 8 OR
		EXTRACT(HOUR FROM Current_time) >= 18) THEN
		RAISE Invalid_Operate_time;
	END IF;
  END;

  SET SCHEMA SYSDBA;

14.7.4 使用触发器生成字段默认值

触发器还经常用来自动生成某些字段的值,这些字段的值有时依赖于本记录中的其他字段的值,有时是为了避免用户直接对这些字段进行修改。这类触发器应该是元组级 BEFORE INSERT 或 UPDATE 触发器。因为:

1.必须在 INSERT 或 UPDATE 操作执行之前生成字段的值;

2.必须为每条元组自动生成一次字段的值。

  SET SCHEMA OTHER;

  CREATE OR REPLACE TRIGGER Emp_auto_value

  BEFORE INSERT

  ON OTHER.EMPTAB

  FOR EACH ROW

  BEGIN

	:new.Sal = 999.99;

  END;

  SET SCHEMA SYSDBA;
微信扫码
分享文档
扫一扫
联系客服