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.