使用PL/SQL编写触发器

ywzqwwt(25)
Published in
#pl
Words
7518
Reading
34 min
Listen
Play
8y

第八章 使用PL/SQL编写触发器
在本章中我们将学习到如下内容:
 触发器的概念和类型
 如何创建使用各种类型的触发器
 如何管理触发器

简介
数据库触发器是一种PL/SQL命名块,它与我们前面一章所讲解的字程序具有许多相同的特点,是数据库中一种较为复杂的用来强制业务规则、数据完整性和一致性的机制。它存放在数据库中,在特定的事件发生时,可以自动地被数据库执行。触发器可以被用于如下几个方面:
 当表被修改时执行校验。因为数据库触发器将校验逻辑直接与数据库对象联系在一起,这样就保证所需的逻辑总被强制执行。
 自动维护数据库。从Oracle8i开始,我们可以使用数据库启动和关闭触发器自动执行所需的初始化和清理工作。
 用一种细粒度的方式将规则应用于可接受的数据库管理活动。我们可以使用触发器牢固地控制什么类型的行为可以作用于数据库对象,例如删除或修改表。一旦在触发器中放入这些规则,那么想绕过这些规则就相当困难。
可以将触发器代码与三种类型的事件绑定在一起:
 DML语句:DML触发器在对表执行INSERT、UPDATE或DELETE操作时激发。这种触发器可以用于执行校验、设置初始值、审核改变,甚至禁止某种DML操作。
 数据库事件:数据库事件触发器在数据库启动、关闭、用户登录、退出或者Oracle错误发生时,以及执行创建、删除表、索引等DDL语句时激发。从Oracle8i开始,这种触发器提供了一种跟踪数据库活动的方法。
 INSTEAD OF:INSTEAD OF触发器是DML触发器的替代品。它在INSERT、UPDATE或DELETE将要发生时触发,我们在代码中写入替代这些DML操作的代码。INSTEAD OF触发器控制对视图的操作,也不是对表的操作。它可以使不能更新的视图变为可更新,以及覆盖可更新的视图的行为。
在这些触发器类型中,DML触发器对于程序员来说是最常用的,而其它类型的触发器主要被DBA使用。
本章将详细介绍如何用PL/SQL建立DML触发器,其它的触发器类型只做简要介绍。
8.1 DML触发器
DML触发器在对特定的表执行DML操作(INSERT、UPDATE或DELETE)时激发,如图8.1所示。
DML触发器有很多相关的选项。它们可以在一条DML语句执行前或者执行后激发,或在一条DML语句内被处理的每一行之前或者之后激发。它们可以被INSERT、UPDATE或DELETE语句激发,也可以被三者的组合激发。
实际配置DML触发器有很多方法。要决定在当前环境下使用哪种方式,我们需要回答如下问题:
 触发器为整个DML语句激发一次,还是被该语句所涉及的每一行激发一次?
 触发器是在整个语句完成之前或之后激发,还是在每一行被处理之前或之后激发?
 触发器是被INSERT、UPDATE、DELETE还是三者的组合激发?

