I use Flashback Data Archive for SCD Type 4 and Temporal Validity (plus code for automation) for SCD Type 2.
To automate, I'd use a VIEW
on the actual table with an INSTEAD OF
trigger to do the actual work. Grant CRUD capability on the VIEW
and Read-Only on the actual table.
create table my_data_history (
data_id int generated always as identity,
some_data varchar2(10),
start_date date not null,
end_date date,
period for current_data (start_date, end_date),
constraint my_data_history_pk primary key (data_id, start_date),
constraint my_data_history_valid_date_range check ( start_date < end_date )
);
create view my_data
as
select *
from my_data_history
as of period for current_data( sysdate );
create or replace trigger my_data_trg
instead of insert or update or delete
on my_data
for each row
begin
if deleting
then
update my_data_history set end_date = sysdate
where my_data_id = :old.my_data_id and start_date = :old.start_date;
end if;
if updating or inserting
then
if :old.my_data_id <> :new.my_data_id
then
raise_application_error( -20000, 'This method does not support modifying PK' );
end if;
if inserting and ( :new.my_data_id is not null or :new.start_date is not null or :new.end_date is not null)
then
raise_application_error( -20001, q'[No thank you! We've alrady got one (DATA_ID, START_DATE, END_DATE)]' );
end if;
if updating and :new.end_date is not null
then
raise_application_error( -20002, q'[Don't set END_DATE. use DELETE instead]' );
end if;
if updating and :new.start_date <> :old.start_date
then
raise_application_error( -20002, q'[YOU aren't allowed to modify the START_DATE]' );
end if;
merge into my_data_history a
using (
select my_data_id, some_data, start_date, end_date
from my_data_history
where my_data_id = :old.my_data_id and start_date = :old.start_date
union all
select :new.my_data_id my_data_id, :new.some_data some_data
,sysdate start_date, null end_date
from dual
) b
on (a.my_data_id = b.my_data_id and a.start_date = b.start_date)
when matched then update set a.some_data=b.some_data, a.end_date = sysdate
when not matched then insert (my_data_id, some_data, start_date)
values ( nvl(b.my_data_id, my_data_seq.nextval), b.some_data, b.start_date);
end if;
end;
Test Script:
select * from my_data;
insert into my_data some_data (some_data) values ( 'abcdefg' );
select * from my_data;
update my_data set some_data = 'xyz' WHERE my_data_id = 1;
select * from my_data;
delete from my_data where my_data_id = 1;
select * from my_data;
alter session set nls_date_format = 'yyyy-mm-dd hh24:mi:ss';
select * from my_data_history;