Oracle 触发器(一)

1)触发器是一种特殊的存储过程,触发器一般由事件触发并且不能接受参数,存储器由语句块去调用;触发器是当某个事件发生时自动地隐式运行。

2)触发器分类:

  1.dml触发器: 创建在表上,由dml事件引发

  2.替代触发器(insteadof):由于视图有可能是由多个表进行关联而成,直接通过更新视图来更新基表是不可行的,那我们就可以通过替代触发器来工作了。

  3.ddl触发器: 触发事件时数据库对象的创建和修改

  4.数据库事件触发器:定义在数据库或者模式上,由数据库事件触发

      或者如下分类:

  1.行触发器:数据库表中的每一行有变化都会触发一次触发器代码

  2.语句触发器:与语句所影响的行数无关,仅触发一次

  3.before触发器:在dml语句执行之前触发

  4.alfter触发器:在dml语句执行之后触发

3)组成:  

  1.触发事件:引发触发器被触发的事件 dml语句(insert, update, delete语句对表或视图执行数据处理操作)、ddl语句(如create、alter、drop语句在数据库中创建、修改、删除模式对象)、数据库系统事件(如系统启动或退出、异常错误)、用户事件(如登录或退出数据库)。

  2.触发时间:即该触发器是在触发事件发生之前(before)还是之后(after)触发

  3.触发操作:触发器触发后要完成的事情

  4.触发对象:包括表、视图、模式、数据库。只有在这些对象上发生了符合触发条件的触发事件,触发器才会执行触发操作。

  5.触发条件:由when子句指定一个逻辑表达式。只有当该表达式的值为true时,遇到触发事件才会自动执行触发操作。

  6.触发频率:说明触发器内定义的动作被执行的次数。即语句级(statement)触发器和行级(row)触发器。(比如delete多条数据时,行级触发器可能会执行多次,语句级触发器只会触发一次)  

4)、注意:

1、触发器不能接收参数

2、一张表最多可以有12个触发器

before insert
before insert for each row
after insert
after insert for each row
 
before update
before update for each row
after update
after update for each row
 
before delete
before delete for each row
after delete
after delete for each row

 

3、触发器最大为32k

4、触发器中不能使用数据库事务控制语句,并且由触发器所调用的过程或函数也不能使用数据库事务控制语句,如commit、rollback

5、触发器中不能使用long

 二、语法说明

1、dml触发器:dml触发器指的是在对表进行增删改操作引发的自动执行事件。

--dml触发器基本定义:
  create  [or replace] trigger  [user.] trigger
  {before|after}  {insert | delete | update [of column [, column …]]}
  [or {insert | delete | update [of column [, column …]]}...] on
[user.]table   [for each row]   [when (condition)]   begin     .................   end;

其中:

  before 和after指出触发器的触发时序分别为前触发和后触发方式。

  for each row选项说明触发器为行触发器。

  在行触发器的pl/sql块和when 子句中可以使用相关名称参照当前的新、旧列值,默认的相关名称分别为old和new。触发器的pl/sql块中应用相关名称时,必须在它们之前加冒号(:),但在when子句中则不能加冒号。   when 子句说明触发约束条件。

 demo1:

记录每条记录的插入时间(10g后可以在建表时用rowdependencies实现等级追踪实现效果同样)

--先创建日志记录表 
create table sz_record_test ("szid" varchar2(1024) not null, "szversion" varchar2(1024), "event_time" date default sysdate not null); --定义event_time的默认值是sysdate; / --创建触发器 create or replace trigger trg_szd --触发器名称 after insert on tt_szd --在tt_szd表插入表之后出发 for each row --行级触发器 begin insert into sz_record_test(szid,szversion) values (:new.szd_id, :new.version); end;

demo2:

限制对departments表修改(包括insert,delete,update)的时间范围,即不允许在非工作时间修改departments表

 

 

create or replace trigger tr_dept_time
 before insert or delete or update on departments --在插入,删除,修改数据时出发