图8.1 DML触发器在更改数据库表时激发
8.1.1 DML触发器的相关概念
在深入DML触发器的语法和示例之前,我们先大致浏览一下如下DML触发器的概念和相关术语:
1)BEFORE触发器
BEFORE触发器是在某种操作发生之前执行的触发器,例如BEFORE INSERT。
2)AFTER触发器
AFTER触发器是在某种操作发生之后执行的触发器,例如AFTER UPDATE。
3)语句级触发器
语句级触发器是将一条SQL语句做为一个整体来执行。不管该SQL语句会影响到多少行,语句级触发器在该SQL语句运行期间只激发一次。
4)行级触发器
行级触发器是在一条SQL语句影响的每个行上执行一次。例如,假如表books有1000行,那么如下的UPDATE语句会更改1000行:
UPDATE books SET title = UPPER (title);
如果我们在表books上定义了一个行级触发器,那么该触发器就会触发1000次。
5)NEW 伪记录
NEW伪记录是一个名为NEW的数据结构,类似于PL/SQL的记录。这个伪记录只在UPDATE和INSERT DML触发器内可用,它包含了修改发生后被影响的行的值。
6)OLD 伪记录
OLD伪记录是一个名为OLD的数据结构,类似于PL/SQL的记录。这个伪记录只在UPDATE和DELETE DML触发器内可用,它包含了修改发生之前被影响的行的值。
7)WHEN子句
在DML触发器中使用WHEN子句,可以决定触发器中某段代码是否被执行。
8.1.2创建 DML触发器的语法
创建DML触发器的语法如下:
1 CREATE [OR REPLACE] TRIGGER 触发器名
2 {BEFORE | AFTER}
3 {INSERT | DELETE | UPDATE | UPDATE OF 列名列表} ON 表名
4 [FOR EACH ROW]
5 [WHEN (...)]
6 [DECLARE ... ]
7 BEGIN
8 ... 可执行语句 ...
9 [EXCEPTION ... ]
10 END;
下面我们详细说明一下语法中每行所代表的含义:
 第1行:说明指定名称的触发器要被创建。OR REPLACE是可选的。如果触发器已存在,但是没有指定REPLACE,那么会抛出一个ORA-4081错误。
 第2行:指定触发器是在语句或行被处理时之前(BEFORE)还是之后(AFTER)激发。
 第3行:指定触发器应用的DML类型:INSERT、UPDATE或DELETE。注意UPDATE可以指定整个记录还是仅仅是用逗号分隔开的列名列表。列名可以是组合的(用OR分隔),也可以是按顺序指定的。第3行还指定触发器所应用的表。记住:每个DML语句只能应用于一个表。
 第4行:如果指定FOR EACH ROW,那么就是行级触发器,每行触发一次。如果该子句没有指定,那么默认是行级触发器。
 第5行:WHEN子句是可选的,它允许我们指定逻辑来避免触发器的不必要的执行。
 第6行:可选的组成触发器代码的匿名块定义部分。如果不需要定义本地变量,就不需要该关键词。注意不要定义NEW和OLD伪记录,二者是系统自动完成的。
 第7-8行:触发器的执行部分。这部分是必需的,至少要包含一条语句。
 第9行:可选的异常部分,只用来捕获和处理(或试图处理)执行部分抛出的异常。
 第10行:触发器必需的END语句。
8.1.3语句级触发器
语句级触发器是指当执行DML语句时被隐含地执行的触发器。如果在表上针对某种DML操作建立了语句级触发器,那么当执行DML操作时会自动执行级触发器的相应代码。当审计DML操作,或者确定DML操作安全执行时,可以使用语句级触发器。注意,使用语句级触发器时,不能记录列数据的变化。下面通过示例说明如何建立语句级触发器。
1)建立BEFORE语句级触发器
为了确保DML操作在正常情况下执行,可以基于DML操作建立BEFORE语句级触发器。例如,为了禁止工作人员在休息日改变雇员信息,开发人员可以建立BEFORE语句级触发器,以实现数据的安全。代码如下:
CREATE OR REPLACE TRIGGER tr_sec_emp
BEFORE INSERT OR UPDATE OR DELETE
ON emp
BEGIN
IF to_char(SYSDATE,'DY','nls_date_language=AMERICAN')
IN('SAT','SUN') THEN
raise_application_error(-20001,'不能在休息日改变雇员信息!');
END IF;
END;
在建立触发器tr_sec_emp之后,如果在星期六、星期日在EMP表上执行DML操作,则会显示错误信息。
注意:在执行update语句时,请将系统日期改为星期六或周期日才能实现上面的效果。
2)使用条件谓词
当在触发器中同时包含多个触发事件(INSERT、UPDATE、DELETE)时,为了在触发器代码中区分具体的触发事件,可以使用以下三个条件谓词:
 INSERTING:当触发事件是INSERT操作时,该条件谓词返回值为TRUE,否则为FALSE。
 UPDATING:当触发事件是UPDATE操作时,该条件谓词返回值为TRUE,否则为FALSE。
 DELETING:当触发事件是DELETE操作时,该条件谓词返回值为TRUE,否则为FALSE。
下面举例说明在触发器中使用这三个条件谓词的方法,示例如下:
CREATE OR REPLACE TRIGGER tr_sec_emp
BEFORE INSERT OR UPDATE OR DELETE
ON emp
BEGIN
IF to_char(SYSDATE,'DY','nls_date_language=AMERICAN')
IN('SAT','SUN') THEN
CASE
WHEN inserting THEN
raise_application_error(-20001,'不能在休息日增加雇员信息!');
WHEN updating THEN
raise_application_error(-20001,'不能在休息日修改雇员信息!');
WHEN deleting THEN
raise_application_error(-20001,'不能在休息日删除雇员信息!');
END CASE;

