ChatGPT解决这个技术问题 Extra ChatGPT

Oracle: how to UPSERT (update or insert into a table?)

The UPSERT operation either updates or inserts a row in a table, depending if the table already has a row that matches the data:

if table t has a row exists that has key X:
    update t set mystuff... where mykey=X
else
    insert into t mystuff...

Since Oracle doesn't have a specific UPSERT statement, what's the best way to do this?


M
Mark Harrison

The MERGE statement merges data between two tables. Using DUAL allows us to use this command. Note that this is not protected against concurrent access.

create or replace
procedure ups(xa number)
as
begin
    merge into mergetest m using dual on (a = xa)
         when not matched then insert (a,b) values (xa,1)
             when matched then update set b = b+1;
end ups;
/
drop table mergetest;
create table mergetest(a number, b number);
call ups(10);
call ups(10);
call ups(20);
select * from mergetest;

A                      B
---------------------- ----------------------
10                     2
20                     1

Apparently the "merge into" statement is not atomic. It can result in "ORA-0001: unique constraint" when used concurrently. The check for existence of a match and the insertion of a new record are not protected by a lock, so there is a race condition. To do this reliably, you need to catch this exception and either re-run the merge or do a simple update instead. In Oracle 10, you can use the "log errors" clause to make it continue with the rest of the rows when an error occurs and log the offending row to another table, rather than just stopping.
Hi, I tried to use same query pattern in my query but somehow my query is inserting duplicate rows. I am not able to find more information about DUAL table. Can anyone please tell me where can I get information of DUAL and also about merge syntax?
@Shekhar Dual is a dummy table with a single row and columnn adp-gmbh.ch/ora/misc/dual.html
@TimSylvester - Oracle uses transactions, so guarantees the snapshot of data at the start of a transaction is consistent throughout the transaction save any changes made within it. Concurrent calls to the database uses the undo stack; so Oracle will manage the final state based on the order of when the concurrent transactions started/completed. So, you'll never have a race condition if a constraint check is done before the insert regardless of how many concurrent calls are made to same SQL code. Worst case, you may get lots of contention and Oracle will take much longer to reach a final state.
@RandyMagruder Is it the case that its 2015 and we still cannot do a upsert reliably in Oracle! Do you know of a concurrent safe solution?
S
Synesso

The dual example above which is in PL/SQL was great becuase I wanted to do something similar, but I wanted it client side...so here is the SQL I used to send a similar statement direct from some C#

MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name") 
    VALUES ( 2097153,"smith", "john" )

However from a C# perspective this provide to be slower than doing the update and seeing if the rows affected was 0 and doing the insert if it was.


I've come back here to check out this pattern again. It fails silently when concurrent inserts are attempted. One insert takes effect, the second merge neither inserts or updates. However, the faster approach of doing two separate statements is safe.
oralcle newbies like me may ask what is this dual table see this : stackoverflow.com/q/73751/808698
Too bad that with this pattern we need to write twice the data (John, Smith...). In this case, I win nothing using MERGE, and I prefer using much simpler DELETE then INSERT.
@NicolasBarbulesco this answer doesn't need to write the data twice: stackoverflow.com/a/4015315/8307814
@NicolasBarbulesco MERGE INTO mytable d USING (SELECT 1 id, 'x' name from dual) s ON (d.id = s.id) WHEN MATCHED THEN UPDATE SET d.name = s.name WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name);
T
Tony Andrews

An alternative to MERGE (the "old fashioned way"):

begin
   insert into t (mykey, mystuff) 
      values ('X', 123);
exception
   when dup_val_on_index then
      update t 
      set    mystuff = 123 
      where  mykey = 'X';
end;   

The issue is that you have a window in between the insert and the update where another process could successfully fire a delete. I did however use this pattern on a table that never has deletes fired against it.
OK, I agree. Don't know why it wasn't obvious to me.
I disagree with Chotchki. "Lock Duration : All locks acquired by statements within a transaction are held for the duration of the transaction, preventing destructive interference including dirty reads, lost updates, and destructive DDL operations from concurrent transactions." Souce : link
@yohannc: I think the point is that we haven't acquired any locks just by trying and failing to insert a row.
B
Brian Schmitt

Another alternative without the exception check:

UPDATE tablename
    SET val1 = in_val1,
        val2 = in_val2
    WHERE val3 = in_val3;

IF ( sql%rowcount = 0 )
    THEN
    INSERT INTO tablename
        VALUES (in_val1, in_val2, in_val3);
END IF;

