Dit umiddelbare problem med else
altid at blive kaldt er fordi du bruger din indeksvariabel r
direkte i stedet for at slå det relevante kolonnenavn op:
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;
Du viser også kun et id
kolonne i din tabeloprettelse, så når r
er 2
, vil den altid sige, at den indsætter navn
, opdaterer aldrig. Endnu vigtigere, hvis du havde et navn
kolonne og opdaterede kun det for et givet id
, ville denne kode vise id
som at indsætte, når det ikke havde ændret sig. Du skal opdele indsættelsen/opdateringen i separate blokke:
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;
Dette vil stadig sige, at det indsætter navn
selvom kolonnen ikke eksisterer, men jeg antager, at det er en fejl, og jeg gætter på, at du ville prøve at udfylde listen over navne fra user_tab_columns
alligevel, hvis du virkelig vil prøve at gøre det dynamisk.
Jeg er enig med (i hvert fald nogle af) de andre i, at du sandsynligvis ville være bedre stillet med en revisionstabel, der tager en kopi af hele rækken, frem for individuelle kolonner. Din indsigelse ser ud til at være komplikationen ved individuelt at angive, hvilke kolonner der er ændret. Du kan stadig få disse oplysninger, med lidt arbejde, ved at deaktivere revisionstabellen, når du har brug for kolonne-for-kolonne data. For eksempel:
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
Så du har én revisionsrække for hver udført handling, to indsættelser og tre opdateringer. Men du ønsker at se separate data for hver kolonne, der ændrede sig.
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
De fem revisionsprotokoller er blevet til syv opdateringer; de tre opdateringssætninger viser de fem modificerede kolonner. Hvis du kommer til at bruge dette meget, kan du overveje at gøre det til en visning.
Så lad os bryde det lidt ned. Kernen er dette indre udvalg, som bruger lag()
for at hente den forrige værdi af rækken fra den forrige revisionspost for det 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
Det giver os en midlertidig visning, som har alle revisionstabellernes kolonner plus lagkolonnen, som derefter bruges til unpivot()
operation, som du kan bruge, da du har tagget spørgsmålet som 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')
)
Nu har vi en midlertidig visning, som har id, action, when, col, value, prev_value
søjler; i dette tilfælde, da jeg kun har tre kolonner, har det tre gange antallet af rækker i revisionstabellen. Til sidst filtrerer det ydre udvalg denne visning til kun at inkludere de rækker, hvor værdien er ændret, dvs. hvor værdi !=prev_value
(der tages højde for nuller).
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)
Jeg bruger case
at bare printe noget, men du kan selvfølgelig gøre hvad du vil med dataene. Den distinkte
er nødvendig, fordi insert
poster i revisionstabellen konverteres også til tre rækker i den ikke-pivoterede visning, og jeg viser den samme tekst for alle tre fra min første sag
klausul.