views:

84

answers:

2

I am using SQL Server 2008, and would like to be able to take advantage of something like mySQL's ON DUPLICATE KEY UPDATE clause for INSERT statements

Current legacy code does a delete and subsequent insert that is running into concurrency issues with duplicate key inserts from separate threads:

Here is the error I see in my production environment:

Violation of PRIMARY KEY constraint 'PK_Audience'. Cannot insert duplicate key in object 'dbo.Audience'.

(sp_ContentUpdate)

Primary Key:

AudienceId, VersionId

Offending SQL:

DELETE  FROM  dbo.Audience
WHERE   VersionId = @VersionId

IF  @AudienceXml IS NOT NULL
    BEGIN
    INSERT INTO dbo.Audience (
        VersionId,
        AudienceId,
        CreatedDate,
        CreatedByPersonId,
        )
    SELECT  @VersionId,
            AudienceId,
            GETUTCDATE(),
            @PersonId
                FROM    dbo.Audience
    JOIN    @AudienceXml.nodes('/Audiences/Audience') node(c)
    ON      Audience.AudienceName = c.value('@Name', 'nvarchar(50)')
    END

Wrapping this TSQL in a transaction seems to either remove the concurrency issue or mask the issue by changing the timings. However, I do not think wrapping in a transaction has actually solved the concurrency.

Perhaps I am going about this wrong. Your suggestions are appreciated.

+7  A: 

You should read about how to use the MERGE statement in Microsoft SQL Server 2008. This is actually the ANSI/ISO SQL way of handling this situation (MySQL's ON DUPLICATE KEY is a proprietary MySQLism).

See docs on the MERGE statement at MSDN.

Bill Karwin
+1: Drats, beat me to it.
OMG Ponies
+7  A: 

Well, Bill beat us all, but here's a sample of what it might look like:

Merge dbo.Audience As target
Using   (
        Select @VersionId As VersionId, AudienceId, GetUtcDate() As CreatedDate, @PersonId As CreatedByPersonId
        From dbo.Audience
            Join @AudienceXml.nodes('/Audiences/Audience') node(c)
                On Audience.AudienceName = c.value('@Name', 'nvarchar(50)')
        )
When Matched Then
    Update 
    Set VersoinId = target.VersionId, Audience = target.AudienceId
        , CreatedDate = target.CreatedDate
        , CreatedByPersionId = target.CreatedByPersonId
When Not Matched Then
    Insert dbo.Audience(VersionId, AudienceId, CreatedDate, CreatedByPersonId)
Thomas
+1 Cheers for the example, Thomas.
Chris Ballance
+1 Wow, thanks for doing the work.
Bill Karwin
@Bill Karwin - Thx. When you answered, I felt like Rusty's character when he tries to recruit Saul in Ocean's 11. "Thomas, I saw the answer before you got up this morning." But, the brownie points for a bit of extra work is nice too. :)
Thomas