首页 > 代码库 > ORACLE触发器的管理与实际应用【weber出品】

ORACLE触发器的管理与实际应用【weber出品】

一、INSTEAD OF触发器

  对于简单的视图可以执行INSERT,UPDATE和DELETE操作,但是对于复杂视图,不允许直接执行INSERT,UPDATE,DELETE操作,当视图出现以下任何一种情况时,都不允许直接执行DML操作,如下:

具有集合操作符(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 选项

  现在进行演示instead of 触发器的使用:
  我们现在创建一个表:

SQL> conn /as sysdba已连接。SQL> grant create view to scott;授权成功。SQL> conn scott/tiger已连接。create or replace view dept_emp as select b.deptno,b.dname,a.empno,a.ename from emp a,dept b where a.deptno=b.deptno;视图已创建。

插入数据:

SQL> desc dept_emp 名称                                    是否为空? 类型 ----------------------------------------- -------- ---------------------------- DEPTNO                                    NOT NULL NUMBER(2) DNAME                                              VARCHAR2(14) EMPNO                                     NOT NULL NUMBER(4) ENAME                                              VARCHAR2(10)SQL> insert into dept_view values(11,dname,ename,120);insert into dept_view values(11,dname,ename,120)ORA-01779: 无法修改与非键值保存表对应的列SQL> insert into dept_emp values(50,ADMIN,1223,MARY);insert into dept_emp values(50,ADMIN,1223,MARY)*1 行出现错误:ORA-01779: 无法修改与非键值保存表对应的列


错误原因:这是个包含了两张表的负责视图,不可以进行dml操作。现在进行创建触发器:

create or replace trigger tr_instead_of_dept_emp  instead of insert on dept_emp  for each rowdeclare  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) values (:new.empno, :new.ename);  end if;end;

注意:
这里千万不要在select count(*) from dept_view where deptno=:new.deptno;不能从视图只查找内容
否则会报错:

    SQL> insert into dept_view values(20,dname,ename,7777);insert into dept_view values(20,dname,ename,7777)ORA-00036: 超过递归 SQL 级别的最大值 50ORA-06512: 在 "SCOTT.TR_INSTEAD_DEPT_EMP", line 4ORA-04088: 触发器 SCOTT.TR_INSTEAD_DEPT_EMP 执行过程中出错ORA-06512: 在 "SCOTT.TR_INSTEAD_DEPT_EMP", line 6ORA-04088: 触发器 SCOTT.TR_INSTEAD_DEPT_EMP 执行过程中出错

正确插入的结果:

QL> insert into dept_emp values(50,ADMIN,1223,MARY);已创建 1 行。SQL> commit;提交完成。SQL> select * from emp;EMPNO ENAME  JOB         MGR HIREDATE         SAL       COMM DEPTNO----- ------ --------- ----- -------------- ----- ---------- ------ 7369 SMITH  CLERK      7902 17-12月-80       800                20 7499 ALLEN  SALESMAN   7698 20-2月 -81      1600        300     30 7521 WARD   SALESMAN   7698 22-2月 -81      1250        500     30 7566 JONES  MANAGER    7839 02-4月 -81      2975                20 7654 MARTIN SALESMAN   7698 28-9月 -81      1250       1400     30 7698 BLAKE  MANAGER    7839 01-5月 -81      2850                30 7782 CLARK  MANAGER    7839 09-6月 -81      2450                10 7788 SCOTT  ANALYST    7566 19-4月 -87      4000                20 7839 KING   PRESIDENT       17-11月-81      5000                10 7844 TURNER SALESMAN   7698 08-9月 -81      1500          0     30 7876 ADAMS  CLERK      7788 23-5月 -87      1100                20 7900 JAMES  CLERK      7698 03-12月-81       950                30 7902 FORD   ANALYST    7566 03-12月-81      3000                20 7934 MILLER CLERK      7782 23-1月 -82      1300                10 1223 MARY已选择15行。SQL> select * from dept;DEPTNO DNAME          LOC------ -------------- -------------    10 ACCOUNTING     NEW YORK    20 RESEARCH       DALLAS    30 SALES          CHICAGO    40 OPERATIONS     BOSTON    50 ADMIN

二、数据库事件触发器

   系统事件触发器是指基于oracle系统事件(例如LOGON和STARTUP)所建立的触发器,通过使用系统事件触发器提供了跟踪系统或数据库变化的机制

 下面介绍一些常用的系统事件属性函数,以及建立各种事件触发器的方法

 

1. 系统事件属性函数:

