精通Oracle10编程SQL(13)开发触发器

/*
 *开发触发器
 */
--得到日期是周几
select to_char(sysdate+4,'DY','nls_date_language=AMERICAN') from dual;

select to_char(sysdate,'DY','nls_date_language=AMERICAN') from dual;

--建立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 emp set sal=sal*1.1 where deptno=1;

--使用条件谓词
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(-20002,'不能在休息日更新雇员');
       when deleting then
          raise_application_error(-20003,'不能在休息日解雇雇员');
     end case;
  end if;
end;

--建立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_aduit_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中
update emp set sal=2000 where empnoo=7788;
update emp set sal=2000 where empnoo=1111;
select * from audit_table;

select * from emp;

--行触发器
--行触发器是指执行DML操作时,每作用一行就触发一次的触发器
--建立BEFORE行触发器
--下面确保雇员工资不能低于其原有工资为例,说明建立BEFORE行触发器的方法
CREATE OR REPLACE TRIGGER tr_emp_sal
BEFORE update of sal on emp
for each row
begin
   if :new.sal<:old.sal then
      raise_application_error(-20010,'工资只涨不降');
   end if;
end;

--在建立触发器tr_emp_sal之后,如果雇员新工资低于其原有工资,则会提示错误信息。
update emp set sal=80 where empno=7788;

select * from emp;

--建立AFTER行触发器
--下面以审计雇员工资变化为例,说明使用AFTER行触发器的方法
--在建立触发器之前,首先应建立存放审计数据的表audit_emp_change
CREATE TABLE audit_emp_change(
   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_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;

--在建立触发器tr_sal_change之后,当修改雇员工资时,会将每个雇员的工资变化全部写入到审计表audit_empO_change中
update emp set sal=sal*1.1 where deptno=1;

select * from audit_emp_change;

select * from emp;

--限制行触发器
--当使用行触发器时,默认情况下会在每个被作用行上执行一次触发器代码
--为了使得在特定条件下执行行触发器代码,就需要使用WHERE子句对触发条件加以限制
--下面以审计岗位为"manager"的雇员工资变化为例,说明限制行触发器的方法
CREATE OR REPLACE TRIGGER tr_sal_change
AFTER UPDATE OF sal ON emp
for each row
when (upper(old.job)=upper('manager'))
declare
   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;

--当建立触发器tr_sal_change时,因为使用WHERE子句指定了触发条件,所以只有在满足触发条件时才会执行触发器代码
update emp set sal=sal*1.1 where deptno=2;


--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操作时会出错
update emp set sal=6000 where empno=7788;


--使用DML触发器
--为了确保数据库数据满足特定的商业规则或企业逻辑,可以使用约束、触发器和子程序实现。因为约束性能最好,实现最简单,所以首选约束
--如果使用约束不能实现特定规则,那么应该选择触发器
--如果触发器仍然不能实现特定规则,那么应该选择子程序(过程和函数)
--DML触发器可以用于实现数据安全保护、数据审计、数据完整性、参照完整性、数据复制等功能

--控制数据安全
--在服务器级控制数据安全是通过授予和收回对象权限来完成的,如
grant select,insert,update,delete on emp to smith;

--为了实现更复杂的安全模型(例如限制要修改的数据、修改时间等),就需要使用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(-20101,'非工作时间');
   end if;
end;

--建立了触发器tr_emp_time之后,只能在9:00-17:00之间在EMP表上执行DML操作,如果不在该时间段,则会显示错误信息
update emp set sal=3200 where empno=7788;

--实现数据审计
--审计可以用于监视非法和可疑的数据库活动,ORACLE数据库本身提供了审计功能
--例如,如果要对EMP表上的DML操作进行审计,可以执行如下命令
AUDIT INSERT,UPDATE,DELETE ON emp;
--如上所示,在设置了审计选项之后,如果在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_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;

--在建立了触发器tr_sal_change之后,当修改雇员工资时,会将每个雇员的工资变化全部写入到审计表audit_emp_change中
update emp set sal=sal*1.1 where deptno=1;

select * from audit_emp_change;

select * from emp;


--实现数据完整性
--数据完整性用于确保数据库数据满足特定的商业逻辑或企业规则,数据完整性可以使用约束、触发器和子程序实现。因为约束的实现最简单,性能也最好
--所以实现数据完整性首选约束。
--例如,为了限制雇员工资不能低于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(-20931,'工资只升不降,并且升幅不能超过20%');
end;

--在建立了触发器tr_check_sal之后,如果雇员新工资不符合相应规则,则会提示错误信息
update emp set sal=sal*1.25 where empno=7788;

--实现参照完整性
--参照完整性是指若两个表之间具有主从关系(也即主外键关系),当删除主表数据时,必须确保相关的从表数据已经被删除
--当修改主表的主键列数据时,必须确保相关的从表数据已经被修改。
--为了实现级联删除,可以在定义外部键约束时指定ON DELETE CASCADE关键字
--示例如下:
ALTER TABLE emp add constraint fk_deptno
  foreign key(deptno) references dept(deptno)
  on delete cascade;
  
--当用如上方式建立了外部键约束fk_deptno之后,在删除主表DEPT的数据时,会同时删除从表EMP的所有相关数据。
--但使用约束却不能实现级联更新,如果要更新DEPT表的部门号,则会显示错误信息

update dept set deptno=5 where deptno=1;

--原因是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表的相应雇员的部门号
update dept set deptno=5 where deptno=1;

select ename from emp where deptno=5;

select * from dept;
select * from emp;

delete dept where deptno >=7;


--建立INSTEAD OF触发器
--对于简单视图,可以直接执行 INSERT、UPDATE和DELETE操作
--但是对于复杂视图,不允许直接执行INSERT、UPDATE和DELETE操作。
--当视图符合以下任何一种情况时,都不允许直接执行DML操作
--1.具有集合操作符(UNION,UNION ALL,INTERSECT,MINUS)
--2.具有分组函数(MIN,MAX,SUM,AVG,COUNT等)
--3.具有GROUP BY,CONNECT BY或START WITH等子句
--4.具有DISTINCT关建字
--5.具有连接查询
--为了在具有以上情况的复杂视图上执行DML操作,必须要基于视图建立INSTEAD-OF触发器
--在建立了INSTEAD-OF触发器之后,就可以基于复杂视图执行INSERT,UPDATE和DELETE语句。但建立INSTEAD-OF触发器有以下注意事项
--1.INSTEAD OF选项只适用于视图
--2.当基于视图建立触发器时,不能指定BEFORE和AFTER选项
--3.在建立视图时没有指定WITH CHECK OPTION选项
--4.当建立INSTEAD OF触发器时,必须指定FOR EACH ROW选项

--建立复杂视图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操作
select * from dept_emp where deptno=1;

--建立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操作了
insert into dept_emp values(50,'ADMIN','1223','MARY');
insert into dept_emp values(10,'ADMIN','1224','BAKE');

select * from dept_emp;

select * from dept;

select * from emp;


--建立系统事件触发器
--建立例程启动和关闭触发器
create table event_table(event varchar2(30),time date);
--例程启动触发器和例程关闭触发器只有特权用户才能建立,并且例程启动触发器只能使用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命令不会触发该触发器
shutdown

startup

select event,to_char(time,'YYYY/MM/DD HH24:MI') time from event_table;


--建立登录和退出触发器
--首先建立专门存放登录和退出的信息表LOG_TABLE
create table log_table(username varchar2(20),logon_time date,logoff_time date,address varchar2(20));

--注意,登录触发器和退出触发器一定要以特权用户身份建立,并且登录触发器只能使用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,logoff_time,address) values(ora_login_user,sysdate,ora_client_ip_address);
end;

