I am working in Greenplum DB.
Getting strange result in First_value window function when in order by clause passing string value which is common in all rows then its always return the row which was first inserted but ideally it should return any value.Below is my code...
create temporary table test_first_value (id int,statename
varchar(50),episodeid int,
episodedate date) distributed by (id);
insert into test_first_value values(12,'MP',9863,'2015-11-06');
insert into test_first_value values(12,'MP',98123,'2009-11-06');
insert into test_first_value values(12,'MP',90123,'2017-03-06');
insert into test_first_value values(12,'MP',44567,'2013-03-17');
insert into test_first_value values(13,'MP',189300,'2013-03-17');
insert into test_first_value values(13,'MP',443467,'2016-03-19');
It is always returning the same value which was inserted first that is episodeid = 9863 for id = 12 and episodeid = 189300 for id = 13
Select *,
First_value(episodeid) over(partition by id order by statename) as
first_episodeid,
First_value(episodedate) over(partition by id order by statename) as
first_episodedate
from
test_first_value;
Now If I change my insert order then it will always return the row value which was inserted first that is episodeid = 98123 for id = 12 and episodeid = 443467 for id = 13
delete from test_first_value;
insert into test_first_value values(12,'MP',98123,'2009-11-06');
insert into test_first_value values(12,'MP',90123,'2017-03-06');
insert into test_first_value values(12,'MP',44567,'2013-03-17');
insert into test_first_value values(12,'MP',9863,'2015-11-06');
insert into test_first_value values(13,'MP',443467,'2016-03-19');
insert into test_first_value values(13,'MP',189300,'2013-03-17');
Select *,
First_value(episodeid) over(partition by id order by statename) as
first_episodeid,
First_value(episodedate) over(partition by id order by statename) as
first_episodedate
from
test_first_value;
Please help me, where I am doing wrong.
Your code works fine. This is your window function:
First_value(episodeid) over(partition by id order by statename)
As you yourself show in the data, you have multiple rows for an id with the same statename. In this situation, the database will return an arbitrary and indeterminate value from the matching keys.
Another way to say this is that sorting is not stable in relational databases. The reason is simple: Tables represent unordered sets. If the sort keys are all the same, then there is no natural ordering to use instead.
So, find another key so the order by uniquely identifies each row. That means that the results will be stable, because the desired row will be uniquely identified. In your data, you can add episode_date as a second key to the order by.
Related
I need to create a trigger in oracle 11g for auditing a table .
I have a table with 50 columns that need to be audited.
For every new insert into a table ,i need to put an entry in audit table (1 row).
For every update ,suppose i update 1st 2nd column ,then it will create two record in audit with its old value and new value .
structure of audit table will be
id NOT NULL
attribute NOT NULL
OLD VALUE NOT NULL
NEW VALUE NOT NULL
cre_date NOT NULL
upd_date NULL
cre_time NOT NULL
upd_time NULL
In case of insert ,only the primary key (main table)i.e the id and cre_date and cre_time need to be populated and attribute equal to * ,in case of update ,suppose colA and colB is updating then all need to be populated.In this case two records will be created with attribute of first record colA and corresponding old and new value , and same for the colB
Now my solution to audit is not very optimized , i have created a row level trigger ,which will check for each and every 50 columns for that table whether it is been changed or not based on its new and old value(if -else) , and it will populate the audit table .
I am not satisfied with my soltuion thats why i am posting here.
Another solution which i have seen in the link below :
http://stackoverflow.com/questions/1421645/oracle-excluding-updates-of-one-column-for-firing-a-trigger
This is not working in my case , I have done a POC for that as shown below:
create table temp12(id number);
create or replace trigger my_trigger
after update or insert on temp12
for each row
declare
TYPE tab_col_nt IS table of varchar2(30);
v_tab_col_nt tab_col_nt;
begin
v_tab_col_nt := tab_col_nt('id','name');
for r in v_tab_col_nt.first..v_tab_col_nt.last
loop
if updating(r) then
insert into data_table values(1,'i am updating'||r);
else
insert into data_table values(2,'i am inserting'||r);
end if;
end loop;
end;
In case of updating it is calling the else part i don't know why .
Can this be possible through compound trigger
Your immediate problem with the else always being called is because you're using your index variable r directly, rather than looking up the relevant column name:
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;
You're also only showing an id column in your table creation, so when r is 2, it will always say it's inserting name, never updating. More importantly, if you did have a name column and were only updating that for a given id, this code would show the id as inserting when it hadn't changed. You need to split the insert/update into separate blocks:
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;
This will still say it's inserting name even if the column doesn't exist, but I assume that's a mistake, and I guess you'd be trying to populate the list of names from user_tab_columns anyway if you really want to try to make it dynamic.
I agree with (at least some of) the others that you'd probably be better off with an audit table that takes a copy of the whole row, rather than individual columns. Your objection seems to be the complication of individually listing which columns changed. You could still get this information, with a bit of work, by unpivoting the audit table when you need column-by-column data. For example:
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
So you have one audit row for each action taken, two inserts and three updates. But you want to see separate data for each column that changed.
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
The five audit records have turned into seven updates; the three update statements show the five columns modified. If you'll be using this a lot, you might consider making that into a view.
So lets break that down just a little bit. The core is this inner select, which uses lag() to get the previous value of the row, from the previous audit record for that 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
That gives us a temporary view which has all the audit tables columns plus the lag column which is then used for the unpivot() operation, which you can use as you've tagged the question as 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')
)
Now we have a temporary view which has id, action, when, col, value, prev_value columns; in this case as I only have three columns, that has three times the number of rows in the audit table. Finally the outer select filters that view to only include the rows where the value has changed, i.e. where value != prev_value (allowing for nulls).
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)
I'm using case to just print something, but of course you can do whatever you want with the data. The distinct is needed because the insert entries in the audit table are also converted to three rows in the unpivoted view, and I'm showing the same text for all three from my first case clause.
Why not make life easier and insert the entire row when any data in any column is updated. So any update (or delete typically) on the main table has the original row copied to the audit table first. So your audit table will have same layout as the main table, but with an extra few tracking fields, something like:
create or replace trigger my_tab_tr
before update or delete
on my_tab
referencing new as new and old as old
for each row
declare
l_type varchar2(3);
begin
if (updating) then
l_type = 'UPD';
else
l_type = 'DEL';
end if;
insert into my_tab_audit(
col1,
col2,
audit_type,
audit_date)
values (
:old.col1,
:old.col2,
l_type,
sysdate
);
end;
Add additional columns as you like to the audit table, this is just a typical example
The only way I've seen field-by-field audits done is to check each of the fields :OLD and :NEW values against each other and write the appropriate records to the audit table. You can semi-automate this by having a subroutine in the trigger to which you pass the appropriate values, but one way or another I believe you're going to have to write code for each individual field. Unless someone else has a brilliant way to do this with some sort of reflective API of which I'm not aware (and "what I'm not aware of" is applicable to more stuff each day, or so it seems :-).
The choice of whether to audit individual fields or to audit the entire row (which I usually call "history" tables) depends on how you intend to use the data. In this case, where individual fields changes need to be reported, I agree that a field-by-field audit seems to be a better fit. In other cases (for example, where a data extract must be reproducible for any given date) a row-by-row audit or "history table" approach is a better fit.
Irregardless of the the audit level (field-by-field or row-by-row), the comparison logic needs to be carefully written to handle the NULL/NOT NULL cases, so that you don't get bitten by comparing :OLD.FIELD1 = :NEW.FIELD1 where one of the values (or both) is NULL, and end up not taking the appropriate action because NULL is not equal to anything, even itself. Don't ask me how I know... :-)
Just out of curiosity, what will be put in for OLD_VALUE and NEW_VALUE in the single row which will be created when an INSERT occurs?
Share and enjoy.
the way i like to do it:
create an audit table that is parallel to your existing original
table.
add a timestamp and user columns to this audit table.
whenever the original table is inserted or updated, then just insert
into the audit table.
the audi table should have a trigger to set the timestamp and user values -
all other values come in as the new values.
then you can query at any time who did what, and when.
A very unorthodox solution:
(only if you have access to system tables, at least the SELECT privilege)
You know your table's NAME. Identify the ID of the owner of the table. Look it up in SYS.USER$ by the user's (=owner's) name.
Look up your table's object-ID (= OBJ#) in SYS.OBJ$ by OWNER# (= owner's ID) and NAME (=table's name).
Look up the columns that compose the table in SYS.COL$ by OBJ#. You will find all the columns, their IDs (COL#) and names (NAME).
Write an UPDATE trigger with a cursor that moves on the set of those columns. You will have to write the nucleus of the loop only once.
and end of it: I don't provide code, because the details may differ from Oracle version to Oracle version.
This is real dynamic SQL programming. I happened to use it even on fairly large enterprise systems (the team leaders did not know about it) and it worked. It is fast and reliable.
Drawbacks: {privileges; transportability; bad consideration from responsible people}.
I am trying to sum the values of one record based on the value in another column. I will try to explain using a dummy table and data.
table creation code:
create table test_prj_linking_t
(
prj_name varchar2(10),
prj_code varchar2(20),
prj_alt_code varchar2(50),
prj_bud1 number,
prj_bud2 number
)
insert statement:
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_ALT_CODE,PRJ_BUD1,PRJ_BUD2) values ('prj_A','A123','B123,C123',100,100);
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_ALT_CODE,PRJ_BUD1,PRJ_BUD2) values ('prj_B','B123',null,200,200);
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_ALT_CODE,PRJ_BUD1,PRJ_BUD2) values ('prj_C','C123',null,50,50);
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_ALT_CODE,PRJ_BUD1,PRJ_BUD2) values ('prj_D','D123',null,70,70);
Intended Output: for a project whose prj_alt_code has prj_code of other projects, bud value should be added
For Prj_A, bud1 should come as 350, bud2 as 350 (sum of the bud values of prj_A,prj_B,prj_C)
For Prj_b, Prj_C, Prj_D there will be no summation of values
I was trying with something like this:
select prj_name,prj_code,
case
when prj_alt_code is not null
then
(select sum(t1.prj_bud1)
from test_prj_linking_t t1, test_prj_linking_t t2
where t1.prj_code=t2.prj_alt_code)
when prj_alt_code is null
then
(select t1.prj_bud1
from test_prj_linking_t t1)
end bud
from test_prj_linking_t
but this does not work when there are multiple records with NULL value for prj_alt_code, neither for a prj_alt_code having comma separated values (ex: B123,C123).
Please let me know how to do this in a select query.
Thanks & Regards,
Bishal Mandal
you should to change your data model to keep all attributes separatelly
create table test_prj_linking_t
(
prj_name varchar2(10),
prj_code varchar2(20),
prj_code_parent varchar2(20),
prj_bud1 number,
prj_bud2 number
)
after that, insert each prj_code with its parent separately ... it means:
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_CODE_PARENT,PRJ_BUD1,PRJ_BUD2) values ('prj_A','A123',null,100,100);
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_CODE_PARENT,PRJ_BUD1,PRJ_BUD2) values ('prj_B','B123','A123',200,200);
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_CODE_PARENT,PRJ_BUD1,PRJ_BUD2) values ('prj_C','C123','A123',50,50);
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_CODE_PARENT,PRJ_BUD1,PRJ_BUD2) values ('prj_D','D123',null,70,70);
this solution should solve your problem
Thank you for your inputs !! I agree, putting multiple values in a cell is not a good approach. However, this approach has been implemented for a long time in this case and I am not allowed to change it. So, I went ahead with regexp_substr to handle the multiple values :
(select regexp_substr(t2.prj_alt_code,'[^,]+', 1, level) from dual
connect by regexp_substr(t2.prj_alt_code, '[^,]+', 1, level) is not null)
and a combination of union all prj_alt_code=null and prj_alt_code is not null to achieve the intended result.
Let say I have table my table has values(which they are varchar):
values
a
o
g
t
And I have insert a new value called V
values
V
a
o
g
t
Is there a way or query that can specify what is the last value was insert in the column ? the desired query : select * from dual where rown_num = count(*) -- just an example and the result will be V
Rows in a table have no inherent order. rownum is a pseudocolumn that's part of the select so it isn't useful here. There is no way to tell where in the storage a new row will physically be placed, so you can't rely on rowid, for example.
The only way to do this reliably is to have a timestamp column (maybe set by a trigger so you don't have to worry about it). That would let you order the rows by timestamp and find the row with the highest (most recent) timestamp.
You are still restricted by the precision of the timestamp, as I discovered creating a SQL Fiddle demo; without forcing a small gap between the inserts the timestamps were all the same, but then it only seems to support `timestamp(3). That probably won't be a significant issue in the real world, unless you're doing bulk inserts, but then the last row inserted is still a bit of an arbitrary concept.
As quite correctly pointed out in comments, if the actual time doesn't need to be know, a numeric field populated by a sequence would be more reliable and performant; another SQL Fiddle demo here, and this is the gist:
create table t42(data varchar2(10), id number);
create sequence seq_t42;
create trigger bi_t42
before insert on t42
for each row
begin
:new.id := seq_t42.nextval;
end;
/
insert into t42(data) values ('a');
insert into t42(data) values ('o');
insert into t42(data) values ('g');
insert into t42(data) values ('t');
insert into t42(data) values ('V');
select data from (
select data, row_number() over (order by id desc) as rn
from t42
)
where rn = 1;
I need to create a trigger in oracle 11g for auditing a table .
I have a table with 50 columns that need to be audited.
For every new insert into a table ,i need to put an entry in audit table (1 row).
For every update ,suppose i update 1st 2nd column ,then it will create two record in audit with its old value and new value .
structure of audit table will be
id NOT NULL
attribute NOT NULL
OLD VALUE NOT NULL
NEW VALUE NOT NULL
cre_date NOT NULL
upd_date NULL
cre_time NOT NULL
upd_time NULL
In case of insert ,only the primary key (main table)i.e the id and cre_date and cre_time need to be populated and attribute equal to * ,in case of update ,suppose colA and colB is updating then all need to be populated.In this case two records will be created with attribute of first record colA and corresponding old and new value , and same for the colB
Now my solution to audit is not very optimized , i have created a row level trigger ,which will check for each and every 50 columns for that table whether it is been changed or not based on its new and old value(if -else) , and it will populate the audit table .
I am not satisfied with my soltuion thats why i am posting here.
Another solution which i have seen in the link below :
http://stackoverflow.com/questions/1421645/oracle-excluding-updates-of-one-column-for-firing-a-trigger
This is not working in my case , I have done a POC for that as shown below:
create table temp12(id number);
create or replace trigger my_trigger
after update or insert on temp12
for each row
declare
TYPE tab_col_nt IS table of varchar2(30);
v_tab_col_nt tab_col_nt;
begin
v_tab_col_nt := tab_col_nt('id','name');
for r in v_tab_col_nt.first..v_tab_col_nt.last
loop
if updating(r) then
insert into data_table values(1,'i am updating'||r);
else
insert into data_table values(2,'i am inserting'||r);
end if;
end loop;
end;
In case of updating it is calling the else part i don't know why .
Can this be possible through compound trigger
Your immediate problem with the else always being called is because you're using your index variable r directly, rather than looking up the relevant column name:
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;
You're also only showing an id column in your table creation, so when r is 2, it will always say it's inserting name, never updating. More importantly, if you did have a name column and were only updating that for a given id, this code would show the id as inserting when it hadn't changed. You need to split the insert/update into separate blocks:
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;
This will still say it's inserting name even if the column doesn't exist, but I assume that's a mistake, and I guess you'd be trying to populate the list of names from user_tab_columns anyway if you really want to try to make it dynamic.
I agree with (at least some of) the others that you'd probably be better off with an audit table that takes a copy of the whole row, rather than individual columns. Your objection seems to be the complication of individually listing which columns changed. You could still get this information, with a bit of work, by unpivoting the audit table when you need column-by-column data. For example:
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
So you have one audit row for each action taken, two inserts and three updates. But you want to see separate data for each column that changed.
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
The five audit records have turned into seven updates; the three update statements show the five columns modified. If you'll be using this a lot, you might consider making that into a view.
So lets break that down just a little bit. The core is this inner select, which uses lag() to get the previous value of the row, from the previous audit record for that 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
That gives us a temporary view which has all the audit tables columns plus the lag column which is then used for the unpivot() operation, which you can use as you've tagged the question as 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')
)
Now we have a temporary view which has id, action, when, col, value, prev_value columns; in this case as I only have three columns, that has three times the number of rows in the audit table. Finally the outer select filters that view to only include the rows where the value has changed, i.e. where value != prev_value (allowing for nulls).
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)
I'm using case to just print something, but of course you can do whatever you want with the data. The distinct is needed because the insert entries in the audit table are also converted to three rows in the unpivoted view, and I'm showing the same text for all three from my first case clause.
Why not make life easier and insert the entire row when any data in any column is updated. So any update (or delete typically) on the main table has the original row copied to the audit table first. So your audit table will have same layout as the main table, but with an extra few tracking fields, something like:
create or replace trigger my_tab_tr
before update or delete
on my_tab
referencing new as new and old as old
for each row
declare
l_type varchar2(3);
begin
if (updating) then
l_type = 'UPD';
else
l_type = 'DEL';
end if;
insert into my_tab_audit(
col1,
col2,
audit_type,
audit_date)
values (
:old.col1,
:old.col2,
l_type,
sysdate
);
end;
Add additional columns as you like to the audit table, this is just a typical example
The only way I've seen field-by-field audits done is to check each of the fields :OLD and :NEW values against each other and write the appropriate records to the audit table. You can semi-automate this by having a subroutine in the trigger to which you pass the appropriate values, but one way or another I believe you're going to have to write code for each individual field. Unless someone else has a brilliant way to do this with some sort of reflective API of which I'm not aware (and "what I'm not aware of" is applicable to more stuff each day, or so it seems :-).
The choice of whether to audit individual fields or to audit the entire row (which I usually call "history" tables) depends on how you intend to use the data. In this case, where individual fields changes need to be reported, I agree that a field-by-field audit seems to be a better fit. In other cases (for example, where a data extract must be reproducible for any given date) a row-by-row audit or "history table" approach is a better fit.
Irregardless of the the audit level (field-by-field or row-by-row), the comparison logic needs to be carefully written to handle the NULL/NOT NULL cases, so that you don't get bitten by comparing :OLD.FIELD1 = :NEW.FIELD1 where one of the values (or both) is NULL, and end up not taking the appropriate action because NULL is not equal to anything, even itself. Don't ask me how I know... :-)
Just out of curiosity, what will be put in for OLD_VALUE and NEW_VALUE in the single row which will be created when an INSERT occurs?
Share and enjoy.
the way i like to do it:
create an audit table that is parallel to your existing original
table.
add a timestamp and user columns to this audit table.
whenever the original table is inserted or updated, then just insert
into the audit table.
the audi table should have a trigger to set the timestamp and user values -
all other values come in as the new values.
then you can query at any time who did what, and when.
A very unorthodox solution:
(only if you have access to system tables, at least the SELECT privilege)
You know your table's NAME. Identify the ID of the owner of the table. Look it up in SYS.USER$ by the user's (=owner's) name.
Look up your table's object-ID (= OBJ#) in SYS.OBJ$ by OWNER# (= owner's ID) and NAME (=table's name).
Look up the columns that compose the table in SYS.COL$ by OBJ#. You will find all the columns, their IDs (COL#) and names (NAME).
Write an UPDATE trigger with a cursor that moves on the set of those columns. You will have to write the nucleus of the loop only once.
and end of it: I don't provide code, because the details may differ from Oracle version to Oracle version.
This is real dynamic SQL programming. I happened to use it even on fairly large enterprise systems (the team leaders did not know about it) and it worked. It is fast and reliable.
Drawbacks: {privileges; transportability; bad consideration from responsible people}.
I have this DML statement..
delete from (select key,value,computed, row_number() OVER (Partition By key, value order by seq asc) as a
from excelformats a )
where A > 1
and this throws
ORA-01732: data manipulation operation not legal on this view
This statement basically selects duplicate rows from excelFormats table those to be deleted
How can I revise so that
You could use:
DELETE FROM excelformats
WHERE rowid not in
(SELECT MIN(rowid)
FROM excelformats
GROUP BY key, value, computed);
This will delete duplicate rows in your excelformats table given the three key columns you stated.
Hope it helps...