从过程调用函数时ORA-04091变异表错误

时间:2015-06-17 14:14:24

标签: oracle stored-procedures plsql

我有一个任务:

  

根据等级在emp表中编写更新工资(工资*增量%)的程序。使用函数来增加

这是我的程序:

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

我做错了什么?

2 个答案:

答案 0 :(得分:1)

您的函数指的是您在调用该函数时在该过程中使用的同一个表,这是导致此错误的原因。您正在以可能导致不确定(或令人困惑)结果的方式同时更新和查询它,即使您没有查询要更新的列。 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表。除非您已经传递了一个不存在的值(这不能从您的过程中发生),否则子查询只能返回原始的p_grade参数值,因此它与以下内容相同:

SELECT raise_percent 
INTO v_inc 
FROM sal_inc 
WHERE grade_id = p_grade;

如果这样做,该函数不再引用emp_task,因此当它作为更新的一部分被调用时,它不会抛出变异的触发器/函数错误。

并且您的函数不应该发出COMMIT - 让调用过程,或者最好是调用过程的会话,决定是应该提交还是回滚谁。

此外,从标题和列名称看起来raise_percent是一个百分比,因此您需要使用它来查找要乘以的值 - 您不应该添加百分比数字。例如,如果在2%加注时为您提供值2,则需要在您的过程中执行此操作:

UPDATE emp_task SET sal = sal * (1 + (sal_incr(grade_id)/100));    

或者更整洁地让你的函数返回1 + (raise_percent/100)并执行:

UPDATE emp_task SET sal = sal * sal_incr(grade_id);

答案 1 :(得分:0)

更改以下程序:

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;