触发器TRIGGER-记录

直接上例子:

 

drop table web_org_oper_history;

 

create table web_org_oper_history(

autoId number primary key,

c_oper_id varchar2(50) not null,

c_passwd varchar2(32),

oper_date timestamp

);

comment on column web_org_oper_history.c_oper_id is 'ID';

comment on column web_org_oper_history.c_passwd is '密码';

 

CREATE SEQUENCE seq_trg_oper_autoid;

 

select * from web_org_oper_history;

 

 

---触发器

create or replace trigger web_org_oper_triger

       after insert or update or delete on web_org_oper

       for each row 

declare

     pragma AUTONOMOUS_TRANSACTION;

     l_found boolean;

     code NUMBER;

     msg  VARCHAR2(500);

begin

     if length(:new.c_passwd)>6 then--测试

        raise_application_error(-20000, '密码长度超过6字节');

      end if;

     if inserting and :new.c_oper_id is not null then

        insert into web_org_oper_history(autoId,c_oper_id,c_passwd,oper_date)

        values(seq_trg_oper_autoid.NEXTVAL,:new.c_oper_id,:new.c_passwd,sysdate); 

        Dbms_output.put_line('增加操作');

        

     elsif updating and  :new.c_passwd is not null then

        l_found :=false;

        for c1 in(select c_oper_id from web_org_oper_history where c_oper_id=:new.c_oper_id and c_passwd=:old.c_passwd) loop

         l_found :=true;

         exit;

         end  loop;

         if not l_found then

            insert into web_org_oper_history(autoId,c_oper_id,c_passwd,oper_date)

                    values(seq_trg_oper_autoid.NEXTVAL,:new.c_oper_id,:new.c_passwd,sysdate); 

             Dbms_output.put_line('增加操作');

         else

            update web_org_oper_history set c_passwd=:new.c_passwd,oper_date=sysdate where c_oper_id=:new.c_oper_id;

            Dbms_output.put_line('更新操作');

            /*insert into web_org_oper_history(c_oper_id,c_passwd)

                    values(:new.c_oper_id,:new.c_passwd); */

          end if;

      end if;

      commit;

      /*下面异常捕获 对上面判断不返回到应用程序 注释掉即可返回到应用*/

      EXCEPTION

          WHEN OTHERS THEN

               code := SQLCODE;

               msg  := substr(SQLERRM, 1, 500);

               Dbms_output.put_line(code);

               Dbms_output.put_line(msg);

               ROLLBACK;

               RAISE;

end web_org_oper_triger;

 

instead of触发器 转自文档:http://blog.sina.com.cn/s/blog_7cc3b8db010113oj.html

下面介绍一种instead of触发器,该触发器主要使用在对视图的更新上,以下是instead of触发器的语法:
CREATE OR REPLACE TRIGGER trigger_name
INSTEAD OF <insert | update | delete> ON view_name
[FOR EACH ROW]
WHEN (condition)
DECLARE
BEGIN
 --触发器代码
END;

其他部分语法同前面所述的before和after语法是一样的,唯一不同的是在第二行用上了instead of关键字。对于普通的视图来说,进行insert等操作是被禁止的,因为Oracle无法知道操作的字段具体是哪个表中的字段。但我们可以通过建立instead of触发器,在触发器主体中告诉Oracle应该更新,删除或者修改哪些表的哪部分字段。如:

6,例三:instead of触发器
新建视图
CREATE VIEW employee_salary(employee_id, maxsalary, MONTH, amount) AS
SELECT a.employee_id, a.maxsalary, b.MONTH, b.amount
FROM employment a, salary b
WHERE a.employee_id = b.employee_id

如果执行插入语句
INSERT INTO employee_salary(employee_id, maxsalary, MONTH, amount)
VALUES(10, 100000, '200606', 10000);
系统会报错:
ORA-01779:无法修改与非键值保存表对应的列

我们可以通过建立以下的instead of存储过程,将插入视图的值分别插入到两个表中:
create or replace trigger employee_salary_rii
  instead of insert on employee_salary 
  for each ROW
DECLARE
 v_cnt NUMBER;
