Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

auditando 50 colunas usando o gatilho oracle


Seu problema imediato com o else sempre sendo chamado é porque você está usando sua variável de índice r diretamente, em vez de procurar o nome da coluna 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;

Você também está mostrando apenas um id coluna na criação de sua tabela, então quando r é 2 , ele sempre dirá que está inserindo name , nunca atualizando. Mais importante, se você tivesse um name coluna e estavam atualizando apenas para um determinado id , este código mostrará o id como inserir quando não havia mudado. Você precisa dividir a inserção/atualização em blocos 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;

Isso ainda dirá que está inserindo name mesmo que a coluna não exista, mas presumo que seja um erro e acho que você estaria tentando preencher a lista de nomes de user_tab_columns de qualquer forma, se você realmente quiser tentar torná-lo dinâmico.

Eu concordo com (pelo menos alguns) os outros que você provavelmente estaria melhor com uma tabela de auditoria que tira uma cópia de toda a linha, em vez de colunas individuais. Sua objeção parece ser a complicação de listar individualmente quais colunas foram alteradas. Você ainda pode obter essas informações, com um pouco de trabalho, desarticulando a tabela de auditoria quando precisar de dados coluna por coluna. Por exemplo:
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

Assim, você tem uma linha de auditoria para cada ação realizada, duas inserções e três atualizações. Mas você deseja ver dados separados para cada coluna alterada.
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

Os cinco registros de auditoria se transformaram em sete atualizações; as três instruções de atualização mostram as cinco colunas modificadas. Se você for usar muito isso, considere transformá-lo em uma visualização.

Então vamos quebrar isso um pouco. O núcleo é essa seleção interna, que usa lag() para obter o valor anterior da linha, do registro de auditoria anterior para esse 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

Isso nos dá uma visão temporária que tem todas as colunas das tabelas de auditoria mais a coluna lag que é então usada para o unpivot() operação, que você pode usar, pois marcou a pergunta 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')
    )

Agora temos uma visão temporária que tem id, action, when, col, value, prev_value colunas; neste caso como eu tenho apenas três colunas, que tem três vezes o número de linhas na tabela de auditoria. Por fim, a seleção externa filtra essa visualização para incluir apenas as linhas em que o valor foi alterado, ou seja, onde value != prev_value (permitindo 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)

Estou usando case apenas imprimir algo, mas é claro que você pode fazer o que quiser com os dados. O distinct é necessário porque o insert as entradas na tabela de auditoria também são convertidas em três linhas na exibição não dinâmica e estou mostrando o mesmo texto para todas as três do meu primeiro case cláusula.