Oracle 如何编写触发器以强制执行业务规则?
我想创建用于实践PL/SQL的触发器,我有点被这两个触发器卡住了,我确信它们很简单,但我无法掌握这段代码 第一个触发器禁止员工的工资高于其上司的80%(代码不完整,因为我不知道如何继续):Oracle 如何编写触发器以强制执行业务规则?,oracle,plsql,oracle10g,database-trigger,Oracle,Plsql,Oracle10g,Database Trigger,我想创建用于实践PL/SQL的触发器,我有点被这两个触发器卡住了,我确信它们很简单,但我无法掌握这段代码 第一个触发器禁止员工的工资高于其上司的80%(代码不完整,因为我不知道如何继续): 创建或替换触发器最大工资 在EMP上插入之前 每行 P.BOSS EMP.JOB%类型:=“BOSS” P.SALARY EMP.SAL%类型 开始 从EMP中选择SAL 哪里 工作!=老板 ... 第二,每个部门不得少于两名员工 创建触发器最小\u限制 删除或更新EMPNO后 员工人数(2,0); 开始
创建或替换触发器最大工资
在EMP上插入之前
每行
P.BOSS EMP.JOB%类型:=“BOSS”
P.SALARY EMP.SAL%类型
开始
从EMP中选择SAL
哪里
工作!=老板
...
第二,每个部门不得少于两名员工
创建触发器最小\u限制
删除或更新EMPNO后
员工人数(2,0);
开始
从EMP中选择计数(EMPNO)到员工
其中DEPTNO=DEPT.DEPTNO;
如果员工人数<2,则
DBMS_OUTPUT.PUT_LINE(“每个部门不能少于两名员工”);
如果结束;
结束;
我真的不知道我是真的离它越来越近还是越来越远
我相信它们很简单
实际上,这些任务对于触发器来说并不简单。业务逻辑很简单,执行业务逻辑的SQL很简单,但在触发器中实现它很困难。要理解为什么你需要理解触发器是如何工作的
触发器作为事务的一部分触发,这意味着它们将应用于SQL语句(如insert或update)的结果。触发器有两种类型,行级和语句级触发器
行级触发器为结果集中的每一行触发一次。我们可以引用当前行中的值,这对于评估行级规则非常有用。。但是我们不能对拥有的表执行DML:Oracle抛出ORA-04088变异表异常,因为这样的操作违反了事务完整性
语句级触发器在每个语句中只触发一次。因此,它们对于强制执行表级规则非常有用,但至关重要的是,它们无法访问结果集,这意味着它们不知道哪些记录受到DML的影响
您的两个业务规则都是表级规则,因为它们需要评估多个EMP记录。那么,我们可以通过触发器强制执行它们吗?让我们从第二条规则开始:
每个部门不得少于两名员工
我们可以使用一个触发器接一个语句触发器来实现这一点,如下所示:
CREATE or replace TRIGGER MIN_LIMIT
AFTER DELETE OR UPDATE on EMP
declare
EMPLOYEES pls_integer;
BEGIN
for i in ( select * from dept) loop
SELECT COUNT(EMPNO) INTO EMPLOYEES
FROM EMP
where i.DEPTNO = EMP.DEPTNO;
IF EMPLOYEES < 2 THEN
raise_application_error(-20042, 'problem with dept #' || i.DEPTNO || '. There cannot be less than two employees per department');
END IF;
end loop;
END;
/
我们可以做到这一点。它会成功,因为我们的触发器不会在插入时触发。但其他用户的更新随后将失败。这显然是线轴。所以我们需要在触发器操作中包含INSERT
CREATE or replace TRIGGER MIN_LIMIT
AFTER INSERT or DELETE OR UPDATE on EMP
declare
EMPLOYEES pls_integer;
BEGIN
for i in ( select deptno, count(*) as emp_cnt
from emp
group by deptno having count(*) < 2
) loop
raise_application_error(-20042, 'problem with dept #' || i.DEPTNO || '. There cannot be less than two employees per department');
end loop;
END;
/
请注意,将现有员工切换到新部门具有相同的限制:我们必须在同一报表中更新至少两名员工
另一个问题是触发器的性能可能很差,因为我们必须在每条语句之后查询整个表。也许我们可以做得更好?对复合触发器(Oracle11g及更高版本)允许我们跟踪受影响的记录,以便在触发后在语句级别使用。让我们看看如何使用它来实现第一条规则
没有一个员工的工资能高于老板的80%
复合触发器非常整洁。它们允许我们在触发器的所有事件中共享程序结构。这意味着我们可以将行级事件中的值存储在集合中,我们可以使用集合在代码之后在语句级驱动一些SQL
这个触发器触发了三个事件。在处理SQL语句之前,我们初始化使用EMP表投影的集合。如果员工有经理,则行前代码隐藏当前行中的相关值。(显然,这条规则不适用于没有老板的金总统)。after代码遍历隐藏的值,查找相关经理的工资,并根据其老板的工资评估员工的新工资
CREATE OR REPLACE TRIGGER MAX_SALARY
FOR INSERT OR UPDATE ON EMP
COMPOUND TRIGGER
type emp_array is table of emp%rowtype index by simple_integer;
emps_nt emp_array ;
v_idx simple_integer := 0;
BEFORE STATEMENT IS
BEGIN
emps_nt := new emp_array();
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
v_idx := v_idx + 1;
if :new.mgr is not null then
emps_nt(v_idx).empno := :new.empno;
emps_nt(v_idx).mgr := :new.mgr;
emps_nt(v_idx).sal := :new.sal;
end if;
END BEFORE EACH ROW;
AFTER EACH ROW IS
BEGIN
null;
END AFTER EACH ROW;
AFTER STATEMENT IS
mgr_sal emp.sal%type;
BEGIN
for i in emps_nt.first() .. emps_nt.last() loop
select sal into mgr_sal
from emp
where emp.empno = emps_nt(i).mgr;
if emps_nt(i).sal > (mgr_sal * 0.8) then
raise_application_error(-20024, 'salary of empno ' || emps_nt(i).empno || ' is too high!');
end if;
end loop;
END AFTER STATEMENT;
END;
/
此代码将检查每个员工的更新是否通用,例如,当每个人都获得20%的加薪时
update emp
set sal = sal * 1.2
/
但如果我们只更新EMP表的一个子集,它只会检查boss记录,它需要:
update emp set sal = sal * 1.2
where deptno = 20
/
这使得它比以前的触发器更有效。我们可以将触发器MIN_LIMIT作为复合触发器重新写入;这是留给读者的练习:)
同样,一旦发现一个违规行,每个触发器都会失败:
ORA-20024:EMPNO7902的工资太高了ORA-06512:“APC.最高工资”,第36行 可以计算所有受影响的行,将冲突行隐藏在另一个集合中,然后显示集合中的所有行。读者的另一个练习 最后,请注意,在同一个表上的同一事件上触发两个触发器不是好的做法。通常最好(更有效,更容易调试)有一个触发器来完成所有事情
深思熟虑。如果一次会议提高了员工的工资,而另一次会议同时降低了老板的工资,那么规则1会发生什么变化?触发器将传递两个更新,但最终可能会违反规则。这是触发器与Oracle的读-提交事务一致性工作方式的必然结果。除非采用悲观锁定策略并预先锁定可能受更改影响的所有行,否则无法避免这种情况。这可能无法扩展,而且使用纯SQL很难实现:它需要存储过程。这是触发器不适合执行业务规则的另一个原因
我正在使用Oracle10g 这是不幸的。Oracle 10g已经过时近十年了。即使是11g也不受欢迎。然而,如果你真的别无选择,只能坚持10g,那么你有几个选择 第一种方法是在整张桌子上翻来翻去,为每个员工查找每个老板
CREATE OR REPLACE TRIGGER MAX_SALARY
FOR INSERT OR UPDATE ON EMP
COMPOUND TRIGGER
type emp_array is table of emp%rowtype index by simple_integer;
emps_nt emp_array ;
v_idx simple_integer := 0;
BEFORE STATEMENT IS
BEGIN
emps_nt := new emp_array();
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
v_idx := v_idx + 1;
if :new.mgr is not null then
emps_nt(v_idx).empno := :new.empno;
emps_nt(v_idx).mgr := :new.mgr;
emps_nt(v_idx).sal := :new.sal;
end if;
END BEFORE EACH ROW;
AFTER EACH ROW IS
BEGIN
null;
END AFTER EACH ROW;
AFTER STATEMENT IS
mgr_sal emp.sal%type;
BEGIN
for i in emps_nt.first() .. emps_nt.last() loop
select sal into mgr_sal
from emp
where emp.empno = emps_nt(i).mgr;
if emps_nt(i).sal > (mgr_sal * 0.8) then
raise_application_error(-20024, 'salary of empno ' || emps_nt(i).empno || ' is too high!');
end if;
end loop;
END AFTER STATEMENT;
END;
/
update emp
set sal = sal * 1.2
/
update emp set sal = sal * 1.2
where deptno = 20
/