END IF;
END;
当建立了触发器tr_sec_emp之后,如果在星期六、星期日在EMP表中操作,则会根据不同的操作显示不同的错误号和错误消息。
3)建立AFTER语句级触发器
为了审计DML操作,或者在DML操作之后执行汇总运算,可以使用AFTER语句级触发器。例如,为了审计EMP表中INSERT、UPDATE和DELETE的操作次数,可以建立AFTER触发器。在建立AFTER触发器之前,首先建立审计表audit_table。示例如下:
CREATE TABLE audit_table
(
name VARCHAR2(20),
ins INT,
upd INT,
del INT,
starttime DATE,
endtime DATE
);
为了审计EMP表上DML操作执行的次数,最早执行时间和最近执行时间,需要建立AFTER语句级触发器。示例代码如下:
CREATE OR REPLACE TRIGGER TR_AUDIT_EMP
AFTER INSERT OR UPDATE OR DELETE ON EMP
DECLARE
v_temp INT;
BEGIN
SELECT COUNT() INTO v_temp FROM audit_table WHERE NAME = 'EMP';
IF v_temp = 0 THEN
INSERT INTO audit_table VALUES ('EMP', 0, 0, 0, SYSDATE, NULL);
END IF;
CASE
WHEN INSERTING THEN
UPDATE audit_table SET ins=ins+1, endtime=SYSDATE
WHERE NAME='EMP';
WHEN UPDATING THEN
UPDATE audit_table SET upd=upd+1, endtime=SYSDATE
WHERE NAME='EMP';
WHEN DELETING THEN
UPDATE audit_table SET del=del+1, endtime=SYSDATE
WHERE NAME='EMP';
END CASE;
END;
/
在建立了触发器TR_AUDIT_EMP之后,在EMP表上执行DML操作,都会将DML操作次数以及时间段记录在审计表audit_table中。
8.1.4行级触发器
行级触发器是指执行DML操作时,每作用一行就触发一次的触发器。审计数据变化时,可以使用行级触发器。下面通过示例说明如何建立行级触发器。
1)建立BEFORE行级触发器
在开发数据库应用时,为了确保数据符合商业逻辑或企业规则,应该使用约束对输入数据加以限制,但某些情况下使用约束可能无法实现复杂的商业逻辑或企业规则,此时可以考试使用BEFORE行级触发器。下面以确保雇员工资不能低于原有工资为例,说明建立BEFORE触发器的方法。示例如下:
CREATE OR REPLACE TRIGGER tr_emp_sal
BEFORE UPDATE
ON emp
FOR EACH ROW
BEGIN
IF :NEW.sal<:OLD.sal THEN
raise_application_error(-20010,'工资不能低于原有工资!');
END IF;
END;
/
在建立触发器tr_emp_sal之后,如果雇员新工资低于原有工资,则会显示错误信息。
2)建立AFTER行级触发器
为了审核DML操作,可以使用触发器或Oracle系统提供的审计功能;而为了审计数据变化,则应该使用AFTER行级触发器。下面以审计雇员工资变化为例,说明使用AFTER行级触发器的方法。在建立触发器之前,首先应建立存放审计数据的表audit_emp_change,示例如下:
CREATE TABLE audit_emp_chage(
name VARCHAR2(10),oldSal NUMBER(6,2),
newSal NUMBER(6,2),time DATE);
为了审计所有雇员的工资变化和雇员工资的更新日期,必须要建立AFTER行级触发器。示例如下:
CREATE OR REPLACE TRIGGER tr_sal_change
AFTER UPDATE OF sal
ON emp
FOR EACH ROW
DECLARE
v_temp INT;
BEGIN
SELECT COUNT(
) INTO v_temp FROM audit_emp_chage WHERE name=:OLD.ename;
IF v_temp=0 THEN
INSERT INTO audit_emp_chage
VALUES(:OLD.ename,:OLD.sal,:NEW.sal,SYSDATE);
ELSE
UPDATE audit_emp_chage
SET oldSal=:OLD.sal,newSal=:NEW.sal,time=SYSDATE
WHERE name=:OLD.ename;
END IF;
END;
/
在建立触发器tr_sal_change之后,当修改雇员工资时,会将每个雇员的工资变化全部写到审计表audit_emp_change中。
3)限制行级触发器
当使用行级触发器,默认情况下会在每个被作用行上执行一次触发器代码。为了使得在特定条件下执行行级触发器代码,就需要使用WHEN子句对触发条件加以限制。下面以审计岗位为“SALESMAN”的雇员工资变化为例,说明限制行级触发器的方法。示例如下:
CREATE OR REPLACE TRIGGER tr_sal_change
AFTER UPDATE OF sal
ON emp
FOR EACH ROW
DECLARE
v_temp INT;
BEGIN
SELECT COUNT() INTO v_temp FROM audit_emp_chage WHERE name=:OLD.ename;
IF v_temp=0 THEN
INSERT INTO audit_emp_chage
VALUES(:OLD.ename,:OLD.sal,:NEW.sal,SYSDATE);
ELSE
UPDATE audit_emp_chage
SET oldSal=:OLD.sal,newSal=:NEW.sal,time=SYSDATE
WHERE name=:OLD.ename;
END IF;
END;
/
当建立触发器tr_sal_change时,因为使用WHEN子句指定了触发条件,所以只有在满足触发条件时才会执行级触发器代码。这样,当修改部门30的雇员工资时,只有部分雇员会审计。
4)DML触发器使用注意事项
当编写DML触发器时,触发器代码不能从触发器所对应的基表中读取数据。例如,如果要基于EMP表建立触发器,那么该触发器的执行代码不能包含对EMP表的查询操作。尽管在建立触发器时不会出现任何错误,但在执行相应触发时会显示错误信息。假定希望雇员工资不能超过当前的最高工资,并使用触发器实例该规则。示例如下:
CREATE OR REPLACE TRIGGER tr_emp_sal
BEFORE UPDATE OF sal
ON emp
FOR EACH ROW
DECLARE
maxSal NUMBER(6,2);
BEGIN
SELECT MAX(sal) INTO maxSal FROM emp;
IF:NEW.sal>maxSal THEN
raise_application_error(-20010,'超出工资上限');
END IF;
END;
/
如上所示,当建立触发器tr_emp_sal时,不会显示任何错误。但因为触发器代码引用了基本表emp,所以在执行UPDATE操作时显示错误消息。
8.1.5 DML触发器的实际应用
为了确保数据库数据满足特定的商业规则或企业逻辑,可以使用约束、触发器和子程序实现。因为约束性能最好,实现最简单,所以首选约束;如果使用约束不能实现特定规则,那么应该选择触发器;如果使用约束不能实现特定规则,那么应该选择触发器;如果触发器仍然不能实现特定规则,那么应该选择子程序(过程或函数)。DML触发器可以用于实现数据安全保护、数据审计、数据完整性、参照完整性、数据复制等功能,下面通过示例阐述如何实现这些功能。
1)控制数据安全
在服务器级控制数据安全是通过授予和回收对象权限来完成的。例如,为了使SMITH用户可以在SCOTT.EMP表上执行DML操作和SELECT操作,必须要为SMITH用户授予相应的对象权限。如下所示:
SQL> CONN SCOTT/TIGER
SQL> GRANT SELECT,INSERT,UPDATE,DELETE ON emp TO SMITH;
当用户具有了以上对象权限之后,就可以随时在EMP表上执行相应的SQL操作。为了实现更复杂的安全模型(例如限制要修改的数据、修改时间),就需要使用DML触发器了。下面以限制用户在正常工作时间(9:00~17:00)改变EMP表数据为例,说明使用DML触发器控制数据安全的方法。示例如下:
CREATE OR REPLACE TRIGGER tr_emp_time
BEFORE INSERT OR UPDATE OR DELETE
ON emp
BEGIN
IF to_char(SYSDATE,'HH24') NOT BETWEEN '9' AND '17' THEN
raise_application_error(-20010,'非工作时间');
END IF;
END;
/
建立了触发器tr_emp_time之后,只能在9:00~17:00之间在EMP表上执行DML操作。如果不在该时间段,则会显示错误信息。
2)实现数据审计
审计可以用于监视非法和可疑的数据库活动。Oracle数据库本身提供了审计功能。例如,要用EMP表上的DML操作进行审计,可以执行如下命令:
SQL> AUDIT INSERT,UPDATE,DELETE ON emp by ACCESS;
如上所示,在设置了审计选项之后,如果在EMP表上执行了INSERT、UPDATE和DELETE操作,Oracle会将关于SQL操作的信息(用户、时间等)写入数据字典中。注意,使用数据库审计只能审计SQL操作,而不会记载数据变化。为了审计SQL操作所引起的数据变化,必须要使用DML触发器。示例如下:
CREATE OR REPLACE TRIGGER tr_sal_change
AFTER UPDATE OF sal
ON emp
FOR EACH ROW
DECLARE
v_temp INT;
BEGIN
SELECT COUNT(
) INTO v_temp FROM audit_emp_chage
WHERE NAME =:OLD.ename;
IF v_temp=0 THEN
INSERT INTO audit_emp_chage
VALUES(:OLD.ename,:OLD.sal,:NEW.sal,SYSDATE);
ELSE
UPDATE audit_emp_chage
SET oldSal=:OLD.sal,newSal=:NEW.sal,TIME=SYSDATE
WHERE NAME=:OLD.ename;
END IF;
END;
/
在建立了触发器tr_sal_change之后,当修改雇员工资时,会将每个雇员的工资变化全部写入到审计到audit_emp_chage中。
4)实现数据完整性
数据完整性用于确保数据库数据满足特定商业逻辑或企业规则,数据完整性可以使用约束、触发器和子程序实现。因为约束的实现最简单,性能也最好,所以实现数据完整性首选约束。例如,为了限制雇员工资不能低于800元,可以选用CHECK约束。示例如下:
SQL> ALTER TABLE EMP ADD CONSTRAINT ch_sal check(sal>=800);
但某些情况下使用约束无法实现特定的商业规则,此时可以使用触发器来实现数据完整性。例如,假定希望雇员的新工资不能低于其原工资,但也不能高出原工资的20%,使用约束显然无法完成该规则,但通过触发器却可以实现该项规则。示例如下:
CREATE OR REPLACE TRIGGER tr_check_sal
BEFORE UPDATE OF sal
ON emp
FOR EACH ROW
WHEN (NEW.sal<OLD.sal OR NEW.sal>1.2OLD.sal)
BEGIN
raise_application_error(-20931,'工资只升不降,但是升幅不能超过20%');
END;
在建立了触发器tr_check_sal之后,如果雇员新工资不符合相应规则,则会提示错误信息。
5)实现参照完整性
参照完整性是指若两个表之间具有主从关系(也即主外键关系),当删除主表数据时,必须确保相关的从表数据已经被删除;当修改主表的主键列数据时,必须确保相关从表已经被修改。为了实现级联删除,可以在定义外部键约束时指定ON DELETE CASCADE关键字。示例如下:
SQL> ALTER TABLE emp ADD CONSTRAINT fk_deptno
FOREIGN KEY(deptno) REFERENCES dept(deptno)
ON DELETE CASCADE;
当用如上方式建立外键约束fk_deptno之后,在删除主表DEPT数据时,会同时删除从表DEPT的所有相关数据。但使用约束却不能实现级联更新,如果要更新DEPT表的部门号,则会显示错误信息。错误原因是EMP表包含有该部门的相应雇员。为了实现级联更新,可以更新触发器。示例如下:
CREATE OR REPLACE TRIGGER tr_update_cascade
AFTER UPDATE OF deptno
ON dept
FOR EACH ROW
BEGIN
UPDATE emp SET deptno=:NEW.deptno
WHERE deptno=:OLD.deptno;
END;
/
在建立了触发器tr_update_cascade之后,当更新DEPT表的部门号时,会级联更新EMP表的相应雇员的部门号。
8.2 INSTEAD OF触发器
对于简单视图,可以直接INSERT、UPDATE和DELETE操作。但是对于符合如下任何一种情况的复杂视图,不允许直接执行INSERT、UPDATE和DELETE操作:
 视图中含有集合操作符(UNION,UNION ALL,INTERSECT,MINUS);
 视图中含有聚合函数(MIN,MAX,SUM,AVG,COUNT等);
 视图中含有GROUP BY,CONNECT BY或START WITH等子句;
 视图中含有DISTINCT关键字;
 视图中含有联接查询。