ora_client_ip_address:用于返回客户端的IP地址ora_database_name:用于返回当前数据库名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(cloumn_name in varchar2):用于检测特定列是否被修改ora_is_creating_nested_table:用于检测是否正在建立嵌套表ora_is_drop_column(column_name in varchar2):用于检测特定列是否被删除ora_is_servererror(error_number):用于检测是否返回了特定oracle错误ora_login_user:用于返回登录用户名ora_sysevent:用于返回触发触发器的系统事件名

 

2.建立实例启动和关闭触发器

实例启动触发器和实例关闭触发器只有特权用户才能建立,实例启动触发器只能使用after关键字而实例关闭触发器只能使before关键字,示例如下:

为了跟踪实例启动和关闭事件,可以分别建立示例启动和示例关闭触发器,为了记载实例启动和关闭事件和时间,首先建立事件表event_table,示例如下:

conn scott/tigercreate table event_table(event varchar2(30),time date);

在建立了事件表event_table之后,就可以在触发器中引用该表了,注意,实例启动触发器和实例关闭触发器只有特权用户才能建立,并且实例启动触发器只能使用after关键字,而实例关闭触发器只能使用before关键字,示例如下:

SQL> show userUSER 为 "SCOTT"SQL> create table event_table(event varchar2(100),time date);表已创建。

创建打开数据库触发器:

SQL> conn /as sysdbacreate or replace trigger tr_startupafter startup on databasebegininsert into scott.event_table values(ora_sysevent,sysdate);end;SQL> /触发器已创建

创建关闭数据库触发器:

create or replace trigger tr_shutdownbefore shutdown on databasebegininsert into scott.event_table values(ora_sysevent,sysdate);end;/触发器已创建

  查看数据:

SQL> shutdown immediateDatabase closed.Database dismounted.ORACLE instance shut down.SQL> startupORACLE instance started.Total System Global Area  595591168 bytesFixed Size            1275032 bytesVariable Size          163580776 bytesDatabase Buffers      423624704 bytesRedo Buffers            7110656 bytesDatabase mounted.Database opened.SQL> conn scott/tigerConnected.SQL> select * from event_table;EVENT       TIME---------- ---------STARTUP    04-SEP-14SHUTDOWN   04-SEP-14

 

3.建立登录和退出触发器

为了记录用户登录和退出事件,可以分别建立登录和退出触发器,为了记载登录用户和退出用户的名称时间和IP地址

应该首先建立专门存放登录和退出的信息表LOG_TABLE示例如下:

SQL> conn scott/tiger已连接。SQL> create table log_table(  2  username varchar2(10),  3  logontime date,  4  logofftime date,  5  address varchar2(20));表已创建。

创建登入触发器:

SQL> conn /as sysdba已连接。create or replace trigger tr_on  after logon on databasebegin  insert into scott.log_table    (username, logontime, address)  values    (ora_login_user, sysdate, ora_client_ip_address);end;


触发器已创建

 

  创建退出触发器:

create or replace trigger tr_off  before logoff on databasebegin  insert into scott.log_table    (username, logofftime, address)  values    (ora_login_user, sysdate, ora_client_ip_address); end;触发器已创建

测试触发器:

SQL> select * from scott.log_table;

USERNAME LOGONTIME LOGOFFTIME ADDRESS
---------- -------------- -------------- --------------------
SCOTT 03-9月 -14 192.168.0.5

exit

SQL> select * from scott.log_table;

USERNAME LOGONTIME LOGOFFTIME ADDRESS
---------- -------------- -------------- --------------------
SCOTT 03-9月 -14 192.168.0.5
SCOTT 03-9月 -14

 

4. 建立DDL触发器

为了记录系统所发生的DDL事件(CREATE,ALTER,DROP等),可以建立DDL触发器为了记载DDL时间信息,应该建立专门的表,以便存放DDL事件信息

示例如下:

SQL> conn /as sysdba已连接。create table event_ddl(event varchar2(20),username varchar2(10),owner varchar2(10),objname varchar2(10),objtype varchar2(10),time date);表已创建。

创建记录DDL事件触发器:

create or replace trigger tr_ddlafter ddl on scott.schemabegininsert into event_ddl values(ora_sysevent,     ora_login_user,     ora_dict_obj_owner,     ora_dict_obj_name,     ora_dict_obj_type,     sysdate);end;SQL> /

执行结果:

SQL> select * from event_ddl;未选定行SQL> conn scott/tiger已连接。SQL> drop table e purge;表已删除。SQL> drop table d purge;表已删除。SQL> create table e as select * from emp;表已创建。SQL> conn /as sysdba已连接。SQL> select * from event_ddl;EVENT                USERNAME   OWNER      OBJNAME    OBJTYPE    TIME-------------------- ---------- ---------- ---------- ---------- --------------DROP                 SCOTT      SCOTT      E          TABLE      03-9月 -14DROP                 SCOTT      SCOTT      D          TABLE      03-9月 -14CREATE               SCOTT      SCOTT      E          TABLE      03-9月 -14

