你如何编写一个程序来显示一个字段的值不能高于另一个字段的值,就数字而言。说。雇员的薪水不能高于其经理的薪水。我以前从来没有做过
1 回答
在 SQL 中没有声明式的方式来执行这样的业务规则。所以必须用代码来完成。有许多陷阱,其中最重要的是确定需要强制执行规则的所有场景。
以下是场景:
- 当我们插入一个员工时,我们需要检查他们的薪水是否大于其经理薪水的 90%。
- 当我们更新员工的工资时,我们需要检查它是否仍然不超过经理工资的 90%。
- 当我们更新经理的工资时,我们需要检查它是否仍然大于所有下属工资的 110%。
- 如果我们同时为经理及其下属插入记录(例如使用 INSERT ALL),我们需要确保该规则仍然有效。
- 如果我们将一名员工从一位经理转移到另一位经理,我们需要确保该规则仍然得到执行。
以下是使这一切变得更加困难的事情:
- 执行这些规则涉及从我们正在操作的表中进行选择,因此由于 ORA-04088: mutating tables 异常,我们不能使用 BEFORE ... FOR EACH ROW 触发器。
- 此外,从表中选择意味着我们不能在多用户模式下运行,因为读取一致性(否则会话 #1 可能会继续对员工加薪,而忽略会话 #2 当前正在应用减薪的事实)该员工的经理)。
因此,出于所有这些原因,执行此类业务规则的唯一方法是使用 API。构建一个存储过程,永远不要让任何进程对表进行裸 DML 访问。
以下代码块仅在更新员工工资时强制执行该规则。兴趣点包括:
- 它具有用户定义的异常来识别规则违规。实际上,这些应该在包规范中定义,以便其他程序单元可以引用它们。
- 使用 SELECT ... FOR UPDATE 锁定感兴趣的行。
使用 COMMIT 和 ROLLBACK 来释放锁。在实际实现中,这可能会以不同方式处理(即由调用程序)。
创建或替换过程 change_emp_sal(emp.empno%type 中的 p_eno,emp.sal%type 中的 p_new_sal)是类型 emp_nt 是 emp%rowtype 的表;l_emp emp%rowtype; l_mgr emp%rowtype; l_subords emp_nt;l_idx pls_integer; x_mgr_not_paid_enough 异常;pragma exception_init(x_mgr_not_paid_enough, -20000); x_sub_paid_too_much 异常;pragma exception_init(x_sub_paid_too_much, -20001); begin -- 锁定员工记录 select * into l_emp from emp where empno = p_eno for update of sal;
-- lock their manager's record (if they have one) if l_emp.mgr is not null then select * into l_mgr from emp where empno = l_emp.mgr for update; end if; -- lock their subordinates' records select * bulk collect into l_subords from emp where mgr = p_eno for update; -- compare against manager's salary if l_mgr.sal is not null and l_mgr.sal < ( p_new_sal * 1.1 ) then raise x_mgr_not_paid_enough; end if; -- compare against subordinates' salaries for i in 1..l_subords.count() loop if l_subords(i).sal > ( p_new_sal * 0.9 ) then l_idx := i; raise x_sub_paid_too_much; end if; end loop; -- no exceptions raised so we can go ahead update emp set sal = p_new_sal where empno = p_eno; -- commit to free the locks commit;
当 x_mgr_not_paid_enough 然后 dbms_output.put_line ('错误!经理只赚 '||l_mgr.sal) 时出现异常;回滚;增加; 当 x_sub_paid_too_much 然后 dbms_output.put_line ('错误!下属赚取'||l_subords(l_idx).sal); 回滚;增加; 结束change_emp_sal;/
以下是 50 部门的四名员工:
SQL> select e.empno, e.ename, e.sal, m.ename as mgr_name, m.empno as mgr_no
2 from emp e join emp m on (e.mgr = m.empno)
3 where e.deptno = 50
4 order by sal asc
5 /
EMPNO ENAME SAL MGR_NAME MGR_NO
---------- ---------- ---------- ---------- ----------
8060 VERREYNNE 2850 FEUERSTEIN 8061
8085 TRICHLER 3500 FEUERSTEIN 8061
8100 PODER 3750 FEUERSTEIN 8061
8061 FEUERSTEIN 4750 SCHNEIDER 7839
SQL>
让我们试着给比利一个大的加薪,这应该会失败......
SQL> exec change_emp_sal (8060, 4500)
Error! manager only earns 4750
BEGIN change_emp_sal (8060, 4500); END;
*
ERROR at line 1:
ORA-20000:
ORA-06512: at "APC.CHANGE_EMP_SAL", line 67
ORA-06512: at line 1
SQL>
好吧,让我们给比利小幅加薪,这应该会成功……
SQL> exec change_emp_sal (8060, 4000)
PL/SQL procedure successfully completed.
SQL>
现在让我们试着给史蒂文一个摇摆不定的减薪,这应该会失败......
SQL> exec change_emp_sal (8061, 3500)
Error! subordinate earns 3500
BEGIN change_emp_sal (8061, 3500); END;
*
ERROR at line 1:
ORA-20001:
ORA-06512: at "APC.CHANGE_EMP_SAL", line 71
ORA-06512: at line 1
SQL>
所以让我们给史蒂文一个象征性的减薪,这应该会成功......
SQL> exec change_emp_sal (8061, 4500)
PL/SQL procedure successfully completed.
SQL>
这是新的薪酬结构...
SQL> select e.empno, e.ename, e.sal, m.ename as mgr_name, m.empno as mgr_no
2 from emp e join emp m on (e.mgr = m.empno)
3 where e.deptno = 50
4 order by sal asc
5 /
EMPNO ENAME SAL MGR_NAME MGR_NO
---------- ---------- ---------- ---------- ----------
8085 TRICHLER 3500 FEUERSTEIN 8061
8100 PODER 3750 FEUERSTEIN 8061
8060 VERREYNNE 4000 FEUERSTEIN 8061
8061 FEUERSTEIN 4500 SCHNEIDER 7839
SQL>
所以它有效,就它而言。它只处理五个场景中的两个。重构代码以满足其他三个要求留给读者作为练习。