--在建立了触发器tr_logon之后,当用户登录到数据库之后,会执行其触发器代码
--在建立了触发器tr_logoff之后,当用户断开数据库连接之前,会执行其触发器代码
select * from log_table;

--建立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);

--注意,当建立DDL触发器时,必须要使用AFTER关键字
CREATE OR REPLACE TRIGGER tr_ddl
after ddl on haiya1.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之后,如果在haiya1方案对象上执行了DDL操作,则会将该信息记载到表event_ddl中
create table bjtemp(cola int);
drop table bjtemp;

select event,owner,objname from event_ddl;

select * from event_ddl;


--管理触发器
--显示触发器信息
select trigger_name,status from user_triggers where table_name='EMP';

select * from user_triggers where table_name='EMP';

--禁止触发器
alter trigger tr_check_sal disable;

--激活触发器
alter trigger tr_check_sal enable;

--禁止或激活表的所有触发器
--如果在表上同时存在多个触发器,那么使用ALTER TABLE命令可以一次禁止或激活所有触发器
alter table emp disable all triggers;
alter table emp enable all triggers;

--重新编译触发器
--当使用ALTER TABLE命令修改表结构(例如增加列、删除列)时,会使得其触发器转变为INVALID状态。
--在这种情况下,为了使得触发器继续生效,需要重新编译触发器
alter trigger tr_check_sal compile;

--删除触发器
drop  trigger tr_check_sal;

猜你喜欢

转载自bijian1013.iteye.com/blog/2222464