Не существует декларативного способа применения таких бизнес-правил в SQL. Так что это должно быть сделано с кодом. Существует ряд ошибок, не в последнюю очередь из которых идентифицируется все сценарии , где необходимо применять правило.
Вот сценарии:
- Когда мы вводим сотрудника, нам нужно проверить, превышает ли его зарплата 90% от зарплаты их менеджера.
- Когда мы обновляем зарплату сотрудника, нам нужно убедиться, что она по-прежнему не превышает 90% от зарплаты их менеджера.
- Когда мы обновляем зарплату менеджера, нам нужно убедиться, что она по-прежнему превышает 110% всех зарплат их подчиненных.
- Если мы вставляем записи одновременно для менеджера и его подчиненных (скажем, с помощью INSERT ALL), мы должны убедиться, что правило все еще применяется.
- Если мы переводим сотрудника с одного менеджера на другого, нам нужно убедиться, что это правило все еще применяется.
Вот вещи, которые усложняют все это:
- Для применения этих правил необходимо выбрать из таблицы, которой мы манипулируем, поэтому мы не можем использовать триггеры BEFORE ... FOR EACH ROW, из-за ORA-04088: исключения мутирующих таблиц.
- Кроме того, выбор из таблицы означает, что мы не можем работать в многопользовательском режиме из-за согласованности чтения (в противном случае сеанс № 1 может привести к увеличению заработной платы сотрудника, не обращая внимания на тот факт, что сеанс № 2 в настоящее время применяет уменьшение зарплаты руководителю этого сотрудника).
Таким образом, по всем этим причинам единственный способ обеспечить соблюдение таких бизнес-правил - это использовать API; создайте хранимую процедуру и никогда не позволяйте никаким процессам иметь доступ к таблице с открытым доступом DML.
Следующий код chunk o 'применяет правило только при обновлении зарплаты сотрудника. Достопримечательности включают в себя:
- имеет определенные пользователем исключения для выявления нарушений правил. На самом деле они должны быть определены в спецификации пакета, чтобы другие программные модули могли ссылаться на них.
- использование SELECT ... FOR UPDATE для блокировки интересующих строк.
использование COMMIT и ROLLBACK для снятия блокировок. В реальной реализации это может быть обработано по-другому (то есть вызывающей программой).
создать или заменить процедуру change_emp_sal
(p_eno в типе emp.empno%
, p_new_sal в emp.sal% type)
является
тип emp_nt - таблица типа emp% row;
l_emp emp% rowtype;
l_mgr emp% rowtype;
l_subords emp_nt;
l_idx pls_integer;
исключение x_mgr_not_paid_enough;
исключение прагмы (x_mgr_not_paid_enough, -20000);
исключение x_sub_paid_too_much;
исключение прагмы (x_sub_paid_too_much, -20001);
начать
- заблокировать запись сотрудника
выберите * в l_emp
от emp
где empno = p_eno
для обновления сала;
-- 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);
откатить;
поднять;
end 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>
Так что это работает, насколько это возможно. Он обрабатывает только два из пяти сценариев. Рефакторинг кода, чтобы удовлетворить три других, оставлен читателю в качестве упражнения.