begin 
  if (to_char(sysdate,'day') in ('星期六', '星期日')) or (to_char(sysdate, 'hh24:mi') not between '08:30' and '18:00') then   
      raise_application_error(-20001, '不是上班时间,不能修改departments表'); 
  end if;
end; 
/*可能不是很多人知道 raise_application_error 的用途是什么,虽然从字面上已经猜到这个函数是干什么用的。平时用来测试的异常处理
我们都是通过dbms_output.put_line来输出异常信息,但是在实际的应用中,需要把异常信息返回给调用的客户端。
其实 raise_application_error 是将应用程序专有的错误从服务器端转达到客户端应用程序(其他机器上的sqlplus或者其他前台开发语言)
raise_application_error 的声明:
procedure raise_application_error( error_number_in in number, error_msg_in in varchar2);
里面的错误代码和内容,都是自定义的。说明是自定义,当然就不是系统中已经命名存在的错误类别,是属于一种自定义事务错误类型,才调用此函数。
error_number_in 之容许从 -20000 到 -20999 之间,这样就不会与 oracle 的任何错误代码发生冲突。error_msg_in 的长度不能超过 2k,否则截取 2k。*/

 demo3:

 限定只对部门号为80的记录进行行触发器操作。

create or replace trigger tr_emp_sal_comm 
before update of salary,
 commission_pct or delete on hr.employees
 for each row 
 when (old.department_id = 80)
 begin 
    case when updating ('salary') then 
        if :new.salary < :old.salary then 
            raise_application_error(-20001, '部门80的人员的工资不能降'); 
        end if; 
        when updating ('commission_pct') then 
        if :new.commission_pct < :old.commission_pct then
raise_application_error(-20002, '部门80的人员的奖金不能降');    end if;       when deleting then      raise_application_error(-20003, '不能删除部门80的人员记录');   end case; end;

 2、替代触发器(insteadof):由于视图有可能是由多个表进行关联而成,直接通过更新视图来更新基表是不可行的,那我们就可以通过替代触发器来工作了。

 demo4:

--准备操作
insert into dept(deptno,dname,loc) values(60,'研发部','上海');
/
insert into emp(empno, ename,job,
                mgr,hiredate,sal,
                comm,deptno)
              values(1001,'贾宝玉','程序员',7788,to_date ('2013-02-03','yyyy-mm-dd'),8000,1000,60);
/             
create view emp_info_view
 as 
 select e.empno,e.ename,e.job,e.sal,d.dname,d.loc
 from emp e join dept d on e.deptno=d.deptno ;
 /
 select * from emp_info_view;
 
--创建替代触发器
create or replace trigger emp_info_view_trigger 
instead of update on emp_info_view for each row begin update dept set dname=:new.dname,loc=:new.loc where dname=:new.dname; end; --更新视图
/ update emp_info_view set loc='杭州' where dname='研发部' --查询结果
/ select * from dept;
create or replace view emp_view as 
select deptno, count(*) total_employeer, sum(sal) total_salary 
from emp group by deptno;
/
在此视图中直接删除是非法:
sql>delete from emp_view where deptno=10;
delete from emp_view where deptno=10
*
error 位于第 1 行:
ora-01732: 此视图的数据操纵操作非法

--但是我们可以创建instead_of触发器来为 delete 操作执行所需的处理,即删除emp表中所有基准行:
/
create or replace trigger emp_view_delete
instead of delete on emp_view for each row
begin
delete from emp where deptno= :old.deptno;
end emp_view_delete;
/
delete from emp_view where deptno=10;

 3、ddl触发器

ddl触发器,当执行ddl语句时会被触发。按照作用范围,分为schema triggers,database triggers。schema triggers作用在一个用户上,database triggers作用在整个数据库所有用户上。

创建ddl触发器

要创建一个ddl触发器,语法如下:

