【发布时间】:2018-01-02 09:35:14
【问题描述】:
我在名为 DM_USER_ROLE 的表上有以下 AFTER INSERT 触发器
create or replace TRIGGER "DM_USER_ROLE_T1"
AFTER
insert on "DM_USER_ROLE"
for each row
DECLARE
v_cert_enrolment_id number;
v_user_role_id number;
begin
v_cert_enrolment_id := "DM_CERTIFICATION_ENROLMEN_SEQ".nextval;
v_user_role_id := :new.USER_ROLE_ID;
/*
When a user is assigned a role, we create an enrolment record
in DM Certification record linked to this user/role combination.
We also insert into the DM_COURSE_ENROLMENT table the courses
associated with the certfication
*/
--FIRST AN ENROLMENT RECORD IS CREATED IN DM_CERTIFICATION_ENROLMENT
INSERT INTO DM_CERTIFICATION_ENROLMENT
(CERTIFICATION_ENROLMENT_ID, ALLOCATED_DT, DEADLINE_DATE, STATUS, USER_ROLE_ID)
VALUES
(
v_cert_enrolment_id,
trunc(sysdate),
trunc(sysdate) + 60,
'Enrolled',
v_user_role_id
);
--COURSES LINKED TO THE CERTIFICATION ARE INSERTED INTO DM_COURSE_ENROLMENT
INSERT INTO DM_COURSE_ENROLMENT
(
CERTIFICATION_ENROLMENT_ID,
COURSE_ID,
ALLOCATED_DT,
DEADLINE_DT,
STATUS
)
SELECT v_cert_enrolment_id,
COURSE.COURSE_ID,
trunc(sysdate),
trunc(sysdate) + 60,
'Enrolled'
FROM DM_CERTIFICATION_COURSE COURSE
WHERE CERTIFICATION_ID =
(
SELECT C.CERTIFICATION_ID FROM
DM_CERTIFICATION_ENROLMENT A,
DM_USER_ROLE B,
DM_ROLE_CERTIFICATION C
WHERE
A.USER_ROLE_ID = B.USER_ROLE_ID
AND
B.ROLE_ID = C.ROLE_ID
AND
A.CERTIFICATION_ENROLMENT_ID = v_cert_enrolment_id
);
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQLERRM(-20299)));
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM, 1, 2000));
end;
当在此表中发生插入时,我需要填充 2 个单独的表,并且我认为 AFTER INSERT 触发器避免了变异表的问题?
我不确定是什么原因造成的,可能是从 DM_USER_ROLE 读取的第二个 INSERT 语句,这是触发此触发器的位置...但我的印象是,在 INSERT 之后可以安全地避免突变,因为更新已经发生了。
错误是:
ORA-04091: 表 AZLEARN_BACKUP.DM_USER_ROLE 正在变异, 触发器/函数可能看不到它
第一次插入发生,第二次没有。
这篇文章让我相信 AFTER 触发器是安全的。
http://www.dba-oracle.com/t_avoiding_mutating_table_error.htm
-------更新---------------
我将其更改为使用两个参数化游标进行逐行插入,并且它有效...仍然不确定错误是什么:
create or replace TRIGGER "DM_USER_ROLE_T1"
AFTER
insert on "DM_USER_ROLE"
for each row
DECLARE
v_cert_enrolment_id number;
v_user_role_id number;
v_role_id number;
v_certification_id number;
cursor certs_for_role(p_role_id number) is
select * from DM_ROLE_CERTIFICATION where ROLE_ID = p_role_id;
r_certs_for_role certs_for_role%rowtype;
cursor courses_for_certs(p_cert_id number) is
select * from DM_CERTIFICATION_COURSE where CERTIFICATION_ID = p_cert_id;
r_courses_for_certs courses_for_certs%rowtype;
begin
v_user_role_id := :new.USER_ROLE_ID;
v_role_id := :new.ROLE_ID;
open certs_for_role(v_role_id);
loop
fetch certs_for_role into r_certs_for_role;
exit when certs_for_role%notfound;
v_cert_enrolment_id := "DM_CERTIFICATION_ENROLMEN_SEQ".nextval;
INSERT INTO DM_CERTIFICATION_ENROLMENT
(CERTIFICATION_ENROLMENT_ID, ALLOCATED_DT, DEADLINE_DATE, STATUS, USER_ROLE_ID, CERTIFICATION_ID)
VALUES
(
v_cert_enrolment_id,
trunc(sysdate),
trunc(sysdate) + 60,
'Enrolled',
v_user_role_id,
r_certs_for_role.CERTIFICATION_ID
);
open courses_for_certs(r_certs_for_role.CERTIFICATION_ID);
loop
fetch courses_for_certs into r_courses_for_certs;
exit when courses_for_certs%notfound;
INSERT INTO DM_COURSE_ENROLMENT
(
CERTIFICATION_ENROLMENT_ID,
COURSE_ID,
ALLOCATED_DT,
DEADLINE_DT,
STATUS
)
VALUES
(
v_cert_enrolment_id,
r_courses_for_certs.COURSE_ID,
trunc(sysdate),
trunc(sysdate) + 60,
'Enrolled'
);
end loop;
close courses_for_certs;
end loop;
close certs_for_role;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQLERRM(-20299)));
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM, 1, 2000));
end;
【问题讨论】:
-
如果可以一次插入一行来避免突变,那么业务逻辑中的明显缺陷可能更容易看到。
-
有趣的是,第一次插入发生了,第二次没有发生——我原以为这会被视为事务。如何使用光标一次插入一行?
-
我认为,你应该在一个过程/函数中实现这个逻辑。
标签: oracle plsql oracle11g database-trigger