如何避免 "table mutating" 错误
How to avoid "table mutating" errors
我有一个触发器需要在删除行后从 table 中读取。本质上,我需要计算与当前行相似的剩余行,如果该计数为零,则更新别处的字段。
经过两天的反复思考,我一直无法弄清楚如何重组我的思维过程来让我做到这一点。这是一个例子:
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;
/
简短回答 - 没有触发器,没有变异。
您可以使用带有 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
希望对您有所帮助。
您可以为这种情况创建复合触发器:
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;
/
我有一个触发器需要在删除行后从 table 中读取。本质上,我需要计算与当前行相似的剩余行,如果该计数为零,则更新别处的字段。
经过两天的反复思考,我一直无法弄清楚如何重组我的思维过程来让我做到这一点。这是一个例子:
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;
/
简短回答 - 没有触发器,没有变异。
您可以使用带有 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
希望对您有所帮助。
您可以为这种情况创建复合触发器:
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;
/