Oracel触发器

 

一、什么是触发器

    触发器类似于函数和过程,它们都是具有声明、执行部分和异常处理部分的PL/SQL块。

    触发器必须在数据库中以独立对象的身份存储,不能定义到包中。

    过程是显式地通过调用而执行的,并且可以传参;与之相反,触发器是在事件发生时隐式地运行的,并且触发器不能接收参数。

    运行触发器的方式叫做激发(firing)触发器。其触发的事件可以是:

对数据库表的DML操作

对视图的操作

可以激发系统事件(如:启动、关闭数据库)……

 

二、触发器的作用

1 、可以用来自动审计数据内容。

2 、在内容发生变更时,自动通知其他程序采取相应的处理。

 

三、触发器的类型

•         DML 触发器

•         替代触发器

•          系统触发器

四、创建触发器的语法

CREATE [OR REPLACE] TRIGGER trigger_name 

{BEFORE | AFTER | INSTEAD OF}

 [FOR EACH ROW]

trigger_body; 

 

其中,trigger_name是触发器的名称,triggering_event说明了激发触发器的事件(也可能包括特殊的表或视图),trigger_body是触发器的代码。referencing_clause用来引用正在处于修改状态下的行中的数据,如果在WHEN子句中指定trigger_condition的话,则首先对该条件求值。触发器主体只有在该条件为真值时才运行。

注意触发器主体不能超过32K。

 

五、DML触发器

(一)分类

1 、根据触发事件划分

insert/update/delete

 

2 、根据触发时间划分

before—— 先触发后执行

after—— 先执行后触发

 

3 、根据触发级别划分

语句级:每个dml语句不论影响了多少行都只触发1次

记录级:针对dml语句影响的每个行都会触发一次

 

Dml 触发器案例一

要求:用户下班时间和周末不能更改emp表

分析思路:

触发事件:对emp表的update、insert、delete操作

触发时间:使用前触发可以避免不必要的回滚所以选择before触发

触发级别:每个语句触发一次就可以

代码描述:当用户对表进行更改操作的时候,判断当前的系统时间,如果是周末或者下班时间就回滚或抛出异常

 

create or replace trigger myti2
  before insert or update or delete on emp
declare 
  i varchar2( 20 );
  j varchar2( 20 );
begin 
  select to_char(sysdate, 'day' ) into i from dual;
  select to_char(sysdate, 'hh24' ) into j from dual;
  if i= ' 星期六' then 
     raise_application_error(- 20082 , ' 不能在非工作时间更改表' ); -- 注意,错误号在20000--20999
  elsif i= ' 星期日' then 
     raise_application_error(- 20082 , ' 不能在非工作时间更改表' );
  elsif j< '08' then 
     raise_application_error(- 20082 , ' 不能在非工作时间更改表' );
  elsif j> '17' then 
     raise_application_error(- 20082 , ' 不能在非工作时间更改表' );
  else 
     null;
  end if;  
end myti2;

insert into emp(empno) values( 9999 ); 


 

说明:Oracle的触发器中不支持回滚操作。

 

Dml 触发器案例二

要求:用户对emp表的sal更改量不能超过10%

分析思路:

触发事件:对emp表的update操作

触发时间:使用前触发可以避免不必要的回滚所以选择before触发

触发级别:因为会影响到多行因此使用行触发,针对被更改的每一行都触发一次

代码描述:当用户对表进行更改操作的时候,根据更改后的sal(使用:new.sal)和更改前的sal(使用:old.sal),判断更改量是否超过10%,如果超出了就抛出异常。

注意:如果要使用:old和:new一定是行触发(for each row)

create or replace trigger myti1
  before update on emp for each row 
begin 
  if abs(:new.sal-:old.sal)/:old.sal > 0.1 then 
    raise_application_error(- 20001 , ' 改动过大' );
  end if;
end; 
  

update emp set sal= 1500 where empno= 7369 ; 


 

Dml 触发器案例三

要求:现在有两个表,商品库存表,商品交易表,如果交易表中插入记录a商品进货10个,那么a商品的库存就增加10个,如果销售10个,a的库存就减少10个,如果请求的数量超出库存,我们就提示出错

分析思路:

触发事件:对商品交易表的insert操作

触发时间:使用前触发可以避免不必要的回滚所以选择before触发

触发级别:因为要使用:old和:new一定是行触发(for each row)

代码描述:根据插入的记录首先判断交易类型,如果是进货就更改商品库存表把相应商品的库存加上进货数量,如果是出货首先判断该商品是否有足够的库存,如果有就更改该库存,如果库存不足就抛出异常。

create table kucun(pid number,num number);

create table churuku(pid number,inorout varchar2( 5 ),n number);

