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
|