为了在具有以上情况的复杂视图上执行DML操作,必须要基于视图建立INSTEAD OF触发器。在建立了INSTEAD OF触发器之后,就可以基于复杂视图执行INSERT、UPDATE和DELETE语句。但建立INSTEAD OF触发器有以下注意事项:
 INSTEAD OF选项只适用于视图;
 当基于视图建立触发器时,不能指定BEFORE和AFTER选项;
 在建立视图时没有指定WITH CHECK OPTION选项;
 当建立INSTEAD OF触发器时,必须指定FOR EACH ROW选项。
1)建立复杂视图dept_emp
视图是逻辑表,本身没有任何数据。视图只是对应于一条SELECT语句,当查询视图时,其数据实际是从视图基表上取得。为了简化部门及其雇员信息的查询,应建立复杂视图dept_emp。示例如下:
CREATE OR REPLACE VIEW dept_emp AS
SELECT a.deptno,a.dname,b.empno,b.ename
FROM dept a,emp b
WHERE a.deptno=b.deptno
当执行以上语句建立了复杂视图dept_emp之后,直接查询视图dept_emp会显示部门及其雇员信息,但不允许执行DML操作。
2)建立INSTEAD OF触发器
为了在复杂视图上执行DML操作,必须要基于复杂视图来建立INSTEAD OF触发器。下面以复杂视图dept_emp上执行INSERT操作为例,说明建立INSTEAD OF触发器的方法。示例如下:
CREATE OR REPLACE TRIGGER tr_instead_of_dept_emp
INSTEAD OF INSERT ON dept_emp
FOR EACH ROW
DECLARE
v_temp INT;
BEGIN
SELECT COUNT(
) INTO v_temp FROM dept
WHERE deptno=:NEW.deptno;
IF v_temp=0 THEN
INSERT INTO dept(deptno,dname)
VALUES(:NEW.deptno,:NEW.dname);
END IF;