insert into kucun values( 123 , 20 );
insert into kucun values( 321 , 20 );

create or replace trigger mytri3
  before insert on churuku for each row 
declare 
  i number;
begin 
  if :new.inorout= 'out' then 
    select num into i from kucun where pid=:new.pid;
    if i >= :new.n then 
      update kucun set num=num-:new.n where pid=:new.pid;
    else 
      raise_application_error(- 20009 , ' 库存不足' );
    end if;
  else 
    update kucun set num=num+:new.n where pid=:new.pid;
  end if;
end;

insert into churuku values( 123 , 'in' , 10 );

select * from kucun

select * from churuku 


 

六、详解DML触发器

DML 触发器是由对数据库表进行insert、update、delete操作而激发的触发器

DML 触发器可以设定语句、定时和触发级别

DML 触发器类型

类别

说明

语句

insert 、update、delete

定义何种DML语句激发触发器

定时

before 、after

定义触发器是在语句运行前或运行后激发

级别

语句级、行级

如果是行级触发器,该触发器就对由触发语句变更的每一行激发一次。

如果是语句级触发器,则该触发器就在语句之前或之后激发一次。

行级触发器定义语句for each row

 

DML 触发器激发顺序:

1、  语句级、之前级触发器

2、  行级触发器

(1)之前级触发器

(2)执行该语句本身(insert、update、delete)

(3)之后级触发器

3 、语句级、之后级触发器

 

行级触发器的相关标识

行级触发器是按触发语句所处理的行激发的。在触发器内,我们可以访问正在处理中的行的数据。行级访问的两个PL/SQL宿主变量:old、:new

触发语句

:old

:new

Insert

无定义,所有列为null

将插入的值

Update

更新前,行的原始值

将更新的值

delete

行删除前的原始值

无定义,所有列为null

——

具有只读属性,只能读入

可以修改值

例:为students表制做一个标识列

create table students(id number( 5 ),first_name varchar2( 10 ),last_name varchar2( 10 ));

create sequence stu_num

create or replace trigger genstuid
before insert on students for each row 
begin 
   select stu_num.nextval into :new.id from dual;
end;

insert into students(first_name,last_name) values( 'Anni' , 'Scott' );

select * from students 

说明::old和:new记录不能传递到过程和函数中。

为:old和:new指定一个不同的名称

语法:

 Referencing [old as :old_name] [new as :new_name] 


 

例:修改上例的触发器

create or replace trigger genstuid
before insert or update on students
referencing new as new_stu
for each row 
begin 
   select stu_num.nextval into :new_stu.id from dual;
end; 
  


 

触发器谓语:inserting、updating、deleting

表达式

谓语状态

Inserting

如果触发语句是insert的话,则为真(true),否则为假(false)

Updating

如果触发语句是update的话,则为真(true),否则为假(false)

deleting

如果触发语句是delete的话,则为真(true),否则为假(false)

 

例:

-- 学员注册信息表

create table rs_stu(stu_id number( 5 ),dep char( 10 ),course number( 10 ),grade char( 10 ))

-- 学员信息审计表(日志) 
create table stu_audit(change_type char( 1 ) not null, 
                       changed_by varchar2( 8 ) not null,
                       timestamp date not null,
                       old_stu_id number( 5 ),
                       old_dep char( 10 ),
                       old_course number( 10 ),
                       old_grade char( 10 ),
                       new_stu_id number( 5 ),
                       new_dep char( 10 ),
                       new_course number( 10 ),
                       new_grade char( 10 ));

-- 针对增、删、改的审计触发器                        
create or replace trigger logstuchanges
before insert or delete or update on rs_stu for each row 
declare 
v_changetype char( 1 );
begin 
     if inserting then 
        v_changetype:= 'I' ;
     elsif updating then 
        v_changetype:= 'U' ;
     else 
        v_changetype:= 'D' ;
     end if;
     insert into stu_audit(change_type,changed_by,timestamp,
old_stu_id,old_dep,old_course,old_grade,
                       new_stu_id,new_dep,new_course,new_grade)
      values(v_changetype,user,sysdate,
            :old.stu_id,:old.dep,:old.course,:old.grade,
            :new.stu_id,:new.dep,:new.course,:new.grade);
end;

-- 测试 
insert into rs_stu values( 200 , 'B' , 3 , 2 );

update rs_stu set dep= 'C' where stu_id= 200 

delete from rs_stu

select * from rs_stu

select * from stu_audit 


 

七、替代触发器

1 、定义

    DML 触发器是除去执行insert、update或delete操作外,还要被激活运行的触发器,而替代触发器则被激发来代替执行DML语句。

    替代触发器还可以定义在视图上,而DML触发器只能定义在表上。

    替代触发器是行级的。

 

