|
|
Coding Techniques for Upserts
David Clement
September 2004
Oracle versions 7-8
A long-standing limitation of SQL is that it does not handle the
common situation where new data has to be put into a table either by
inserting or by updating as appropriate. For example, if a
salesperson is verifying corporate customers' orders by phone, it
happens many times a day that the salesperson has to modify an
existing order by adding 5 more cartons of medium widgets. If there
is already an order line for medium widgets, it should be updated,
but if not, a new order line should be inserted. This is called an
"upsert" situation.
Because of the lack of any way to do this in standard SQL, many
programmers have resorted to the following algorithm (expressed in C
with embedded SQL).
exec sql select count (*)
into :hostvariable
from t
where a = :myRequirement;
if (hostvariable == 0) {
exec sql insert into t
values (:myRequirement, :myNewValue);
} else {
exec sql update t
set b = :myNewValue
where a = :myRequirement;
}
One problem with this code is that it performs a minimum of two SQL
statements to do the work of one. Another problem is that it relies
on a host-language variable, so it is not completely portable; that
is, the code cannot be cut and pasted from one source-code file to
another.
The work-around for these problems adopted by many other programmers
has been to force an error, as follows (expressed in PL/SQL).
update t
set b = :myNewValue
where a = :myRequirement;
if sql%notfound
then
insert into t
values (:myRequirement, :myNewValue);
end if;
This code is still not completely portable, in that it uses a host
language feature, in the example, the PL/SQL cursor attribute (where
Pro*C would use the SQLCA).
Since Oracle 8 introduced the returning clause, a
portable solution is available. This is to use where not
exists with returning as follows.
insert into t
(select :myRequirement, :myNewValue
from dual
where not exists
(select 1
from t
where a = :myRequirement))
returning a into :retval;
update t
set b = :myNewvalue
where a = :myRequirement
and a <> :retval;
Since this code is pure SQL, it can be packaged and invoked in all
Oracle environments. However, it provides little improvement in
performance, since it still executes two SQL statements. Of course,
if retval is an array, the code above can process many
rows with those two statements.
Finally, Oracle 9 provided the first real solution to the "upsert"
problem, by extending the SQL language with a new verb,
merge. Here is an example of its use.
merge into t
using (a = :myRequirement)
when not matched then
insert
values (:myRequirement, :myNewValue)
when matched then
update
set b = :myNewvalue;
|
|