create [or replace] trigger trigger name                         --创建一个触发器并制定名称,or replace是可选项 
 {before | after } { ddl event} on {database | schema}     --指定触发器是在ddl事件之前、之后触发。范围是on database、on schema 
 [when (...)]                                                                               --可选的when子句,使用逻辑判断来避免触发器无意义的执行 
 declare                                                                                  --触发器具体内容4-7
 variable declarations 
begin 
 ...some code... 
end;
examples:
sql> create or replace trigger hr.testtrigger 
 after create on schema  -- on schema 作用范围只是在hr用户下create table等触发,其他用户则不会。若是on database则其他用户create table时会触发该触发器 
 begin 
   -- 以下使用的是事件属性 
 dbms_output.put_line('i believe you have created a ' || 
 ora_dict_obj_type || ' called ' || 
 ora_dict_obj_name); 
 end; 
 / 
trigger created.

 

可用事件

                                            可用的ddl事件

ddl事件 触发时机
alter 对数据库中的任何一个对象使用sql的alter命令时触发
analyze 对数据库中的任何一个对象使用sql的analyze命令时触发
associate statistics 统计数据关联到数据库对象时触发
audit 通过sql的audit命令打开审计时触发
comment 对数据库对象做注释时触发
create 通过sql的create命令创建数据库对象时触发
ddl 列表中所用的事件都会触发
disassociate statistics 去掉统计数据和数据库对象的关联时触发
drop 通过sql的drop命令删除数据库对象时触发
grant 通过sql的grant命令赋权时触发
noaudit 通过sql的noaudit关闭审计时触发
rename 通过sql的rename命令对对象重命名时触发
revoke 通过sql的revoke语句撤销授权时触发
truncate 通过sql的truncate语句截断表时触发

可用属性

oracle 提供了一系列的函数用来提供关于什么触发了ddl触发器以及触发器的状态灯信息。上面那个触发器的例子就使用了属性。

                                                       ddl触发器事件以及属性函数

函数名 返回值
ora_client_ip_address 客户端ip地址
ora_database_name 数据库名称
ora_des_encrypted_password 当前用户的des算法加密后的密码
ora_dict_obj_name 触发ddl的数据库对象名称
ora_dict_obj_name_list 受影响的对象数量和名称列表
ora_dict_obj_owner 触发ddl的数据库对象属主
ora_dict_obj_owner_list 受影响的对象数量和名称列表
ora_dict_obj_type 触发ddl的数据库对象类型
ora_grantee 被授权人数量
ora_instance_num 数据库实例数量
ora_is_alter_column 如果操作的参数column_name指定的列,返回true,否则false
ora_is_creating_nested_table 如果正在创建一个嵌套表则返回true,否则false
ora_is_drop_column 如果删除的参数column_name指定的列,返回true,否则false
ora_login_user 触发器所在的用户名
ora_partition_pos sql命令中可以正确添加分区子句位置
ora_privilege_list 授予或者回收的权限的数量。
ora_revokee 被回收者的数量
ora_sql_txt 触发了触发器的sql语句的行数。
ora_sysevent 导致ddl触发器被触发的时间
ora_with_grant_option 如果授权带有grant选项,返回true。否则false

更多属性函数请参考官方文档pl/sql language reference -> triggers and oracle database data transfer utilities

使用事件和属性

examples:

–创建数据库对象时发出警告,删除数据库对象时阻止

create or replace trigger hr.no_drop 
before ddl on database 
begin 
if ora_sysevent = 'create' 
then 
dbms_output.put_line('warning !!! you have created a '|| 
                        ora_dict_obj_type ||' called '|| 
                        ora_dict_obj_name|| '; username(creater):'|| 
                        ora_dict_obj_owner||'; ip:'|| 
                        ora_client_ip_address||'; event:'|| 
                        ora_sysevent);                        
elsif ora_sysevent = 'drop' 
then 
raise_application_error (-20000, 
'cannot create the ' || ora_dict_obj_type || 
' named ' || ora_dict_obj_name || 
' as requested by ' || ora_dict_obj_owner); 
end if; 
end;

 

–操作了数据库表的哪一列

create or replace trigger preserve_app_cols 
   after alter on schema 
