ORA-04091 从过程调用函数时发生变异 table 错误
ORA-04091 mutating table error when calling a function from a procedure
我有一个任务:
Write a procedure to update salary (salary * % of increment) in emp table based on grade. Use function to get increment
这是我的程序:
CREATE OR REPLACE
PROCEDURE sal_incre
IS
CURSOR c_cur
IS
SELECT * FROM emp_task;
BEGIN
UPDATE emp_task SET sal = sal + sal_incr(grade_id);
FOR rec IN c_cur
LOOP
dbms_output.put_line(rec.empno||','||rec.ename||','||rec.sal);
END LOOP;
END;
这是我的函数代码:
CREATE OR REPLACE
FUNCTION sal_incr(
p_grade NUMBER)
RETURN
IS
v_inc NUMBER;
BEGIN
SELECT raise_percent
INTO v_inc
FROM sal_inc
WHERE grade_id IN
(SELECT grade_id FROM emp_task WHERE grade_id = p_grade
);
RETURN v_inc;
COMMIT;
END;
当我调用过程时,我得到:
ORA-04091: table SCOTT.EMP_TASK is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.SAL_INCR", line 8
ORA-06512: at "SCOTT.SAL_INCRE", line 6
ORA-06512: at line 2
我做错了什么?
您的函数指的是您在 调用 该函数时在过程中使用的同一个 table,这就是导致此错误的原因。您正在同时更新和查询它,以一种可能导致不确定(或混淆)结果的方式,即使您没有查询正在更新的列。 Oracle 在这里保护您免受您自己的伤害。
在你的函数中你正在做的事情:
SELECT raise_percent
INTO v_inc
FROM sal_inc
WHERE grade_id IN
(SELECT grade_id FROM emp_task WHERE grade_id = p_grade
);
这里的emp_task
table不用看。除非你传递了一个不存在的值(这不会发生在你的过程中),子查询只能 return 原始的 p_grade
参数值,所以这与:
SELECT raise_percent
INTO v_inc
FROM sal_inc
WHERE grade_id = p_grade;
如果你这样做,该函数不再引用 emp_task
,因此当它作为更新的一部分被调用时,它不会抛出变异 trigger/function 错误。
并且您的函数不应发出 COMMIT
- 让调用过程,或者最好是调用该过程的 session 来决定应该提交还是回滚谁的事务。
此外,从标题和列名看来 raise_percent
是一个百分比,因此您需要使用它来找到要乘以的值 - 您不应该 add那个百分比数字。例如,如果这给你一个 2% 的值 2,你需要在你的程序中这样做:
UPDATE emp_task SET sal = sal * (1 + (sal_incr(grade_id)/100));
或者更巧妙地使用函数 return 1 + (raise_percent/100)
并执行:
UPDATE emp_task SET sal = sal * sal_incr(grade_id);
修改程序如下:
create or replace
procedure sal_incre
is
cursor c_cur is
select distinct e.grade_id,e.ename,e.sal from sal_inc s
join emp_task e on e.grade_id = s.grade_id order by e.ename ;
v_incr number;
begin
for f_cur in c_cur
loop
v_incr := sal_incr(f_cur.grade_id);
update emp_task set sal = sal + v_incr;
dbms_output.put_line('Emp Name : '||f_cur.ename||','
||' Sal:'||f_cur.sal||','||' Grade: '||f_cur.grade_id);
end loop;
end;
我有一个任务:
Write a procedure to update salary (salary * % of increment) in emp table based on grade. Use function to get increment
这是我的程序:
CREATE OR REPLACE
PROCEDURE sal_incre
IS
CURSOR c_cur
IS
SELECT * FROM emp_task;
BEGIN
UPDATE emp_task SET sal = sal + sal_incr(grade_id);
FOR rec IN c_cur
LOOP
dbms_output.put_line(rec.empno||','||rec.ename||','||rec.sal);
END LOOP;
END;
这是我的函数代码:
CREATE OR REPLACE
FUNCTION sal_incr(
p_grade NUMBER)
RETURN
IS
v_inc NUMBER;
BEGIN
SELECT raise_percent
INTO v_inc
FROM sal_inc
WHERE grade_id IN
(SELECT grade_id FROM emp_task WHERE grade_id = p_grade
);
RETURN v_inc;
COMMIT;
END;
当我调用过程时,我得到:
ORA-04091: table SCOTT.EMP_TASK is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.SAL_INCR", line 8
ORA-06512: at "SCOTT.SAL_INCRE", line 6
ORA-06512: at line 2
我做错了什么?
您的函数指的是您在 调用 该函数时在过程中使用的同一个 table,这就是导致此错误的原因。您正在同时更新和查询它,以一种可能导致不确定(或混淆)结果的方式,即使您没有查询正在更新的列。 Oracle 在这里保护您免受您自己的伤害。
在你的函数中你正在做的事情:
SELECT raise_percent
INTO v_inc
FROM sal_inc
WHERE grade_id IN
(SELECT grade_id FROM emp_task WHERE grade_id = p_grade
);
这里的emp_task
table不用看。除非你传递了一个不存在的值(这不会发生在你的过程中),子查询只能 return 原始的 p_grade
参数值,所以这与:
SELECT raise_percent
INTO v_inc
FROM sal_inc
WHERE grade_id = p_grade;
如果你这样做,该函数不再引用 emp_task
,因此当它作为更新的一部分被调用时,它不会抛出变异 trigger/function 错误。
并且您的函数不应发出 COMMIT
- 让调用过程,或者最好是调用该过程的 session 来决定应该提交还是回滚谁的事务。
此外,从标题和列名看来 raise_percent
是一个百分比,因此您需要使用它来找到要乘以的值 - 您不应该 add那个百分比数字。例如,如果这给你一个 2% 的值 2,你需要在你的程序中这样做:
UPDATE emp_task SET sal = sal * (1 + (sal_incr(grade_id)/100));
或者更巧妙地使用函数 return 1 + (raise_percent/100)
并执行:
UPDATE emp_task SET sal = sal * sal_incr(grade_id);
修改程序如下:
create or replace
procedure sal_incre
is
cursor c_cur is
select distinct e.grade_id,e.ename,e.sal from sal_inc s
join emp_task e on e.grade_id = s.grade_id order by e.ename ;
v_incr number;
begin
for f_cur in c_cur
loop
v_incr := sal_incr(f_cur.grade_id);
update emp_task set sal = sal + v_incr;
dbms_output.put_line('Emp Name : '||f_cur.ename||','
||' Sal:'||f_cur.sal||','||' Grade: '||f_cur.grade_id);
end loop;
end;