SELECT COUNT(*) INTO v_temp FROM emp
WHERE empno=:new.empno;

IF v_temp=0 THEN
INSERT INTO emp(empno,ename,deptno)
VALUES(:NEW.empno,:NEW.ename,:NEW.deptno);
END IF;
END;
/
当建立了INSTEAD OF触发器tr_instead_of_dept_emp之后,就可以在复杂视图dept_emp上执行INSERT操作了。示例如下:
SQL> INSERT INTO dept_emp VALUES(50,'ADMIN','1223','MARY');
SQL> INSERT INTO dept_emp VALUES(10,'ADMIN','1223','MARY');
执行了以上两条INSERT语句之后,就为DEPT表插入了一条数据,为EMP表插入了两条数据,想想其原因。
8.3系统事件触发器
系统事件触发器是指基于Oracle系统事件(例如LOGON和STARTUP)所建立的触发器。通过使用系统事件触发器,提供了跟踪系统或数据库变化的机制。下面介绍一些常用的系统事件属性函数,以及建立各种事件触发器的方法。
1)常用事件属性函数
建立系统事件触发器时,应用开发人员经常需要使用事件属性函数。常用的事件属性函数如下:
 ora_client_ip_address:用于返回客户端的IP地址
 ora_database_name:用于返回当前数据库名
 ora_des_encrypted_password:用于返回DES加密后的用户口令
 ora_dict_obj_name:用于返回DDL操作所对应的数据库对象名。
 ora_dict_obj_name_list(name_list OUT ora_name_list_t):用于返回在事件中被修改的对象名列表。
 ora_dict_obj_owner:用于返回DDL操作所对应的对象的所有者名。
 ora_dict_obj_owner_list(owner_list OUT ora_name_list_t):用于返回在事件中被修改的所有者列表。
 ora_dict_obj_type:用于返回DDL操作所对应的数据库对象的类型。
 ora_grantee(user_list OUT ora_name_list_t):用于返回授权事件的授权者。
 ora_instance_num:用于返回例程号。
 ora_is_alter_column(column_name IN VARCHAR2):用于检测特定列是否被修改。
 ora_is_creating_nested_table:用于检测是否正在建立嵌套表。
 ora_is_drop_column(column_name IN VARCHAR2):用于检测特定列是否被删除。
 ora_is_servererror:用于检测是否返回了特定Oracle错误。
 ora_login_user:用于返回登录用户名。
 ora_system:用于返回触发触发器的系统事件名。