declare 
   -- cursor to get columns in a table 
   cursor curs_get_columns (cp_owner varchar2, cp_table varchar2) 
   is 
      select column_name 
        from all_tab_columns 
       where owner = cp_owner and table_name = cp_table; 
begin 
   -- if it was a table that was altered... 
   if ora_dict_obj_type = 'table' 
   then 
      -- for every column in the table... 
      for v_column_rec in curs_get_columns ( 
                             ora_dict_obj_owner, 
                             ora_dict_obj_name 
                          ) 
      loop 
         -- if the current column was the one that was altered then say so 
         if ora_is_alter_column (v_column_rec.column_name) 
         then 
            -- if the table/column is core? 
            if is_application_column ( 
                  ora_dict_obj_owner, 
                  ora_dict_obj_name, 
                  v_column_rec.column_name 
               ) 
            then 
               raise_application_error ( 
                  -20001, 
                  'cannot alter core application attributes' 
               ); 
            end if; -- table/column is core 
         end if; -- current column was altered 
      end loop; -- every column in the table 
   end if; -- table was altered 
end; 

 

–属性函数返回值列表

create or replace trigger hr.what_privs 
   after grant on schema 
declare 
   v_grant_type     varchar2 (30); 
   v_num_grantees   binary_integer; 
   v_grantee_list   ora_name_list_t; 
   v_num_privs      binary_integer; 
   v_priv_list      ora_name_list_t; 
begin 
   v_grant_type := ora_dict_obj_type; 
   v_num_grantees := ora_grantee (v_grantee_list); 
   v_num_privs := ora_privilege_list (v_priv_list);
   if v_grant_type = 'role privilege' 
   then 
      dbms_output.put_line ( 
         chr (9) || 'the following roles/privileges were granted' 
      );
      for counter in 1 .. v_num_privs 
      loop 
         dbms_output.put_line ( 
            chr (9) || chr (9) || 'privilege ' || v_priv_list (counter) 
         ); 
      end loop; 
   elsif v_grant_type = 'object privilege' 
   then 
      dbms_output.put_line ( 
         chr (9) || 'the following object privileges were granted' 
      );
      for counter in 1 .. v_num_privs 
      loop 
         dbms_output.put_line ( 
            chr (9) || chr (9) || 'privilege ' || v_priv_list (counter) 
         ); 
      end loop;
      dbms_output.put (chr (9) || 'on ' || ora_dict_obj_name);
      if ora_with_grant_option 
      then 
         dbms_output.put_line (' with grant option'); 
      else 
         dbms_output.put_line (''); 
      end if; 
   elsif v_grant_type = 'system privilege' 
   then 
      dbms_output.put_line ( 
         chr (9) || 'the following system privileges were granted' 
      );
      for counter in 1 .. v_num_privs 
      loop 
         dbms_output.put_line ( 
            chr (9) || chr (9) || 'privilege ' || v_priv_list (counter) 
         ); 
      end loop; 
   else 
      dbms_output.put_line ('i have no idea what was granted'); 
   end if;
   for counter in 1 .. v_num_grantees 
   loop 
      dbms_output.put_line ( 
         chr (9) || 'grant recipient ' || v_grantee_list (counter) 
      ); 
   end loop; 
end; 

 demo:

–创建登录、退出触发器。

create table log_event
(user_name varchar2(10),
 address varchar2(20), 
 logon_date timestamp,
 logoff_date timestamp); 

--创建登录触发器
create or replace trigger tr_logon
after logon on database
begin
   insert into log_event (user_name, address, logon_date)
   values (ora_login_user, ora_client_ip_address, systimestamp);
end tr_logon;
--创建退出触发器
create or replace trigger tr_logoff
before logoff on database
begin
   insert into log_event (user_name, address, logoff_date)
   values (ora_login_user, ora_client_ip_address, systimestamp);
end tr_logoff;

 

 

     

(0)
上一篇 2022年3月22日
下一篇 2022年3月22日

相关推荐