如何避免“表变异”错误

Posted

技术标签:

【中文标题】如何避免“表变异”错误【英文标题】:How to avoid "table mutating" errors 【发布时间】:2016-10-29 00:13:32 【问题描述】:

我有一个触发器需要在删除一行后从表中读取。本质上,我需要计算与当前行相似的剩余行,如果该计数为零,则更新其他地方的字段。

经过两天的反复琢磨,我无法弄清楚如何重组我的思维过程以允许我这样做。这是一个例子:

CREATE OR REPLACE TRIGGER Di_PatMustBeWell
AFTER DELETE 
    ON Diagnosis
    FOR EACH ROW
Declare
    --PRAGMA AUTONOMOUS_TRANSACTION;
    NumDiseases Number;
BEGIN
    SELECT NUMDISEASES INTO Numdiseases
    FROM DiagnosisCount
    where Di_Patient = :OLD.Di_Patient;

    IF( NumDiseases !=  1 ) THEN
        UPDATE Patient SET Pat_Sick = 0 WHERE Pat_Person = :OLD.Di_Patient;
    END IF;
END;
/

【问题讨论】:

【参考方案1】:

简短的回答 - 没有触发器,没有变异。


您可以使用带有pragma autonomous_transaction 的触发器来计算某些患者的剩余诊断数,但不建议这样做。 最好创建新功能或程序来实现您对已删除诊断的逻辑。像这样的:

create table Diagnosis as select 456 idDiseases, 123 di_patient from dual;
/
create table diagnosisCount as select 1 numDiseases, 123 di_patient from dual;
/
create table Patient as select 123 Pat_Person, 1 Pat_Sick from dual;
/
drop trigger di_patmustbewell;

create or replace function deleteDiagnosis(idDiseases number) return number is
    rows_ number;
    di_patient number;
    Numdiseases number;
begin
    <<del>> begin 
        delete Diagnosis where IdDiseases = deleteDiagnosis.IdDiseases
        returning Diagnosis.di_patient into deleteDiagnosis.di_patient
        ;
        rows_ := sql%rowcount;
        if rows_ != 1 then raise too_many_rows; end if;
    end del;
    select count(1) into deleteDiagnosis.numDiseases from Diagnosis where Di_Patient = deleteDiagnosis.di_patient;
    if deleteDiagnosis.numdiseases = 0 then <<upd>> begin 
        update Patient set Pat_Sick = 0 where Pat_Person = deleteDiagnosis.di_patient;
        exception when others then 
            dbms_output.put_line('Cannot update Patient di_patient='||di_patient);
            raise;
    end upd; end if;
    return rows_;
end;
/
show errors

declare rows_ number :=  deleteDiagnosis(456);
begin dbms_output.put_line('deleted '||rows_||' rows'); end;
/

deleted 1 rows

select * from Patient;
PAT_PERSON   PAT_SICK
---------- ----------
       123          0

如果您更喜欢(或必须)在应用程序中使用触发器,另一种解决方案 - 在触发器主体中声明返回患者诊断计数的内部函数:

create or replace trigger di_patmustbewell
after delete on diagnosis for each row
declare
    numdiseases number;
    function getNumDiagnosis (di_patient number) return number is
        ret number;
        pragma autonomous_transaction;
    begin
        select count(1) into ret from diagnosis where di_patient = getNumDiagnosis.di_patient;
        return ret;
    end getNumDiagnosis;    
begin
    numDiseases := getNumDiagnosis(:old.di_patient);
    if(numdiseases = 0) then
        update patient set pat_sick = 0 where pat_person = :old.di_patient;
    end if;
end;
/
show errors;

Trigger DI_PATMUSTBEWELL compiled

希望对你有所帮助。

【讨论】:

这很有帮助!我很确定类似于您的第一个建议的方法可以正常工作,因为我正在构建它以与其他人正在编写的 Java 应用程序交互,因此向他们提供删除疾病的程序可能与使用触发器一样好.谢谢! 这与我实现所需功能的方式非常接近,所以现在它已经完成并且可以工作了,我已经接受了你的回答!【参考方案2】:

您可以为此类情况创建 COMPOUND 触发器:

create or replace TRIGGER Di_PatMustBeWell
FOR DELETE ON Diagnosis
COMPOUND TRIGGER

    TYPE Di_Patient_Table_type IS TABLE OF DiagnosisCount.Di_Patient%TYPE;
    Di_Patient_Table Di_Patient_Table_type;

     BEFORE STATEMENT IS
      BEGIN
        Di_Patient_Table := Di_Patient_Table_type();
     END BEFORE STATEMENT;

     BEFORE EACH ROW IS
     BEGIN
        Di_Patient_Table.EXTEND;
        Di_Patient_Table(Di_Patient_Table.LAST) := :OLD.Di_Patient;
     END BEFORE EACH ROW;

    AFTER STATEMENT IS
    BEGIN
       FOR i IN Di_Patient_Table.FIRST..Di_Patient_Table.LAST LOOP
          SELECT NUMDISEASES INTO Numdiseases
          FROM DiagnosisCount
          where Di_Patient = Di_Patient_Table(i);

          IF NumDiseases !=  1 THEN
            UPDATE Patient SET Pat_Sick = 0 WHERE Pat_Person = Di_Patient_Table(i);
          END IF;
       END LOOP;
       Di_Patient_Table.DELETE;
     END AFTER STATEMENT;
   END;
/

【讨论】:

以上是关于如何避免“表变异”错误的主要内容,如果未能解决你的问题,请参考以下文章

如何避免“意外模块'HttpClientModule'...注释”错误?

如何避免从 ExpressJS 抛出 400 错误

如何避免 bash 脚本上的 sed 错误?

如何避免嵌套事务不支持错误?

数据库连接失败时,如何捕捉错误,避免弹出错误提示,以便下次自动重新连接

如何避免在错误响应中将错误集合包装在 Apollo Server V2 中的错误对象中