2)建立例程启动和关闭触发器
为了跟踪例程启动和关闭事件,可以分别建立例程启动触发器和例程关闭触发器。为了记载例程启动和关闭的事件和时间,首选要建立事件表event_table。示例如下:
SQL> conn sys/oracle as sysdba
SQL> CREATE TABLE event_table(event VARCHAR2(30),TIME DATE);
Table created
在建立了事件表event_table之后,就可以在触发器中引用该表了。注意,例程启动触发器和关闭触发器只有特权用户才能建立,并且例程启动触发器只能使用AFTER关键字,而例程关闭触发器只能使用BEFORE关键字。示例如下:
CREATE OR REPLACE TRIGGER tr_startup
AFTER STARTUP
ON DATABASE
BEGIN
INSERT INTO event_table VALUES(ora_sysevent,SYSDATE);
END;
/

CREATE OR REPLACE TRIGGER tr_shutdown
BEFORE SHUTDOWN
ON DATABASE
BEGIN
INSERT INTO event_table VALUES(ora_sysevent,SYSDATE);
END;
/
在建立了触发器tr_startup之后,当打开数据库之后,会执行该触发器的相应代码;在建立了触发器tr_shutdown之后,当关闭例程之前,会执行该触发器的相应代码,但是SHUTDOWN ABORT命令不会触发该触发器。示例如下:
SQL>SHUTDOWN
SQL>STARTUP
SQL>SELECT event, to_char(time, 'YYYY/MM/DD HH24:MI') time FROM event_table;
EVENT TIME