Your provided solution does not work for me. Does %rowcount only work with explicit cursors?
What if the update returned 0 rows modified because the record was already there and the values were the same?
@Adriano: sql%rowcount will still return > 0 if the WHERE clause matches any rows, even if the update doesn't actually change any data on those rows.
Doesn't work: PLS-00207: identifier 'COUNT', applied to implicit cursor SQL, is not a legal cursor attribute
Syntax Errors here :(
s
slavoo

insert if not exists update:

INSERT INTO mytable (id1, t1) 
  SELECT 11, 'x1' FROM DUAL 
  WHERE NOT EXISTS (SELECT id1 FROM mytble WHERE id1 = 11); 

UPDATE mytable SET t1 = 'x1' WHERE id1 = 11;

C
Community

None of the answers given so far is safe in the face of concurrent accesses, as pointed out in Tim Sylvester's comment, and will raise exceptions in case of races. To fix that, the insert/update combo must be wrapped in some kind of loop statement, so that in case of an exception the whole thing is retried.

As an example, here's how Grommit's code can be wrapped in a loop to make it safe when run concurrently:

PROCEDURE MyProc (
 ...
) IS
BEGIN
 LOOP
  BEGIN
    MERGE INTO Employee USING dual ON ( "id"=2097153 )
      WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
      WHEN NOT MATCHED THEN INSERT ("id","last","name") 
        VALUES ( 2097153,"smith", "john" );
    EXIT; -- success? -> exit loop
  EXCEPTION
    WHEN NO_DATA_FOUND THEN -- the entry was concurrently deleted
      NULL; -- exception? -> no op, i.e. continue looping
    WHEN DUP_VAL_ON_INDEX THEN -- an entry was concurrently inserted
      NULL; -- exception? -> no op, i.e. continue looping
  END;
 END LOOP;
END; 

N.B. In transaction mode SERIALIZABLE, which I don't recommend btw, you might run into ORA-08177: can't serialize access for this transaction exceptions instead.


Excellent! Finally, a concurrent accesses safe answer. Any way to use such a construct from a client (eg. from a Java client)?
You mean not having to call a stored proc? Well, in that case you could also just catch the specific Java exceptions and retry in a Java loop. It's a hell of a lot more convenient in Java than Oracle's SQL.
I'm sorry: I was not specific enough. But you understood the right way. I resigned to do like you just said. But I'm not 100% satisfied because it generates more SQL queries, more client/server roundtrips. It is not a good solution performance-wise. But my goal is to let the Java developers of my project use my method to upsert in any table (I cannot create one PLSQL stored procedure per table, or one procedure per upsert type).
@Sebien I agree, it'd be nicer to have it encapsulated in the SQL realm, and I think you can do it. I'm just not volunteering to figure it out for you... :) Plus, in reality these exceptions will probably occur less than once in a blue moon, so you shouldn't see an impact on performance in 99.9% of cases. Except when doing load testing of course...
H
Hubbitus

I'd like Grommit answer, except it require dupe values. I found solution where it may appear once: http://forums.devshed.com/showpost.php?p=1182653&postcount=2

MERGE INTO KBS.NUFUS_MUHTARLIK B
USING (
    SELECT '028-01' CILT, '25' SAYFA, '6' KUTUK, '46603404838' MERNIS_NO
    FROM DUAL
) E
ON (B.MERNIS_NO = E.MERNIS_NO)
WHEN MATCHED THEN
    UPDATE SET B.CILT = E.CILT, B.SAYFA = E.SAYFA, B.KUTUK = E.KUTUK
WHEN NOT MATCHED THEN
    INSERT (  CILT,   SAYFA,   KUTUK,   MERNIS_NO)
    VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO); 

Did you mean INSERT (B.CILT, B.SAYFA, B.KUTUK, B.MERNIS_NO) VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO); ?
Thankfully you edited your answer! :) my edit was sadly reject stackoverflow.com/review/suggested-edits/7555674
A
Arturo Hernandez

I've been using the first code sample for years. Notice notfound rather than count.

UPDATE tablename SET val1 = in_val1, val2 = in_val2
    WHERE val3 = in_val3;
IF ( sql%notfound ) THEN
    INSERT INTO tablename
        VALUES (in_val1, in_val2, in_val3);
END IF;

The code below is the possibly new and improved code

MERGE INTO tablename USING dual ON ( val3 = in_val3 )
WHEN MATCHED THEN UPDATE SET val1 = in_val1, val2 = in_val2
WHEN NOT MATCHED THEN INSERT 
    VALUES (in_val1, in_val2, in_val3)

In the first example the update does an index lookup. It has to, in order to update the right row. Oracle opens an implicit cursor, and we use it to wrap a corresponding insert so we know that the insert will only happen when the key does not exist. But the insert is an independent command and it has to do a second lookup. I don't know the inner workings of the merge command but since the command is a single unit, Oracle could execute the correct insert or update with a single index lookup.

I think merge is better when you do have some processing to be done that means taking data from some tables and updating a table, possibly inserting or deleting rows. But for the single row case, you may consider the first case since the syntax is more common.


A
AnthonyVO

A note regarding the two solutions that suggest:

1) Insert, if exception then update,

or

2) Update, if sql%rowcount = 0 then insert

The question of whether to insert or update first is also application dependent. Are you expecting more inserts or more updates? The one that is most likely to succeed should go first.

If you pick the wrong one you will get a bunch of unnecessary index reads. Not a huge deal but still something to consider.


A
Andro Selva

Try this,

insert into b_building_property (
  select
    'AREA_IN_COMMON_USE_DOUBLE','Area in Common Use','DOUBLE', null, 9000, 9
  from dual
)
minus
(
  select * from b_building_property where id = 9
)
;

A
Anon

From http://www.praetoriate.com/oracle_tips_upserts.htm:

"In Oracle9i, an UPSERT can accomplish this task in a single statement:"

INSERT
FIRST WHEN
   credit_limit >=100000
THEN INTO
   rich_customers
VALUES(cust_id,cust_credit_limit)
   INTO customers
ELSE
   INTO customers SELECT * FROM new_customers;

-1 Typical Don Burleson cr@p I'm afraid - this is an insert into one table or another, there is no "upsert" here!