0
votes

déclencheurs génériques dans oracle

J'écris un déclencheur oracle générique. Supposons qu'il existe de nombreuses tables principales telles que PERSON_INFO, EMPLOYEE_INFO, etc. et leurs tables d'audit correspondantes telles que PERSON_INFO_AUDIT, EMPLOYEE_INFO_AUDIT. La structure est donnée ci-dessous.

PERSON_INFO a des colonnes: -

INSERT INTO PERSON_INFO_AUDIT(PERSON_ID,FIRST_NAME,LAST_NAME,AUDIT_DATE,OPERATIONS) VALUES(:OLD.PERSON_ID,:OLD.FIRST_NAME,:OLD.LAST_NAME,30-10-19,'U');

PERSON_INFO_AUDIT a toutes les colonnes de PERSON_INFO ainsi que deux colonnes supplémentaires OPERATIONS et AUDIT_DATE.

La condition est que si l'une des tables principales est mise à jour ou si l'une des lignes de la table principale est supprimée, les anciennes entrées de la table principale doivent être insérées dans leur table d'audit correspondante.

puis j'écris une mise à jour comme: -

UPDATE PERSON_INFO SET FIRST_NAME = 'John';

alors les anciennes valeurs de PERSON_INFO doivent être insérées dans le tableau PERSON_INFO_AUDIT comme ci-dessous: -

PERSON_INFO_AUDIT doit maintenant contenir : -

CREATE OR replace TRIGGER trig_PERSON_INFO_deleteupdate
    after UPDATE OR DELETE 
    ON PERSON_INFO
    FOR EACH ROW
DECLARE
     base_table_name clob;
     audit_table_name clob;
     base_table_cols_in_string clob;
     audit_table_cols_in_string clob;
     operation char;
     final_query clob;    
