Solutions for INSERT OR UPDATE on SQL Server

后端 未结 22 2004
别跟我提以往
别跟我提以往 2020-11-21 22:23

Assume a table structure of MyTable(KEY, datafield1, datafield2...).

Often I want to either update an existing record, or insert a new record if it does

相关标签:
22条回答
  • 2020-11-21 22:43

    You can use MERGE Statement, This statement is used to insert data if not exist or update if does exist.

    MERGE INTO Employee AS e
    using EmployeeUpdate AS eu
    ON e.EmployeeID = eu.EmployeeID`
    
    0 讨论(0)
  • 2020-11-21 22:44

    don't forget about transactions. Performance is good, but simple (IF EXISTS..) approach is very dangerous.
    When multiple threads will try to perform Insert-or-update you can easily get primary key violation.

    Solutions provided by @Beau Crawford & @Esteban show general idea but error-prone.

    To avoid deadlocks and PK violations you can use something like this:

    begin tran
    if exists (select * from table with (updlock,serializable) where key = @key)
    begin
       update table set ...
       where key = @key
    end
    else
    begin
       insert into table (key, ...)
       values (@key, ...)
    end
    commit tran
    

    or

    begin tran
       update table with (serializable) set ...
       where key = @key
    
       if @@rowcount = 0
       begin
          insert into table (key, ...) values (@key,..)
       end
    commit tran
    
    0 讨论(0)
  • 2020-11-21 22:46

    Although its pretty late to comment on this I want to add a more complete example using MERGE.

    Such Insert+Update statements are usually called "Upsert" statements and can be implemented using MERGE in SQL Server.

    A very good example is given here: http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx

    The above explains locking and concurrency scenarios as well.

    I will be quoting the same for reference:

    ALTER PROCEDURE dbo.Merge_Foo2
          @ID int
    AS
    
    SET NOCOUNT, XACT_ABORT ON;
    
    MERGE dbo.Foo2 WITH (HOLDLOCK) AS f
    USING (SELECT @ID AS ID) AS new_foo
          ON f.ID = new_foo.ID
    WHEN MATCHED THEN
        UPDATE
                SET f.UpdateSpid = @@SPID,
                UpdateTime = SYSDATETIME()
    WHEN NOT MATCHED THEN
        INSERT
          (
                ID,
                InsertSpid,
                InsertTime
          )
        VALUES
          (
                new_foo.ID,
                @@SPID,
                SYSDATETIME()
          );
    
    RETURN @@ERROR;
    
    0 讨论(0)
  • 2020-11-21 22:47
    IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
    UPDATE [Table] SET propertyOne = propOne, property2 . . .
    ELSE
    INSERT INTO [Table] (propOne, propTwo . . .)
    

    Edit:

    Alas, even to my own detriment, I must admit the solutions that do this without a select seem to be better since they accomplish the task with one less step.

    0 讨论(0)
  • 2020-11-21 22:47

    Before everyone jumps to HOLDLOCK-s out of fear from these nafarious users running your sprocs directly :-) let me point out that you have to guarantee uniqueness of new PK-s by design (identity keys, sequence generators in Oracle, unique indexes for external ID-s, queries covered by indexes). That's the alpha and omega of the issue. If you don't have that, no HOLDLOCK-s of the universe are going to save you and if you do have that then you don't need anything beyond UPDLOCK on the first select (or to use update first).

    Sprocs normally run under very controlled conditions and with the assumption of a trusted caller (mid tier). Meaning that if a simple upsert pattern (update+insert or merge) ever sees duplicate PK that means a bug in your mid-tier or table design and it's good that SQL will yell a fault in such case and reject the record. Placing a HOLDLOCK in this case equals eating exceptions and taking in potentially faulty data, besides reducing your perf.

    Having said that, Using MERGE, or UPDATE then INSERT is easier on your server and less error prone since you don't have to remember to add (UPDLOCK) to first select. Also, if you are doing inserts/updates in small batches you need to know your data in order to decide whether a transaction is appropriate or not. It it's just a collection of unrelated records then additional "enveloping" transaction will be detrimental.

    0 讨论(0)
  • 2020-11-21 22:50

    Many people will suggest you use MERGE, but I caution you against it. By default, it doesn't protect you from concurrency and race conditions any more than multiple statements, and it introduces other dangers:

    • Use Caution with SQL Server's MERGE Statement

    Even with this "simpler" syntax available, I still prefer this approach (error handling omitted for brevity):

    BEGIN TRANSACTION;
    
    UPDATE dbo.table WITH (UPDLOCK, SERIALIZABLE) 
      SET ... WHERE PK = @PK;
    
    IF @@ROWCOUNT = 0
    BEGIN
      INSERT dbo.table(PK, ...) SELECT @PK, ...;
    END
    
    COMMIT TRANSACTION;
    
    • Please stop using this UPSERT anti-pattern

    A lot of folks will suggest this way:

    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    
    BEGIN TRANSACTION;
    
    IF EXISTS (SELECT 1 FROM dbo.table WHERE PK = @PK)
    BEGIN
      UPDATE ...
    END
    ELSE
    BEGIN
      INSERT ...
    END
    COMMIT TRANSACTION;
    

    But all this accomplishes is ensuring you may need to read the table twice to locate the row(s) to be updated. In the first sample, you will only ever need to locate the row(s) once. (In both cases, if no rows are found from the initial read, an insert occurs.)

    Others will suggest this way:

    BEGIN TRY
      INSERT ...
    END TRY
    BEGIN CATCH
      IF ERROR_NUMBER() = 2627
        UPDATE ...
    END CATCH
    

    However, this is problematic if for no other reason than letting SQL Server catch exceptions that you could have prevented in the first place is much more expensive, except in the rare scenario where almost every insert fails. I prove as much here:

    • Checking for potential constraint violations before entering TRY/CATCH
    • Performance impact of different error handling techniques
    0 讨论(0)
提交回复
热议问题