Oracle
 sql >> Base de données >  >> RDS >> Oracle

audit de 50 colonnes à l'aide du déclencheur oracle

Votre problème immédiat avec le else toujours appelé, c'est parce que vous utilisez votre variable d'index r directement, plutôt que de rechercher le nom de la colonne pertinente :

for r in v_tab_col_nt.first..v_tab_col_nt.last
loop
    if updating(v_tab_col_nt(r)) then
        insert into data_table values(1,'i am updating '||v_tab_col_nt(r));
    else
        insert into data_table values(2,'i am inserting '||v_tab_col_nt(r));
    end if;
end loop;

Vous ne montrez également qu'un id colonne dans la création de votre table, donc quand r est 2 , il dira toujours qu'il insère name , jamais mis à jour. Plus important encore, si vous aviez un name colonne et ne mettaient à jour que pour un id donné , ce code afficherait le id comme insertion alors qu'il n'avait pas changé. Vous devez diviser l'insertion/la mise à jour en blocs distincts :

if updating then
    for r in v_tab_col_nt.first..v_tab_col_nt.last loop
        if updating(v_tab_col_nt(r)) then
            insert into data_table values(1,'i am updating '||v_tab_col_nt(r));
        end if;
    end loop;
else /* inserting */
    for r in v_tab_col_nt.first..v_tab_col_nt.last loop
        insert into data_table values(2,'i am inserting '||v_tab_col_nt(r));
    end loop;
end if;

Cela dira toujours qu'il insère name même si la colonne n'existe pas, mais je suppose que c'est une erreur, et je suppose que vous essayez de remplir la liste des noms de user_tab_columns de toute façon si vous voulez vraiment essayer de le rendre dynamique.

Je suis d'accord avec (au moins certains des) autres que vous seriez probablement mieux avec une table d'audit qui prend une copie de la ligne entière, plutôt que des colonnes individuelles. Votre objection semble être la complication de la liste individuelle des colonnes modifiées. Vous pouvez toujours obtenir ces informations, avec un peu de travail, en désactivant la table d'audit lorsque vous avez besoin de données colonne par colonne. Par exemple :

create table temp12(id number, col1 number, col2 number, col3 number);
create table temp12_audit(id number, col1 number, col2 number, col3 number,
    action char(1), when timestamp);

create or replace trigger temp12_trig
before update or insert on temp12
for each row
declare
    l_action char(1);
begin
    if inserting then
        l_action := 'I';
    else
        l_action := 'U';
    end if;

    insert into temp12_audit(id, col1, col2, col3, action, when)
    values (:new.id, :new.col1, :new.col2, :new.col3, l_action, systimestamp);
end;
/

insert into temp12(id, col1, col2, col3) values (123, 1, 2, 3);
insert into temp12(id, col1, col2, col3) values (456, 4, 5, 6);
update temp12 set col1 = 9, col2 = 8 where id = 123;
update temp12 set col1 = 7, col3 = 9 where id = 456;
update temp12 set col3 = 7 where id = 123;

select * from temp12_audit order by when;

        ID       COL1       COL2       COL3 A WHEN
---------- ---------- ---------- ---------- - -------------------------
       123          1          2          3 I 29/06/2012 15:07:47.349
       456          4          5          6 I 29/06/2012 15:07:47.357
       123          9          8          3 U 29/06/2012 15:07:47.366
       456          7          5          9 U 29/06/2012 15:07:47.369
       123          9          8          7 U 29/06/2012 15:07:47.371

Vous avez donc une ligne d'audit pour chaque action entreprise, deux insertions et trois mises à jour. Mais vous voulez voir des données distinctes pour chaque colonne qui a changé.

select distinct id, when,
    case
        when action = 'I' then 'Record inserted'
        when prev_value is null and value is not null
            then col || ' set to ' || value
        when prev_value is not null and value is null
            then col || ' set to null'
        else col || ' changed from ' || prev_value || ' to ' || value
    end as change
from (
    select *
    from (
        select id,
            col1, lag(col1) over (partition by id order by when) as prev_col1,
            col2, lag(col2) over (partition by id order by when) as prev_col2,
            col3, lag(col3) over (partition by id order by when) as prev_col3,
            action, when
        from temp12_audit
    )
    unpivot ((value, prev_value) for col in (
        (col1, prev_col1) as 'col1',
        (col2, prev_col2) as 'col2',
        (col3, prev_col3) as 'col3')
    )
)
where value != prev_value
    or (value is null and prev_value is not null)
    or (value is not null and prev_value is null)
order by when, id;

        ID WHEN                      CHANGE
---------- ------------------------- -------------------------
       123 29/06/2012 15:07:47.349   Record inserted
       456 29/06/2012 15:07:47.357   Record inserted
       123 29/06/2012 15:07:47.366   col1 changed from 1 to 9
       123 29/06/2012 15:07:47.366   col2 changed from 2 to 8
       456 29/06/2012 15:07:47.369   col1 changed from 4 to 7
       456 29/06/2012 15:07:47.369   col3 changed from 6 to 9
       123 29/06/2012 15:07:47.371   col3 changed from 3 to 7

Les cinq enregistrements d'audit se sont transformés en sept mises à jour ; les trois instructions de mise à jour montrent les cinq colonnes modifiées. Si vous l'utilisez souvent, vous pouvez envisager d'en faire une vue.

Alors, décomposons cela un peu. Le noyau est cette sélection interne, qui utilise lag() pour obtenir la valeur précédente de la ligne, à partir de l'enregistrement d'audit précédent pour cet id :

        select id,
            col1, lag(col1) over (partition by id order by when) as prev_col1,
            col2, lag(col2) over (partition by id order by when) as prev_col2,
            col3, lag(col3) over (partition by id order by when) as prev_col3,
            action, when
        from temp12_audit

Cela nous donne une vue temporaire qui contient toutes les colonnes des tables d'audit plus la colonne de décalage qui est ensuite utilisée pour le unpivot() opération, que vous pouvez utiliser car vous avez marqué la question comme 11g :

    select *
    from (
        ...
    )
    unpivot ((value, prev_value) for col in (
        (col1, prev_col1) as 'col1',
        (col2, prev_col2) as 'col2',
        (col3, prev_col3) as 'col3')
    )

Nous avons maintenant une vue temporaire qui a id, action, when, col, value, prev_value Colonnes; dans ce cas, comme je n'ai que trois colonnes, cela a trois fois le nombre de lignes dans la table d'audit. Enfin, les filtres de sélection externes qui s'affichent pour n'inclure que les lignes où la valeur a changé, c'est-à-dire où value != prev_value (autorisant les valeurs nulles).

select
    ...
from (
    ...
)
where value != prev_value
    or (value is null and prev_value is not null)
    or (value is not null and prev_value is null)

J'utilise case simplement imprimer quelque chose, mais bien sûr, vous pouvez faire ce que vous voulez avec les données. Le distinct est nécessaire car le insert les entrées de la table d'audit sont également converties en trois lignes dans la vue non pivotée, et j'affiche le même texte pour les trois depuis mon premier case clause.