why is it "ugly". it is very common.
I would say, if you think the dup val will be raised "fairly often", that:
update t set ...
if (sql%rowcount = 0)
then
insert into t ...
end if;
is more efficient -- the insert of a dup actually INSERTS (does work), then upon discovering the dup -- rolls back -- and then queries up cons$ to find the name of the violated constraint. Tons of work.
Whereas updating zero rows is fairly "work deintensive".
You can work out your break even using a simple test. Consider two tables:
ops$tkyte@ORA9IR2> create table t1 ( x int primary key, y char(80) );
Table created.
ops$tkyte@ORA9IR2> create table t2 ( x int primary key, y char(80) );
Table created.
<b>now procedure p1 will insert and if dup, update. procedure p2 will update and if nothing happened -- then insert:</b>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace procedure p1
2 as
3 begin
4 for i in 1 .. 1000
5 loop
6 begin
7 insert into t1 values ( i, i );
8 exception
9 when dup_val_on_index
10 then
11 update t1 set y = i+1 where x = i;
12 end;
13 end loop;
14 end;
15 /
Procedure created.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace procedure p2
2 as
3 begin
4 for i in 1 .. 1000
5 loop
6 update t2 set y = i+1 where x = i;
7 if ( sql%rowcount = 0 )
8 then
9 insert into t2 values (i,i);
10 end if;
11 end loop;
12 end;
13 /
Procedure created.
<b>we'll benchmark p1 vs p2 when the table is empty (eg: all of the inserts succeed:</b>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec runStats_pkg.rs_start;
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> exec p1
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> exec runStats_pkg.rs_middle;
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> exec p2
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> exec runStats_pkg.rs_stop(1000);
Run1 ran in 8 hsecs
Run2 ran in 15 hsecs
run 1 ran in 53.33% of the time
Name Run1 Run2 Diff
STAT...recursive calls 1,024 2,025 1,001
LATCH.shared pool 1,148 2,165 1,017
LATCH.cache buffers chains 15,546 16,952 1,406
STAT...consistent gets - exami 16 1,434 1,418
STAT...session logical reads 3,683 5,101 1,418
STAT...consistent gets 24 1,443 1,419
LATCH.library cache pin 2,096 4,107 2,011
LATCH.library cache 2,177 4,199 2,022
Run1 latches total versus runs -- difference and pct
Run1 Run2 Diff Pct
23,877 30,503 6,626 78.28%
PL/SQL procedure successfully completed.
<b>there the extra work of trying to update 1,000 times almost doubled the wall clock time and added measurably to the latching we did.... BUT, let's reverse the roles here. We'll fail inserting 1,000 times and have to resort to an update:</b>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> commit;
Commit complete.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec runStats_pkg.rs_start;
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> exec p1
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> exec runStats_pkg.rs_middle;
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> exec p2
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> exec runStats_pkg.rs_stop(1000);
Run1 ran in 66 hsecs
Run2 ran in 11 hsecs
run 1 ran in 600% of the time
Name Run1 Run2 Diff
STAT...opened cursors cumulati 1,005 4 -1,001
STAT...parse count (total) 1,005 4 -1,001
LATCH.simulator hash latch 1,280 128 -1,152
LATCH.session allocation 2,000 0 -2,000
STAT...calls to get snapshot s 3,001 1,001 -2,000
STAT...no work - consistent re 2,000 0 -2,000
STAT...index fetch by key 3,000 1,000 -2,000
STAT...execute count 3,005 1,005 -2,000
STAT...table fetch by rowid 2,000 0 -2,000
STAT...redo entries 3,508 1,507 -2,001
LATCH.redo allocation 3,518 1,511 -2,007
LATCH.row cache enqueue latch 2,010 0 -2,010
LATCH.row cache objects 2,010 0 -2,010
STAT...db block changes 7,051 3,046 -4,005
STAT...consistent gets - exami 7,004 2,005 -4,999
STAT...buffer is not pinned co 5,000 0 -5,000
LATCH.library cache pin alloca 6,028 22 -6,006
LATCH.shared pool 8,083 1,068 -7,015
STAT...consistent gets 10,004 2,006 -7,998
STAT...db block gets 10,578 1,566 -9,012
STAT...recursive calls 11,002 1,001 -10,001
LATCH.library cache pin 12,061 2,054 -10,007
LATCH.library cache 18,104 2,086 -16,018
STAT...session logical reads 20,582 3,572 -17,010
LATCH.cache buffers chains 47,703 9,692 -38,011
STAT...redo size 915,700 462,856 -452,844
Run1 latches total versus runs -- difference and pct
Run1 Run2 Diff Pct
104,895 16,701 -88,194 628.08%
PL/SQL procedure successfully completed.
<b>having 1,000 failed inserts was killer, 6x the runtime, 6x the latching. 2x the redo (for the attempted insert+rollback and then update)....
So, where is the break even -- in this case, if we expect more than 10% of the rows to "fail", update and then insert will best insert and then update:</b>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> delete from t1 where x <= 900;
900 rows deleted.
ops$tkyte@ORA9IR2> delete from t2 where x <= 900;
900 rows deleted.
ops$tkyte@ORA9IR2> commit;
Commit complete.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec runStats_pkg.rs_start;
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> exec p1
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> exec runStats_pkg.rs_middle;
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> exec p2
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> exec runStats_pkg.rs_stop(1000);
Run1 ran in 18 hsecs
Run2 ran in 17 hsecs
run 1 ran in 105.88% of the time
Name Run1 Run2 Diff
STAT...consistent gets 1,011 2,014 1,003
STAT...consistent gets - exami 611 1,910 1,299
LATCH.cache buffers chains 19,025 17,146 -1,879
STAT...redo size 622,260 576,452 -45,808
Run1 latches total versus runs -- difference and pct
Run1 Run2 Diff Pct
31,917 29,576 -2,341 107.92%
PL/SQL procedure successfully completed.