SHUTDOWN 2008/10/2 09:30
STARTUP 2008/10/2 09:31
3)建立登录和退出触发器
为了记载用户和退出事件,可以分别建立登录和退出触发器。为了记载登录用户和退出用户的名称、时间和IP地址,应该首选专门存放登录和退出的信息表LOG_TABLE。示例如下:
SQL> conn sys/oracle as sysdba
SQL> CREATE TABLE log_table(
2 username VARCHAR2(20),logon_time DATE,
3 logoff_time DATE,address VARCHAR2(20));
在建立了log_table表之后,就可以在触发器中引用该表了。注意,登录触发器和退出触发器一定要以特权用户身份建立,并且登录触发器只能用AFTER关键字,而退出触发器只能使用BEFORE关键字。示例如下:
CREATE OR REPLACE TRIGGER tr_logon
AFTER LOGON
ON DATABASE
BEGIN
INSERT INTO log_table (username,logon_time,address)
VALUES(ora_login_user,SYSDATE,ora_client_ip_address);
END;
/
CREATE OR REPLACE TRIGGER tr_logoff
BEFORE LOGOFF
ON DATABASE
BEGIN
INSERT INTO log_table (username,logon_time,address)
VALUES(ora_login_user,SYSDATE,ora_client_ip_address);
END;
/
在建立了触发器tr_logon之后,当用户登录数据库之后,会执行其触发器代码;在建立了触发器tr_logoff之后,当用户断开数据库连接之前,会执行级触发器代码。示例如下:
SQL> conn scott/tiger@orcl
SQL> conn system/manager@orcl
SQL> conn sys/oracle@orcl as sysdba
SQL> select * from log_table;

USERNAME LOGON_TIME LOGOFF_TIME ADDRESS


SYS 2008-12-7 9
SCOTT 2008-12-7 9
SCOTT 2008-12-7 9
SCOTT 2008-12-7 9
SCOTT 2008-12-7 9
SYS 2008-12-7 9
SYS 2008-12-7 9
4)建立DDL触发器
为了记载系统所发生的DDL事件(CREATE、ALTER、DROP等),可以建立DDL触发器。为了记载DDL事件信息,应该建立专门的表,以便存放DDL事件信息。示例如下:
create table event_ddl(
event VARCHAR2(20),username VARCHAR2(10),
owner VARCHAR2(10),objname VARCHAR2(20),
objtype VARCHAR2(10),time DATE);
在建立了表event_ddl之后,就可以在触发器中引用该表了。为了记载DDL事件,应该建立DDL触发器。注意,当建立触发器时,必须要使用AFTER关键字。示例如下:
CREATE OR REPLACE TRIGGER tr_ddl
AFTER DDL
ON scott.SCHEMA
BEGIN
INSERT INTO event_ddl
VALUES(ora_sysevent,ora_login_user,ora_dict_obj_owner,
ora_dict_obj_name,ora_dict_obj_type,SYSDATE );
END;
/
在建立了触发器tr_ddl之后,如果在SCOTT方案对象上执行DDL操作,则会该信息记载到表event_ddl中。示例如下:
SQL> conn scott/tiger
SQL> create table temp(cola int);
SQL> drop table temp;
SQL> select * from event_ddl;
select * from event_ddl
ORA-00942: 表或视图不存在

