sql >> Base de Datos >  >> RDS >> Oracle

auditando 50 columnas usando el disparador de Oracle

Su problema inmediato con else siempre se le llama porque está usando su variable de índice r directamente, en lugar de buscar el nombre de la columna relevante:

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;

También solo muestra un id columna en la creación de su tabla, por lo que cuando r es 2 , siempre dirá que está insertando name , nunca actualizando. Más importante aún, si tuviera un name columna y solo estaba actualizando eso para un id dado , este código mostraría el id como insertar cuando no había cambiado. Debe dividir la inserción/actualización en bloques separados:

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;

Esto todavía dirá que está insertando name incluso si la columna no existe, pero supongo que es un error, y supongo que estaría intentando completar la lista de nombres de user_tab_columns de todos modos, si realmente quieres intentar que sea dinámico.

Estoy de acuerdo con (al menos algunos de) los demás en que probablemente estaría mejor con una tabla de auditoría que tome una copia de toda la fila, en lugar de columnas individuales. Su objeción parece ser la complicación de enumerar individualmente qué columnas cambiaron. Todavía podría obtener esta información, con un poco de trabajo, desviando la tabla de auditoría cuando necesite datos columna por columna. Por ejemplo:

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

Así que tiene una fila de auditoría para cada acción realizada, dos inserciones y tres actualizaciones. Pero desea ver datos separados para cada columna que cambió.

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

Los cinco registros de auditoría se han convertido en siete actualizaciones; las tres declaraciones de actualización muestran las cinco columnas modificadas. Si va a usar mucho esto, podría considerar convertirlo en una vista.

Así que analicemos eso un poco. El núcleo es esta selección interna, que usa lag() para obtener el valor anterior de la fila, del registro de auditoría anterior para ese 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

Eso nos da una vista temporal que tiene todas las columnas de las tablas de auditoría más la columna de retraso que luego se usa para el unpivot() operación, que puede usar ya que ha etiquetado la pregunta como 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')
    )

Ahora tenemos una vista temporal que tiene id, action, when, col, value, prev_value columnas; en este caso, como solo tengo tres columnas, eso tiene tres veces el número de filas en la tabla de auditoría. Finalmente, los filtros de selección externos que ven incluyen solo las filas donde el valor ha cambiado, es decir, donde value != prev_value (permitiendo valores nulos).

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)

estoy usando case para simplemente imprimir algo, pero por supuesto puedes hacer lo que quieras con los datos. El distinct es necesario porque insert las entradas en la tabla de auditoría también se convierten en tres filas en la vista sin pivotar, y estoy mostrando el mismo texto para los tres de mi primer case cláusula.