BEGIN
  --检查是否存在该员工信息
 SELECT COUNT(*)
  INTO v_cnt
  FROM employment
  WHERE employee_id = :NEW.employee_id;
 IF v_cnt = 0 THEN
  INSERT INTO employment
   (employee_id, maxsalary)
  VALUES
   (:NEW.employee_id, :NEW.maxsalary);
 END IF;
  --检查是否存在该员工的工资信息
 SELECT COUNT(*)
  INTO v_cnt
  FROM salary
  WHERE employee_id = :NEW.employee_id
   AND MONTH = :NEW.MONTH;
 IF v_cnt = 0 THEN
  INSERT INTO salary
   (employee_id, MONTH, amount)
  VALUES
   (:NEW.employee_id, :NEW.MONTH, :NEW.amount);
 END IF;
END employee_salary_rii;

该触发器被建立后,执行上述insert操作,系统就会提示成功插入一条记录。
但需要注意的是,这里的“成功插入一条记录”,只是Oracle并未发现触发器中有异常抛出,而根据insert语句中涉及的记录数作出一个判断。若触发器的主体什么都没有,只是一个空语句,Oracle也会报“成功插入一条记录”。同样道理,即使在触发器主体里往多个表中插入十条记录,Oracle的返回也是“成功插入一条记录”。

 


行级触发器可以解决大部分的问题,但是如果需要对本表进行扫描检查,比如要检查总的工资是否超限了,用行级触发器是不行的,因为行级触发器主体中不能有涉及到关联表的事务,这时就需要用到语句级触发器。以下是语句级触发器的语法:
CREATE OR REPLACE TRIGGER trigger_name
<before | after | instead of ><insert | update | delete > ON table_name
DECLARE
BEGIN
 --触发器主体
END;

从语法定义上来看,行级触发器少了for each row,也不能使用when子句来限定入口条件,其他部分都是一样的,包括insert, update, delete和instead of都可以使用。


7,例四:语句级触发器之一
CREATE OR REPLACE TRIGGER salary_saiu
AFTER INSERT OR UPDATE OF amount ON salary
DECLARE
 v_sumsalary NUMBER;
BEGIN
  SELECT SUM(amount) INTO v_sumsalary FROM salary;
 IF v_sumsalary > 500000 THEN
  raise_application_error(-20001, '总工资超过500000');
 END IF;
END;

以上代码定义了一个语句级触发器,该触发器检查在insert和update了amount字段后操作后,工资表中所有工资记录累加起来是否超过500000,如果超过则抛出异常。从这个例子可以看出,语句级触发器可以对关联表表进行扫描,扫描得到的结果可以用来作为判断一致性的标志。需要注意的是,在before语句触发器主体和after语句触发器主体中对关联表进行扫描,结果是不一样的。在before语句触发器主体中扫描,扫描结果将不包括新插入和更新的记录,也就是说当以上代码换成 before触发器后,以下语句将不报错:
INSERT INTO salary(employee_id, month, amount) VALUEs(2, '200601', 600000)
这是因为在主体中得到的v_sumsalary并不包括新插入的600000工资。
另外,在语句级触发器中不能使用:new和:old对象,这一点和行级触发器是显著不同的。如果需要检查插入或更新后的记录,可以采用临时表技术。
临时表是一种Oracle数据库对象,其特点是当创建数据的进程结束后,进程所创建的数据也随之清除。进程与进程不可以互相访问同一临时表中对方的数据,而且对临时表进行操作也不产生undo日志,减少了数据库的消耗。具体有关临时表的知识,可以参看有关书籍。
为了在语句级触发器中访问新插入后修改后的记录,可以增加行级触发器,将更新的记录插入临时表中,然后在语句级触发器中扫描临时表,获得修改后的记录。临时表的表结构一般与关联表的结构一致。


8,例五:语句级触发器之二
目的:限制每个员工的总工资不能超过50000,否则停止对该表操作。
创建临时表
create global temporary table SALARY_TMP
(
  EMPLOYEE_ID NUMBER,
  MONTH       VARCHAR2(6),
  AMOUNT      NUMBER
)
on commit delete rows;

为了把操作记录插入到临时表中,创建行级触发器:
CREATE OR REPLACE TRIGGER salary_raiu
AFTER INSERT OR UPDATE OF amount ON salary
FOR EACH ROW
BEGIN
  INSERT INTO salary_tmp(employee_id, month, amount)
  VALUES(:NEW.employee_id, :NEW.MONTH, :NEW.amount);
