Create a view over the table and write your custom instead of trigger
--DDL create your sample table:
create table table_name (
field_one varchar2(100)
);
--DDL create the view
create view view_name as
select * from table_name;
--DDL creating instead trigger
CREATE OR REPLACE TRIGGER trigger_name
INSTEAD OF UPDATE ON view_name
FOR EACH ROW
BEGIN
UPDATE table_name
SET field_one = :NEW.field_one
where :OLD.field_one != :NEW.field_one
;
END trigger_name;
--DML testing:
update view_name set field_one = 'one'
EDITED, test results
I have tested my approach in a 200K rows scenario with 1/20 factor for updatable rows. Here results:
- Script time updating table directly: 1.559,05 secs
- Script time updating through trigger: 1.101,14 secs
Steps to repreduce test:
Creating table, view and trigger:
create table table_name (
myPK int primary key,
field_1 varchar2(100),
field_2 varchar2(100),
field_3 varchar2(4000)
);
create view view_name as
select * from table_name;
CREATE OR REPLACE TRIGGER trigger_name
INSTEAD OF UPDATE ON view_name
FOR EACH ROW
BEGIN
UPDATE table_name
SET
field_1 = :NEW.field_1,
field_2 = :NEW.field_2
where
myPK = :OLD.myPK
AND not ( :OLD.field_1 = :NEW.field_1 and
:OLD.field_2 = :NEW.field_2 )
;
END trigger_name;
Inserting dummy data with 1/20 factor for updatable rows:
DECLARE
x NUMBER := 300000;
BEGIN
FOR i IN 1..x LOOP
IF MOD(i,20) = 0 THEN
INSERT INTO table_name VALUES (i, 'rare', 'hello',
dbms_random.string('A', 2000));
ELSE
INSERT INTO table_name VALUES (i, 'ordinary', 'bye',
dbms_random.string('A', 2000) );
END IF;
END LOOP;
COMMIT;
END;
Script for testing performace:
declare
l_start number;
l_end number;
l_diff number;
rows2update int;
begin
rows2update := 100000;
l_start := dbms_utility.get_time ;
affectedRows := 0;
FOR i IN 1..rows2update LOOP
rows2update := rows2update - 1;
update view_name --<---- replace by table_name to test without trigger
set field_1 = 'ordinary'
where myPK = round( dbms_random.value(1,300000) ) ;
commit;
end loop;
l_end := dbms_utility.get_time ;
dbms_output.put_line('l_start ='||l_start);
dbms_output.put_line('l_end ='||l_end);
l_diff := (l_end-l_start)/100;
dbms_output.put_line('Elapsed Time: '|| l_diff ||' secs');
end;
/
Disclaimer: This is a simple test made in a virtual environment only as a first approach test. I'm sure than results can change significantly just changing field lenght or other parameters.