Oracle 如何编写触发器以强制执行业务规则?

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); 开始

我想创建用于实践PL/SQL的触发器,我有点被这两个触发器卡住了,我确信它们很简单,但我无法掌握这段代码

第一个触发器禁止员工的工资高于其上司的80%(代码不完整,因为我不知道如何继续):

创建或替换触发器最大工资
在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
/