END;
该触发器的作用是把更新后的记录信息插入到临时表中,如果更新了多条记录,则每条记录都会保存在临时表中。

创建语句级触发器:
CREATE OR REPLACE TRIGGER salary_sai
AFTER INSERT OR UPDATE OF amount ON salary
DECLARE
 v_sumsalary NUMBER;
BEGIN
 FOR cur IN (SELECT * FROM salary_tmp) LOOP
  SELECT SUM(amount)
   INTO v_sumsalary
   FROM salary
   WHERE employee_id = cur.employee_id;
  IF v_sumsalary > 50000 THEN
   raise_application_error(-20002, '员工累计工资超过50000');
  END IF;
    DELETE FROM salary_tmp;
 END LOOP;
END;

该触发器首先用游标从salary_tmp临时表中逐条读取更新或插入的记录,取employee_id,在关联表salary中查找所有相同员工的工资记录,并求和。若某员工工资总和超过50000,则抛出异常。如果检查通过,则清空临时表,避免下次检查相同的记录。
执行以下语句:
INSERT INTO salary(employee_id, month, amount) VALUEs(7, '200601', 20000);
INSERT INTO salary(employee_id, month, amount) VALUEs(7, '200602', 20000);
INSERT INTO salary(employee_id, month, amount) VALUEs(7, '200603', 20000);
在执行第三句时系统报错:
ORA-20002:员工累计工资超过50000
查询salary表,发现前两条记录正常插入了,第三条记录没有插入。


如果系统结构比较复杂,而且触发器的代码比较多,在触发器主体中写过多的代码,对于维护来说是一个困难。这时可以将所有触发器的代码写到同一个包中,不同的触发器代码以不同的存储过程封装,然后触发器主体中调用这部分代码。

9,例六:用包封装触发器代码
目的:改写例五,封装触发器主体代码
创建代码包:
CREATE OR REPLACE PACKAGE BODY salary_trigger_pck IS

 PROCEDURE load_salary_tmp(i_employee_id IN NUMBER,
       i_month       IN VARCHAR2,
       i_amount      IN NUMBER) IS
 BEGIN
  INSERT INTO salary_tmp VALUES (i_employee_id, i_month, i_amount);
 END load_salary_tmp;

 PROCEDURE check_salary IS
  v_sumsalary NUMBER;
 BEGIN
  FOR cur IN (SELECT * FROM salary_tmp) LOOP
   SELECT SUM(amount)
    INTO v_sumsalary
    FROM salary
    WHERE employee_id = cur.employee_id;
   IF v_sumsalary > 50000 THEN
    raise_application_error(-20002, '员工累计工资超过50000');
   END IF;
   DELETE FROM salary_tmp;
  END LOOP;
 END check_salary;
END salary_trigger_pck;
包salary_trigger_pck中有两个存储过程,load_salary_tmp用于在行级触发器中调用,往salary_tmp临时表中装载更新或插入记录。而check_salary用于在语句级触发器中检查员工累计工资是否超限。

修改行级触发器和语句级触发器:
CREATE OR REPLACE TRIGGER salary_raiu
 AFTER INSERT OR UPDATE OF amount ON salary
 FOR EACH ROW
BEGIN
 salary_trigger_pck.load_salary_tmp(:NEW.employee_id,  :NEW.MONTH, :NEW.amount);
END;

CREATE OR REPLACE TRIGGER salary_sai
AFTER INSERT OR UPDATE OF amount ON salary
BEGIN
 salary_trigger_pck.check_salary;
END;

这样主要代码就集中到了salary_trigger_pck中,触发器主体中只实现了一个调用功能。

10,触发器命名规范
为了方便对触发器命名和根据触发器名称了解触发器含义,需要定义触发器的命名规范:
Trigger_name = table_name_trg_<R|S><A|B|I><I|U|D>

触发器名限于30个字符。必须缩写表名,以便附加触发器属性信息。
<R|S>基于行级(row)还是语句级(statement)的触发器
<A|B|I>after, before或者是instead of触发器
<I|U|D>触发事件是insert,update还是delete。如果有多个触发事件则连着写

例如:
Salary_rai  salary表的行级after触发器,触发事件是insert
Employee_sbiud employee表的语句级before触发器,触发事件是insert,update和delete

猜你喜欢

转载自jsamson.iteye.com/blog/2165615