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

后端 未结 12 1378
南旧
南旧 2020-11-22 07:18

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          


        
相关标签:
12条回答
  • 2020-11-22 07:51

    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;
    
    0 讨论(0)
  • 2020-11-22 07:56

    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;   
    
    0 讨论(0)
  • 2020-11-22 07:59

    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
    
    0 讨论(0)
  • 2020-11-22 08:05

    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.

    0 讨论(0)
  • 2020-11-22 08:06

    Copy & paste example for upserting one table into another, with MERGE:

    CREATE GLOBAL TEMPORARY TABLE t1
        (id VARCHAR2(5) ,
         value VARCHAR2(5),
         value2 VARCHAR2(5)
         )
      ON COMMIT DELETE ROWS;
    
    CREATE GLOBAL TEMPORARY TABLE t2
        (id VARCHAR2(5) ,
         value VARCHAR2(5),
         value2 VARCHAR2(5))
      ON COMMIT DELETE ROWS;
    ALTER TABLE t2 ADD CONSTRAINT PK_LKP_MIGRATION_INFO PRIMARY KEY (id);
    
    insert into t1 values ('a','1','1');
    insert into t1 values ('b','4','5');
    insert into t2 values ('b','2','2');
    insert into t2 values ('c','3','3');
    
    
    merge into t2
    using t1
    on (t1.id = t2.id) 
    when matched then 
      update set t2.value = t1.value,
      t2.value2 = t1.value2
    when not matched then
      insert (t2.id, t2.value, t2.value2)  
      values(t1.id, t1.value, t1.value2);
    
    select * from t2
    

    Result:

    1. b 4 5
    2. c 3 3
    3. a 1 1
    0 讨论(0)
  • 2020-11-22 08:08

    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.

    0 讨论(0)
提交回复
热议问题