How to implement a conditional Upsert stored procedure?

后端 未结 5 1381
闹比i
闹比i 2021-02-06 10:49

I\'m trying to implement your basic UPSERT functionality, but with a twist: sometimes I don\'t want to actually update an existing row.

Essentially I\'m trying to synchr

相关标签:
5条回答
  • 2021-02-06 11:07

    I slapped together the following script to proof this trick I used in years past. If you use it, you'll need to modify it to suit your purposes. Comments follow:

    /*
    CREATE TABLE Item
     (
       Title      varchar(255)  not null
      ,Teaser     varchar(255)  not null
      ,ContentId  varchar(30)  not null
      ,RowLocked  bit  not null
    )
    
    
    UPDATE item
     set RowLocked = 1
     where ContentId = 'Test01'
    
    */
    
    
    DECLARE
      @Check varchar(30)
     ,@pContentID varchar(30)
     ,@pTitle varchar(255)
     ,@pTeaser varchar(255)
    
    set @pContentID = 'Test01'
    set @pTitle     = 'TestingTitle'
    set @pTeaser    = 'TestingTeasier'
    
    set @check = null
    
    UPDATE dbo.Item
     set
       @Check = ContentId
      ,Title  = @pTitle
      ,Teaser = @pTeaser
     where ContentID = @pContentID
      and RowLocked = 0
    
    print isnull(@check, '<check is null>')
    
    IF @Check is null
        INSERT dbo.Item (ContentID, Title, Teaser, RowLocked)
         values (@pContentID, @pTitle, @pTeaser, 0)
    
    select * from Item
    

    The trick here is that you can set values in local variables within an Update statement. Above, the "flag" value gets set only if the update works (that is, the update criteria are met); otherwise, it won't get changed (here, left at null), you can check for that, and process accordingly.

    As for the transaction and making it serializable, I'd like to know more about what must be encapsulated within the transaction before suggesting how to proceed.

    -- Addenda, follow-up from second comment below -----------

    Mr. Saffron's ideas are a thorough and solid way of implementing this routine since your primary keys are defined outside and passed into the database (i.e. you're not using identity columns--fine by me, they are often overused).

    I did some more testing (added a primary key constraint on column ContentId, wrap the UPDATE and INSERT in a transaction, add the serializable hint to the update) and yes, that should do everything you want it to. The failed update slaps a range lock on that part of the index, and that will block any simultaneous attempts to insert that new value in the column. Of course, if N requests are submitted simultaneously, the "first" will create the row, and it will be immediately updated by the second, third, etc.--unless you set the "lock" somewhere along the line. Good trick!

    (Note that without the index on the key column, you'd lock the entire table. Also, the range lock may lock the rows on "either side" of the new value--or maybe they won't, I didn't test that one out. Shouldn't matter, since the duration of the operation should [?] be in single-digit milliseconds.)

    0 讨论(0)
  • 2021-02-06 11:13

    I'd drop the transaction.

    Plus the @@rowcount probably would work, but using global variables as a conditional check will lead to bugs.

    Just do an Exists() check. You have to make a pass through the table anyhow, so speed is not the issue.

    No need for the transaction as far as I can see.

    0 讨论(0)
  • 2021-02-06 11:14

    You could switch the order of the update/insert around. So you do the insert within a try/catch and if you get a constraint violation then do the update. It feels a little dirty though.

    0 讨论(0)
  • 2021-02-06 11:15

    CREATE PROCEDURE [dbo].[usp_UpsertItem] -- Add the parameters for the stored procedure here @pContentID varchar(30) = null, @pTitle varchar(255) = null, @pTeaser varchar(255) = null AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON;

    BEGIN TRANSACTION
        IF EXISTS (SELECT 1 FROM dbo.Item WHERE ContentID = @pContentID
                 AND RowLocked = false)
           UPDATE dbo.Item 
           SET Title = @pTitle, Teaser = @pTeaser
           WHERE ContentID = @pContentID
                 AND RowLocked = false
        ELSE IF NOT EXISTS (SELECT 1 FROM dbo.Item WHERE ContentID = @pContentID)
                INSERT INTO dbo.Item (ContentID, Title, Teaser)
                VALUES (@pContentID, @pTitle, @pTeaser)
    
    COMMIT TRANSACTION
    

    END

    0 讨论(0)
  • 2021-02-06 11:17
    BEGIN TRANSACTION
    
    IF EXISTS(SELECT 1 FROM dbo.Item WHERE ContentID = @pContentID)
         UPDATE dbo.Item WITH (SERIALIZABLE)
         SET Title = @pTitle, Teaser = @pTeaser
         WHERE ContentID = @pContentID
         AND RowLocked = false
    ELSE
         INSERT INTO dbo.Item
              (ContentID, Title, Teaser)
         VALUES
              (@pContentID, @pTitle, @pTeaser)
    
    COMMIT TRANSACTION
    
    0 讨论(0)
提交回复
热议问题