2 、用途

(1)允许对无法变更的视图进行修改;

(2)修改视图中嵌套表的列。

 

3 、可变更的与不可变更的视图

一般来说,视图如果不包括下列命令的就是一个可变更的视图:

l         集合操作(union,union all,minus,intersect)

l         聚合函数(sum,avg,count)

l         Group by

l         Distinct

l         联合查询

 

例:

-- 分组计算每个部门的总销售额的视图

create view myview as
select
deptno,sum(sal) tot from emp group by deptno;

-- 让10部门销售额翻一番
update myview set tot=tot* 2 where deptno= 10 ;

 

说明:这个对视图的更新不会成功,因为该视图用了聚合函数、分组

-- 我们做个替代触发器,来代替上面的更新语句完成任务
create or replace trigger mytri instead of update on myviewfor each row
declare

i number;
begin

select count(*) into i from empwhere deptno=:new.deptno;
   update emp set sal=sal+(:new.tot-:old.tot)/iwhere deptno=:new.deptno;
end;

 

4 、一个复杂的不可更该视图的替代触发器

例:

-- 分组计算每个部门的工资总额的视图

create view v1(deptno,sal) as
  select
deptno,sum(sal) from emp group by deptno;

-- 计算工资的触发器。例如,我们将10部门的工资从6000改为20000,我们就把增长的部分按比例分配给该部门的每个员工。反之如此
create or replace trigger
tu instead of update on v1 for each row
declare

  w number;
  s emp.sal%type;
  cursor cur is select * from empwhere deptno=:new.deptno;
  q number;
  r number;
  temp number;
begin
  s:=:new.sal-:old.sal;  -- 修改的差值
  temp:=s;  -- 修改后的值
  select count(*) into q from empwhere deptno=:new.deptno;
  if s>= 0 then
    for
i in cur
    loop

if cur%rowcount<q then
        w:=i.sal/:old.sal;   -- 先求每个职工的工资百分率
        temp:=temp-abs(w*s); --w*s 是按比例分配更新值
        update emp set sal=sal+w*s where empno=i.empno;
       elsif cur%rowcount = q then
        update
emp set sal=sal+temp where empno=i.empno;
      end if;
    end loop;
  else
    for
i in cur
    loop
      if
cur%rowcount < q then
        w:=i.sal/:old.sal;
        temp:=temp+abs(w*s);
        update emp set sal=sal+w*s where empno=i.empno;
      elsif cur%rowcount = q then
        update
emp set sal=sal+temp where empno=i.empno;
      end if;
    end loop;
  end if;
end;

 

-- 修改上例,四舍五入到整数

create or replace trigger tu instead of update on v1for each row
declare

  w number;
  s emp.sal%type;
  cursor cur is select * from empwhere deptno=:new.deptno;
  q number;
  r number;
  temp number;
begin
  s:=:new.sal-:old.sal;  -- 修改的差值
  temp:=s;  -- 修改后的值
  select count(*) into q from empwhere deptno=:new.deptno;
  if s>= 0 then
    for
i in cur
    loop
      if
cur%rowcount<q then
        w:=i.sal/:old.sal;   -- 先求每个职工的工资百分率
        select round(w*s,0) into r from dual;
        temp:=temp-abs(r);
        update emp set sal=sal+r where empno=i.empno;
      elsif cur%rowcount = q then
        update
emp set sal=sal+temp where empno=i.empno;
      end if;
    end loop;
  else
    for
i in cur
    loop

if cur%rowcount < q then
        w:=i.sal/:old.sal;
        select round(w*s,0) into r from dual;
        temp:=temp+abs(r);
        update emp set sal=sal+r where empno=i.empno;
      elsif cur%rowcount = q then
        update
emp set sal=sal+temp where empno=i.empno;
      end if;
    end loop;
  end if;
end;

 

-- 删除语句触发器
create or replace trigger
tid instead of insert or delete on v1
begin
  dbms_output.put_line( ' 本视图只能进行更新,没有插入和删除功能!' );
end;

 

-- 测试
select
* from emp where deptno= 10 ;
update v1 set sal= 17000 where deptno= 10 ;
select * from emp where deptno= 10 ;
rollback;
delete from v1 where deptno= 10 ;

 

八、删除、禁止触发器

 

1 、删除触发器

语法:

    

Drop trigger 触发器名; 


 

2 、禁止与允许触发器

    与过程和包不同,触发器可以被禁止使用

语法:

    

Alter trigger 触发器名 disable|enable; 


 

3 、禁止与允许整个表的触发器

语法:

    

Alter table 表名 disable|enable all triggers; 



 

你可能感兴趣的:(table,null,application,delete,insert,each)