三、管理触发器

1.显示触发器

建立触发器时,oracle会将触发器信息写入到数据字典中,通过查询数据字典视图user_triggers可以显示当前用户所包含的所有触发器信息,示例如下:

conn scott/tigerselect trigger_name,status from user_triggerswhere table_name=EMP;

2.禁止触发器

禁止触发器是指使触发器临时失效,当触发器处于enable状态时,如果在表上执行DML操作则就会触发相应的触发器,如果基于insert操作建立了触发器,当使用SQL*loader装载大批

量数据时会触发触发器,为了加快数据装载速度,应该在装载数据之前禁止触发器

方法如下:

conn /as sysdbaalter trigger tr_ddl disable;

3.激活触发器

激活触发器是指使用触发器重新生效,当使用SQL*Loader转载完了数据之后为了使被禁止的触发器生效,应该激活触发器

方法如下:

alter trigger tr_ddl enable;

4.禁止或激活表的所有触发器

如果在表上同时存在多个触发器,那么使用alter table命令可以一次禁止或激活所有触发器,示例如下:

alter table emp disable all triggers;alter table emp enable all triggers;

5.重新编译触发器

当使用alter table命令修改表结构(例如增加列、删除列)时,会使得其触发器转变为invalid状态在这种情况下为了使得触发器继续生效,需要重新编译触发器,示例如下:

alter trigger tr_check_sal compile;

6.删除触发器

drop trigger tr_ddl;

 

四、DML触发器在实际中的应用

为了确保数据库满足特定的商业规则或企业逻辑,可以使用约束,触发器和子程序实现由于约束性能最好,实现最简单,所以首选约束,如果使用约束不能实现特定规则

那么因该选择触发器,如果触发器仍然不能实现特定规则,那么应该选择子程序(过程和函数)DML触发器可以用于实现数据库安全,数据审计,数据完整性,参照完整性

等功能


1. 控制数据安全

create or replace trigger tr_emp_time  before insert or delete or update on empbegin  if to_number(to_char(sysdate, HH24)) not between 9 and 17 then    raise_application_error(-20001, 不能在休息时间更改员工信息);  end if;end;

建立了触发器tr_emp_time之后,只能在9:00-17:00之间在emp表上执行DML操作,如果不在该时间段则会报错

 

2. 实现数据审计

如果要在emp上执行insert,update,delete操作,oracle只会记录sql操作,不会记录数据变化,用dml审计可以记录变化

create or replace trigger tr_sal_change  after update of sal on emp  for each rowdeclare  v_temp int;begin  select count(*)    into v_temp    from audit_emp_change   where name = :old.ename;  if v_temp = 0 then    insert into audit_emp_change    values      (:old.ename, :old.sal, :new.sal, sysdate);  else    update audit_emp_change       set oldsal = :old.sal, newsal = :new.sal, time = sysdate     where name = :old.ename;  end if;end;/

执行结果:

SQL> update emp set sal=sal+100 where ename=ywb;update emp set sal=sal+100 where ename=ywbORA-20001: 不能在员工休息的时间修改信息ORA-06512: 在 "SCOTT.TR_EMP_TIME", line 3ORA-04088: 触发器 SCOTT.TR_EMP_TIME 执行过程中出错

在建立了触发器tr_sal_change之后,当修改雇员工资时,会将每个雇员的工资变化全部写入到审计表audit_emp_change中

 

3. 实现数据完整性

数据的完整性用于确保数据库数据满足特定的商业逻辑或企业规则,数据完整性可以通过触发器和子程序约束来实现,因为约束的实现最简单,性能也好,所以实现数据完整性首选约束

例如为了限制雇员工资不能低于800元,可以选用check约束,示例如下:

alter table emp add constraint ck_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.2 * old.sal)begin  raise_application_error(-20001, 工资只能升不能降,并且不能超过20%);end;

 

4. 参照完整性

QL> create table e as select * from emp;表已创建。SQL> create table d as select * from dept;表已创建。SQL> alter table d add constraint pk_d primary key(deptno);表已更改。SQL> alter table e add constraint fk_e foreign key(deptno) references d on delete cascade;表已更改。SQL> delete d where deptno=10;已删除 1 行。SQL> roll回退已完成。

更新后报错:

SQL> update d set deptno=50 where deptno=10;update d set deptno=50 where deptno=10*1 行出现错误:ORA-02292: 违反完整约束条件 (SCOTT.FK_E) - 已找到子记录

通过触发器来解决级联update

create or replace trigger tr_update_cascade  after update of deptno on d  for each rowbegin  update e set deptno = :new.deptno where deptno = :old.deptno;end;

ORACLE触发器的管理与实际应用【weber出品】