在触发器 PL/SQL 中调用存储过程

Posted

技术标签:

【中文标题】在触发器 PL/SQL 中调用存储过程【英文标题】:Call a stored procedure in a trigger PL/SQL 【发布时间】:2021-01-06 18:48:11 【问题描述】:

我正在尝试在触发器中调用存储过程。

我遇到的错误

"table %s.%s is mutating, trigger/function may not see it"
*Cause:    A trigger (or a user defined plsql function that is referenced in
           this statement) attempted to look at (or modify) a table that was
           in the middle of being modified by the statement which fired it.
*Action:   Rewrite the trigger (or function) so it does not read that table.

这是我的触发器代码:

create or replace TRIGGER check_salary_trg 
AFTER INSERT OR UPDATE ON employees
FOR EACH ROW

BEGIN 
DBMS_OUTPUT.PUT_LINE(:new.job_id ||' '|| :new.salary);
check_salary(:new.job_id, :new.salary);
END;

这是我的存储过程:

PROCEDURE check_salary (job_id employees.job_id%TYPE, salary employees.salary%TYPE)
IS
maxSal NUMBER;
minSal NUMBER;
BEGIN
SELECT MAX(salary) INTO maxSal FROM employees WHERE job_id = job_id;
SELECT MIN(salary) INTO minSal FROM employees WHERE job_id = job_id;
IF maxSal >= salary OR minSal <= salary THEN
RAISE_APPLICATION_ERROR(-20001,'Invalid slary '||salary||'. Salaries for job '||job_id||' must be between '||minSal||' and '||maxSal);
ELSE
DBMS_OUTPUT.PUT_LINE('Test');
END IF;
END;

这是我尝试查看触发器是否正常工作的方式:

UPDATE employees SET salary = 100000 WHERE employee_id = 100;

不知何故,我的触发器代码中的DBMS_OUTPUT.PUT_LINE 正在工作。但是存储过程会导致错误。

【问题讨论】:

【参考方案1】:

您不能在触发触发器的表上执行SELECT 或任何其他DML(INSERT,UPDATE,DELETE)。您必须使用复合触发器来避免变异表错误。

触发器调用的过程正在对触发触发器的员工表执行 SELECT,并且被 oracle 禁止。

可以在下面找到相同问题的工作示例,请参阅更新部分Trigger selecting child records, multiplying their values and updating parent record

【讨论】:

【参考方案2】:

首先,你为什么在你的程序中查询同一个表两次?这是对资源的巨大浪费……宁可运行

SELECT min(salary), max(salary) INTO minSal, maxSal
FROM employees 
WHERE job_id = job_id

其次,你不能查询你正在更新的同一张表!这是一个巨大的数据(不)一致性问题。你为什么不在一个包/过程中运行它呢?这将使您更好地控制您的流程

类似:

CREATE OR REPLACE PROCEDURE prcd_update_salary(p_emp_id INT, p_salary INT)
IS
 maxSal INT;
 minSal INT;
 job_id INT;
BEGIN


    SELECT job_id, min(salary), max(salary) INTO job_id, minSal, maxSal
    FROM employees 
    WHERE job_id = (SELECT job_id FROM employees WHERE employee_id = p_emp_id);

    IF (p_salary >= minSal AND p_salary <= maxSal) THEN 
          UPDATE employees SET salary = p_salary WHERE employee_id = p_emp_id;
    ELSE 
          dbms_output.put_line('Sorry, this is out of range!')
          dmbs_output.put_line('You can only use from '||minSal||' up to '||maxSal||' for a job id: '|| job_id);
    END IF;
    
END;

当然,这只是一个示例代码,可以为您提供有关如何执行此操作的提示。您将所有逻辑放在一个地方,而不是在两个差异对象中(非常难以调试!!)...在你的生产代码你必须清理输入,可能会做更多检查,当然正确的索引是必须的 - 但这几乎总结了我会做什么:)

【讨论】:

以上是关于在触发器 PL/SQL 中调用存储过程的主要内容,如果未能解决你的问题,请参考以下文章

触发存储过程运行而不是 INSERT (PL/SQL)

pl/sql编程基础

PL/SQL练习命名块: 存储过程函数触发器包

Oracle笔记4-pl/sql-分支/循环/游标/异常/存储/调用/触发器

Oracle --- 存储过程函数包游标触发器

Oracle --- 存储过程函数包游标触发器