BEGIN
     base_table_name:= 'PERSON_INFO';
     audit_table_name := base_table_name || '_AUDIT';

     IF UPDATING THEN 
          operation:= 'U';
     ELSE
          operation:= 'D';
     END IF;

     SELECT LISTAGG(COLUMN_NAME, ',') WITHIN GROUP (ORDER BY column_id)
     INTO base_table_cols_in_string
     FROM ALL_TAB_COLUMNS
     WHERE TABLE_NAME= 'PERSON_INFO';

     audit_table_cols_in_string:= base_table_cols_in_string || ',AUDIT_DATE,OPERATIONS';

     final_query:= 'INSERT INTO ' ||  audit_table_name || '(' || audit_table_cols_in_string || ') VALUES(' || ':OLD.PERSON_ID,:OLD.FIRST_NAME,:OLD.LAST_NAME,' || SYSDATE || ',''' || operation || ''');';

     dbms_output.put_line(final_query); 
     EXECUTE IMMEDIATE final_query;
END;

Ici, audit_date est la date du jour et operations indique si les lignes de la table principale ont été supprimées (D) ou mises à jour (U). Pour faciliter le scénario ci-dessus, j'ai écrit la fonction de déclenchement suivante.

-------------------------------------------------------------------------
|                         PERSON_INFO_AUDIT                              |
-------------------------------------------------------------------------
|  PERSON_ID   |   FIRST_NAME     |  LAST_NAME |  AUDIT_DATE | OPERATIONS|
|   (NUMBER)   |   (VARCHAR2)     |  (VARCHAR2)| (TIMESTAMP) |   (CHAR)  |
-------------------------------------------------------------------------
|    1         |   Andrew         |  Jack      |  30-08-2019 |     U     |
-------------------------------------------------------------------------

La requête formée est:

            ------------------------------------------------
            |                 PERSON_INFO                  |
            ------------------------------------------------
            |  PERSON_ID   |   FIRST_NAME     |  LAST_NAME |
            |   (NUMBER)   |   (VARCHAR2)     |  (VARCHAR2)|
            ------------------------------------------------
            |    1         |   Andrew         |  Jack      |
            ------------------------------------------------

Cependant si j'essaie d'exécuter la requête en utilisant EXECUTE IMMEDIATE final_query puis i j'obtiens une erreur


8 commentaires

supprimez simplement le point-virgule avant le dernier guillemet simple.


@ BarbarosÖzhan j'ai essayé mais cela n'a pas fonctionné. Je pense que c'est quelque chose lié au déclencheur dynamique


@ BarbarosÖzhan, l'exigence est de ne conserver que les anciennes valeurs dans la table d'audit. les nouvelles valeurs seront de toute façon présentes dans la table principale


Ce que vous essayez de faire n'est pas possible. Il n'y a pas de moyen dynamique d'accéder aux champs dans : old et : new . Ce qui est généralement fait à la place est de lire DBA_TAB_COLUMNS et de créer dynamiquement l'intégralité du déclencheur.


@MatthewMcPeak j'ai terminé la même chose dans les déclencheurs postgres. Maintenant, j'essaie d'y parvenir dans les déclencheurs oracle.


@Abhishek Je suppose que cela dépend de ce que vous entendez par «générique». Je remarque dans votre OP que le code PL / SQL qui construit votre final_query fait référence aux champs par leur nom. Vous auriez besoin de réécrire cette partie de votre déclencheur pour chaque table. Est-ce votre compréhension / vos attentes?


Laissez Oracle prendre la pression. Utilisez Flashback Data Archive - disponible pour Oracle 11.2.0.4 et versions ultérieures.


Je remarque que vous créez la liste de colonnes base_table_cols_in_string à partir de ALL_TAB_COLUMNS pour le nom de table spécifié, mais lorsque vous codez en dur la clause values ​​ correspondante qui doit correspondre, vous pouvez également coder en dur les deux . De plus, vous concaténez la valeur de sysdate au lieu d'inclure simplement le mot-clé sysdate dans l'instruction générée ou de le définir comme colonne par défaut. Mais rien de tout cela n'a d'importance car les déclencheurs ne peuvent pas fournir dynamiquement les valeurs : new ou : old , vous devez donc tout coder en dur (ou générer le déclencheur complet).


3 Réponses :


1
votes

Rédaction de ma solution au problème de compilation comme l'autre solution publiée est mieux adaptée à la question du PO, mais je veux le morceau discursif à rester.


Cependant, nous devons vraiment nous demander si la génération d'une instruction d'insertion dynamique est la meilleure solution. Tout d'abord, vous devez également générer la projection de la clause VALUES, sinon le dynamisme ne sert à rien. Si la structure de la table change, vous devez modifier les deux ensembles de colonnes. De plus, le nom de la table d'audit est fixe (car le nom de la table qui possède le déclencheur est fixe). Alors, quel est le retour sur la génération de l'instruction INSERT à chaque fois? Comparez-le au risque (et à la surcharge) du DML dynamique.

L'idée même des "déclencheurs génériques" dans Oracle est erronée. SQL est un langage fortement typé, tout comme PL / SQL. Ils fonctionnent avec des structures de données prédéfinies. Un déclencheur appartient à une table et fonctionne avec la structure actuelle de la table. Toute solution d'audit doit donc reconnaître ce fait: travailler avec le grain du SGBDR et non contre lui.

Une meilleure approche serait de générer le DDL pour le déclencheur à partir du dictionnaire de données, et de lui faire exécuter une instruction d'insertion statique. Oui, vous devrez régénérer le code de déclenchement chaque fois que vous modifiez la structure de la table, mais franchement, si vous changez si souvent les structures de vos tables, cela devient un fardeau, vous avez de plus gros problèmes dans votre processus de modélisation que vous devez résoudre. .


Un dernier point. Depuis Oracle 11.2.0.4, il n’a été nul besoin du tout d’écrire ce type de déclencheurs d’audit. Oracle a une capacité appelée Flashback Data Archive (précédemment appelée Total Recall) qui journalise automatiquement les tables que nous souhaitons. L'utilisation de fonctionnalités intégrées est toujours préférable à la diffusion de notre propre code. Donc, si vous utilisez une version d'Oracle qui a cette capacité, vous devez absolument l'utiliser. En savoir plus .


0 commentaires

1
votes

La raison de cette erreur est:

  1. Il y a un point-virgule à la fin de la requête dynamique. Supprimez ce point-virgule.
  2. Les anciennes valeurs de la table doivent être écrites sous forme de nom de variable / colonne et non de constante (ne doit pas écrire d'anciennes valeurs dans '') lors de la préparation d'une requête dynamique.
  3. Sysdate doit être écrit correctement.

Veuillez trouver ci-dessous le code corrigé:

create or replace TRIGGER trig_PERSON_INFO_deleteupdate
after UPDATE OR DELETE 
ON PERSON_INFO
FOR EACH ROW
DECLARE

 base_table_name clob;
 audit_table_name clob;
 base_table_cols_in_string clob;
 audit_table_cols_in_string clob;
 operation char;
 final_query clob;      
BEGIN

 base_table_name:= 'PERSON_INFO';
 audit_table_name := base_table_name || '_AUDIT';

 IF UPDATING THEN 
      operation:= 'U';
 ELSE
      operation:= 'D';
 END IF;

 SELECT LISTAGG(COLUMN_NAME, ',') WITHIN GROUP (ORDER BY column_id)
 INTO base_table_cols_in_string
 FROM ALL_TAB_COLUMNS
 WHERE TABLE_NAME= 'PERSON_INFO';

 audit_table_cols_in_string:= base_table_cols_in_string || ',AUDIT_DATE,OPERATIONS';

 final_query:= 'INSERT INTO ' ||  audit_table_name || '(' || audit_table_cols_in_string 
    || ') VALUES(''' || :OLD.PERSON_ID || ''',''' || :OLD.FIRST_NAME || ''',''' || :OLD.LAST_NAME 
    || ''',date ''' || to_char(SYSDATE,'yyyy-mm-dd)' || ''',''' || operation || ''')';

 dbms_output.put_line(final_query); 
 EXECUTE IMMEDIATE final_query;
END;

J'espère que cela vous aidera :)


0 commentaires

0
votes

J'ai trouvé une solution générique pour ma réponse. Mon exigence était qu'il existe de nombreuses tables de base et leurs tables d'audit correspondantes. Si l'une des tables de base est mise à jour / supprimée, je veux que leurs entrées correspondantes soient insérées dans leur table d'audit. Pour cela, je voulais utiliser une fonction commune qui effectue ce travail de manière dynamique. J'ai trouvé une solution et j'en ai posté une: -

CREATE OR REPLACE procedure IR_DEV.audit_trigger(main_table_name varchar2)
AS
    audit_table_name clob;
    main_table_col_list clob;
    trig_struct clob;
BEGIN

audit_table_name := main_table_name || '_AUDIT';

select LISTAGG(COLUMN_NAME,',') WITHIN GROUP(ORDER BY column_id)
into main_table_col_list
from COLS where table_name=upper(main_table_name);

trig_struct:='CREATE or REPLACE TRIGGER trig_'||main_table_name ||'_deleteupdate'||chr(10)
             ||'AFTER UPDATE OR DELETE ON '|| main_table_name||chr(10)  
             ||'FOR EACH ROW'||chr(10)
             ||'DECLARE'||chr(10)
             ||'   opt varchar2(1);'||chr(10)
             ||'BEGIN'||chr(10)
             ||'   IF UPDATING THEN'||chr(10) 
             ||'       opt:=''U'';'||chr(10)
             ||'   ELSE'||chr(10)    
             ||'       opt:=''D'';'||chr(10)
             ||'   END IF;'||chr(10)||chr(10)

             ||'   INSERT INTO ' || audit_table_name || ' ('||main_table_col_list||',audit_date,operations )'||chr(10)
             ||'    VALUES ('||':old.'||REPLACE(main_table_col_list,',',',:old.')||',sysdate,opt);'||chr(10)||chr(10)

             ||'END;';

           dbms_output.put_line(trig_struct); 
           execute immediate trig_struct;        
END;

Tout commentaire / suggestion est très apprécié.


0 commentaires