2017-07-26 1 views
0

J'ai, après déclencheur INSERT sur une table appelée DM_USER_ROLEORA-04091 Mutating Erreur de table au cours APRÈS INSERT Trigger

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; 

je dois remplir 2 tables séparées quand un insert se produit dans ce tableau, et je pensais que Les déclencheurs AFTER INSERT évitaient les problèmes avec les tables mutantes? Je ne suis pas sûr de ce qui cause, peut-être la lecture dans la deuxième instruction INSERT de DM_USER_ROLE, qui est l'endroit où ce déclencheur est initié ... mais j'étais sous l'impression AFTER INSERTs étaient sûrs pour éviter les mutations, comme le la mise à jour a déjà eu lieu.

erreur est:

ORA-04091: table AZLEARN_BACKUP.DM_USER_ROLE mute, déclencheur/fonction peut ne pas voir

Le premier insert arrive, la seconde ne fonctionne pas.

Cet article m'a amené à croire que les déclencheurs AFTER étaient sûrs.

http://www.dba-oracle.com/t_avoiding_mutating_table_error.htm

------- Mise à jour ---------------

je l'ai changé pour faire ligne par insertion de la ligne à l'aide de deux curseurs paramétrés et il a travaillé ... toujours pas sûr de ce que l'erreur était:

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; 
+0

Si vous pouvez insérer une ligne à la fois pour éviter la mutation, alors la faille apparente dans la logique métier peut être plus facile à voir. – jeff6times7

+0

Fait intéressant, le premier insert arrive, le second n'a pas - j'aurais pensé que cela serait traité comme une transaction. Comment est-ce que j'insérerais une rangée à la fois, avec un curseur? – smackenzie

+1

Je pense que vous devriez implémenter cette logique dans une procédure/fonction. – Dmitry

Répondre

0

la cause la plus probable d'une erreur de table mutationniste est l'utilisation abusive de triggers. Voici un exemple typique:

1.you insérer une ligne dans le tableau Un déclencheur

2.a sur la table A (pour chaque ligne) exécute une requête sur la table A, par exemple pour calculer un résumé colonne

3.Oracle lance une ORA-04091: table A mute, déclenchement/fonction ne peut pas le voir

Ceci est un comportement attendu et normal, Oracle veut vous protéger contre vous-même puisque les garanties Oracle :

• (i) que chaque instruction est atomique (IE échouer ou réussir complètement)

• (ii) que chaque déclaration voit une vue cohérente des données

maintenant en votre déclencheur, lorsque vous faites la deuxième insert, il est fait une jointure sur la table DM_USER_ROLE pour aller chercher les dossiers et c'est la raison pour laquelle vous faites face à

ORA-04091: table AZLEARN_BACKUP.DM_USER_ROLE mute, déclencheur/fonction peut voir pas

1

La raison est tout simplement que vous ne pouvez pas sélectionner de la table DM_USER_ROLE où le déclencheur ROW LEVEL est basé sur.

Dans votre première solution vous avez un

SELECT ... 
FROM DM_USER_ROLE ... 

Ce n'est pas autorisé. Votre deuxième déclencheur ne sélectionne pas la table DM_USER_ROLE et fonctionne donc. Le conseil dans la page liée est correct mais trompeur quand ils indiquent 'Use an "après" ou "au lieu de" trigger "- Ce devrait être plus précisément' Use a" after "ou" au lieu de "trigger". Oracle fournit des déclencheurs en fonction des actions suivantes:

  • DML (INSERT, UPDATE, DELETE) sur une table ou vue
  • instructions DDL (CREATE ou ALTER principalement)
  • événements de base de données, telles que l'ouverture de session/logoff, erreurs ou démarrage/arrêt

Vous avez un déclencheur DML qui peut avoir différents points de chronométrage:

  • Avant l'instruction de déclenchement exécute
  • Après l'instruction de déclenchement exécute
  • Avant chaque ligne que l'instruction de déclenchement affecte
  • Après chaque ligne que l'instruction de déclenchement affecte
  • composé Trigger -> celui-ci combine les quatre déclencheurs énumérés ci-dessus
  • déclencheur INSTEAD OF (uniquement pour les vues)

Beaucoup de gens ratent la différence entre un niveau de déclenchement de l'instruction et un déclencheur de niveau ligne.

Les déclencheurs de niveau ligne ont un mot-clé FOR EACH ROW et s'exécutent pour chaque ligne comme le mot-clé l'indique. Si vous ignorez le mot clé FOR EACH ROW, le déclencheur n'est exécuté qu'une seule fois pour chaque instruction, quel que soit le nombre de lignes affectées par votre instruction INSERT/UPDATE/DELETE.