Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Prüfung von 50 Spalten mit Oracle-Trigger

Ihr unmittelbares Problem mit dem else immer aufgerufen wird, weil Sie Ihre Indexvariable r verwenden direkt, anstatt nach dem relevanten Spaltennamen zu suchen:

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;

Sie zeigen auch nur eine id an Spalte in Ihrer Tabellenerstellung, also wenn r ist 2 , es wird immer sagen, dass es name einfügt , nie aktualisieren. Noch wichtiger, wenn Sie einen Namen hatten Spalte und haben diese nur für eine bestimmte id aktualisiert , würde dieser Code die id anzeigen als Einfügen, wenn es sich nicht geändert hatte. Sie müssen die Einfügung/Aktualisierung in separate Blöcke aufteilen:

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;

Dies wird immer noch sagen, dass es name einfügt auch wenn die Spalte nicht existiert, aber ich nehme an, das ist ein Fehler, und ich vermute, Sie würden versuchen, die Liste der Namen aus user_tab_columns zu füllen jedenfalls, wenn Sie wirklich versuchen wollen, es dynamisch zu machen.

Ich stimme (zumindest einigen) den anderen zu, dass Sie wahrscheinlich besser dran wären, wenn Sie eine Audit-Tabelle verwenden würden, die eine Kopie der gesamten Zeile anstelle einzelner Spalten erstellt. Ihr Einwand scheint die Komplikation zu sein, die geänderten Spalten einzeln aufzulisten. Sie könnten diese Informationen mit ein wenig Arbeit immer noch erhalten, indem Sie die Audit-Tabelle entpivotieren, wenn Sie spaltenweise Daten benötigen. Zum Beispiel:

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

Sie haben also eine Audit-Zeile für jede durchgeführte Aktion, zwei Einfügungen und drei Aktualisierungen. Sie möchten jedoch separate Daten für jede geänderte Spalte sehen.

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

Aus den fünf Audit-Aufzeichnungen sind sieben Aktualisierungen geworden; Die drei Aktualisierungsanweisungen zeigen die fünf geänderten Spalten. Wenn Sie dies häufig verwenden, sollten Sie es in eine Ansicht umwandeln.

Also lasst uns das ein bisschen aufschlüsseln. Der Kern ist diese innere Auswahl, die lag() um den vorherigen Wert der Zeile aus dem vorherigen Audit-Datensatz für diese id zu erhalten :

        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

Das gibt uns eine temporäre Ansicht, die alle Audit-Tabellenspalten plus die Verzögerungsspalte enthält, die dann für den unpivot() Operation, die Sie verwenden können, da Sie die Frage als 11g:

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

Jetzt haben wir eine temporäre Ansicht mit id, action, when, col, value, prev_value Säulen; Da ich in diesem Fall nur drei Spalten habe, hat das die dreifache Anzahl von Zeilen in der Audit-Tabelle. Schließlich filtert die äußere Auswahl diese Ansicht so, dass sie nur die Zeilen enthält, in denen sich der Wert geändert hat, d. h. wo value !=prev_value (unter Berücksichtigung von Nullen).

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)

Ich verwende case einfach etwas ausdrucken, aber Sie können natürlich mit den Daten machen, was Sie wollen. Der distinct wird benötigt, weil insert Einträge in der Audit-Tabelle werden auch in der nicht-pivozierten Ansicht in drei Zeilen konvertiert, und ich zeige denselben Text für alle drei aus meinem ersten case Klausel.