SQL> conn sys/oracle as sysdba
SQL> select * from event_ddl;
EVENT USERNAME OWNER OBJNAME OBJTYPE TIME


CREATE SCOTT SCOTT TEMP TABLE 2008-12-7 9:30
DROP SCOTT SCOTT TEMP TABLE 2008-12-7 9:30
8.4管理触发器
触发器建立完毕后,我们可以对数据库中的触发器对象进行管理。
1)显示触发器
建立触发器时,Oracle会将触发器信息写入到数据字典中,通过查询数据字典视图USER_TRIGGERS,可以显示当前用户的所有触发器信息。例如,如下代码可以返回EMP表相关的所有触发器:
SELECT trigger_name, status FROM user_triggers WHERE table_name='EMP';
2)禁止触发器
禁止触发器是指触发器临时失效。当触发器处理ENABLE状态,如果在表上执行DML操作,则会触发相应的触发器。如果基于INSERT操作建立了触发器,当使用SQLLoader装载大批量数据时会触发触发器。为了加快数据装载速度,应该在装载数据之前禁止触发器。方法如下:
ALTER TRIGGER tr_check_sal DISABLE;
3)激活触发器
激活触发器是指让触发器重新生效。当使用SQL
Loader装载了数据之后,为了被禁止的触发器生效,应该激活触发器。方法如下:
ALTER TRIGGER tr_check_sal ENABLE;
4)禁止或激活表的所有触发器
ALTER TABLE emp DISABLE ALL TRIGGERS; --禁止所有触发器
ALTER TABLE emp ENABLE ALL TRIGGERS; --激活所有触发器
5)重新编译触发器
当使用ALTER TABLE命令修改表结构(例如增加列,删除列)时,会使得其触发器转变为INVALID状态。在这种情况下,为了使得触发器继续生效,需要重新编译触发器。示例如下:
ALTER TABLE tr_check_sal COMPLIE;
6)删除触发器
当触发器不再需要时,可以使用DROP TRIGGER命令删除触发器。注意,在表上的触发器越多,对于DML操作的性能影响也就越大,所以一定要适度使用触发器。删除触发器示例如下:
DROP TRIGGER tr_check_sal;
8.5触发器与存储过程
研究前面的触发器代码,我们会看到触发器和存储过程很类似。实际上,触发器就是一种特殊类型的存储过程。只不过,触发器时不能由用户调用,它是在事件发生时自动执行的,而存储过程需要我们显式地调用;触发器不能带有输入、输出参数和返回值,而存储过程可以有输入、输出参数。
总结
 数据库触发器是一种PL/SQL命名块,是数据库中一种较为复杂的用来强制业务规则、数据完整性和一致性的机制。它存放在数据库中,在特定的事件发生时,可以自动地被数据库执行。
 触发器分为DML触发器、系统事件触发器和INSTEAD OF触发器三种类型。
 DML触发器在对特定的表执行DML操作(INSERT、UPDATE或DELETE)时激发。
 语句级触发器是指当执行DML语句时被隐含地执行的触发器。
 行级触发器是指执行DML操作时,每作用一行就触发一次的触发器。
 BEFORE触发器是在某种操作发生之前执行的触发器。AFTER触发器是在某种操作发生之后执行的触发器。
 Instead of触发器在行级触发,触发时代替insert/update/delete操作。
 系统事件触发器是指基于Oracle系统事件(例如LOGON和STARTUP)所建立的触发器。通过使用系统事件触发器,提供了跟踪系统或数据库变化的机制。

使用PL/SQL编写触发器 | Ecency