触发器学习2

4,例一:行级触发器之一
CREATE OR REPLACE TRIGGER salary_raiu
AFTER INSERT OR UPDATE OF amount ON salary
FOR EACH ROW
BEGIN
IF inserting THEN
dbms_output.put_line(‘插入’);
ELSIF updating THEN
dbms_output.put_line(‘更新amount列’);
END IF;
END;
以上是一个after insert和after update的行级触发器。在第二行中of amount on salary的意思是只有当amount列被更新时,update触发器才会有效。所以,以下语句将不会执行触发器:
Update salary set month = ‘200601’ where month = ‘200606’;
在触发器主体的if语句表达式中,inserting, updating和deleting可以用来区分当前是在做哪一种DML操作,可以作为把多个类似触发器合并在一个触发器中判别触发事件的属性。
5,例二:行级触发器之二
新建员工表employment
CREATE TABLE EMPLOYMENT
(
EMPLOYEE_ID NUMBER, –员工ID
MAXSALARY NUMBER –工资上限
)
插入两条记录
Insert into employment values(1, 1000);
Insert into employment values(2, 2000);
CREATE OR REPLACE TRIGGER salary_raiu
AFTER INSERT OR UPDATE OF amount ON salary
FOR EACH ROW
WHEN ( NEW.amount >= 1000 AND (old.amount IS NULL OR OLD.amount <= 500))
DECLARE
v_maxsalary NUMBER;
BEGIN
SELECT maxsalary
INTO v_maxsalary
FROM employment
WHERE employee_id = :NEW.employee_id;
IF :NEW.amount > v_maxsalary THEN
raise_application_error(-20000, ‘工资超限’);
END IF;
END;
以上的例子引入了一个新的表employment,表中的maxsalary字段代表该员工每月所能分配的最高工资。下面的触发器根据插入或修改记录的employee_id,在employment表中查到该员工的每月最高工资,如果插入或修改后的amount超过这个值,则报错误。
代码中的when子句表明了该触发器只针对修改或插入后的amount值超过1000,而修改前的amount值小于500的记录。New对象和old对象分别表示了操作前和操作后的记录对象。对于insert操作,由于当前操作记录无历史对象,所以old对象中所有属性是null;对于delete操作,由于当前操作记录没有更新对象,所以new对象中所有属性也是null。但在这两种情况下,并不影响old和new对象的引用和在触发器主体中的使用,和普通的空值作同样的处理。
在触发器主体中,先通过:new.employee_id,得到该员工的工资上限,然后在if语句中判断更新后的员工工资是否超限,如果超限则错误代码为-20000,错误信息为“工资超限”的自定义错误。其中的raise_application_error包含两个参数,前一个是自定义错误代码,后一个是自定义错误代码信息。其中自定义错误代码必须小于或等于-20000。执行完该语句后,一个异常被抛出,如果在上一层有exception子句,该异常将被捕获。如下面代码:
DECLARE
code NUMBER;
msg VARCHAR2(500);
BEGIN
INSERT INTO salary (employee_id, amount) VALUES (2, 5000);
EXCEPTION
WHEN OTHERS THEN
code := SQLCODE;
msg := substr(SQLERRM, 1, 500);
dbms_output.put_line(code);
dbms_output.put_line(msg);
END;
执行后,将在output中或者sqlplus窗口中见着以下信息:
-20000
ORA-20000: 工资超出限制
ORA-06512: 在”SCOTT.SALARY_RAI”, line 9
ORA-04088: 触发器 ‘SCOTT.SALARY_RAI’ 执行过程中出错
这里的raise_application_error相当于拒绝了插入或者修改事务,当上层代码接受到这个异常后,判断该异常代码等于-20000,可以作出回滚事务或者继续其他事务的处理。
以上两个例子中用到的inserting, updating, deleting和raise_application_error都是dbms_standard包中的函数,具体的说明可以参照Oracle的帮助文档。
create or replace package sys.dbms_standard is
procedure raise_application_error(num binary_integer, msg varchar2,
function inserting return boolean;
function deleting return boolean;
function updating return boolean;
function updating (colnam varchar2) return boolean;
end;
对于before和after行级触发器,:new和:old对象的属性值都是一样的,主要是对于在Oracle约束(Constraint)之前或之后的执行触发器的选择。需要注意的是,可以在before行触发器中更改:new对象中的值,但是在after行触发器就不行。
下面介绍一种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

猜你喜欢

转载自blog.csdn.net/qq_28198893/article/details/80999457
今日推荐