Oracle
 sql >> Database >  >> RDS >> Oracle

controllo di 50 colonne utilizzando Oracle Trigger

Il tuo problema immediato con else essere sempre chiamato è perché stai usando la tua variabile di indice r direttamente, invece di cercare il nome della colonna 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;

Stai anche mostrando solo un id colonna nella creazione della tabella, quindi quando r è 2 , dirà sempre che sta inserendo name , mai aggiornato. Ancora più importante, se avevi un name colonna e lo stavo aggiornando solo per un determinato id , questo codice mostrerebbe l'id come inserimento quando non era cambiato. Devi dividere l'inserimento/l'aggiornamento in blocchi separati:

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;

Questo dirà ancora che sta inserendo name anche se la colonna non esiste, ma presumo che sia un errore e immagino che proveresti a popolare l'elenco di nomi da user_tab_columns comunque se proprio vuoi provare a renderlo dinamico.

Sono d'accordo con (almeno con alcuni) gli altri che probabilmente staresti meglio con una tabella di controllo che prende una copia dell'intera riga, piuttosto che singole colonne. La tua obiezione sembra essere la complicazione dell'elencare individualmente quali colonne sono state modificate. È comunque possibile ottenere queste informazioni, con un po' di lavoro, annullando il pivot della tabella di controllo quando sono necessari dati colonna per colonna. Ad esempio:

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

Quindi hai una riga di controllo per ogni azione intrapresa, due inserimenti e tre aggiornamenti. Ma vuoi vedere dati separati per ogni colonna che è cambiata.

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

I cinque record di audit si sono trasformati in sette aggiornamenti; le tre istruzioni di aggiornamento mostrano le cinque colonne modificate. Se lo utilizzerai spesso, potresti considerare di trasformarlo in una vista.

Quindi analizziamolo un po'. Il nucleo è questa selezione interna, che utilizza lag() per ottenere il valore precedente della riga, dal record di controllo precedente per quell'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

Questo ci fornisce una vista temporanea che ha tutte le colonne delle tabelle di controllo più la colonna del ritardo che viene quindi utilizzata per unpivot() operazione, che puoi utilizzare poiché hai contrassegnato la domanda come 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')
    )

Ora abbiamo una vista temporanea che ha id, action, when, col, value, prev_value colonne; in questo caso, poiché ho solo tre colonne, che ha tre volte il numero di righe nella tabella di controllo. Infine i filtri di selezione esterni che visualizzano per includere solo le righe in cui il valore è cambiato, ovvero dove value != prev_value (consentendo i valori nulli).

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)

Sto usando case per stampare semplicemente qualcosa, ma ovviamente puoi fare quello che vuoi con i dati. Il distinct è necessario perché insert anche le voci nella tabella di controllo vengono convertite in tre righe nella vista non pivot e sto mostrando lo stesso testo per tutti e tre dal mio primo case clausola.