Login or Sign Up to become a member!
LessThanDot Sit Logo

LessThanDot

Data Management

Less Than Dot is a community of passionate IT professionals and enthusiasts dedicated to sharing technical knowledge, experience, and assistance. Inside you will find reference materials, interesting technical discussions, and expert tips and commentary. Once you register for an account you will have immediate access to the forums and all past articles and commentaries.

LTD Social Sitings

Lessthandot twitter Lessthandot Linkedin Lessthandot friendfeed Lessthandot facebook Lessthandot rss

Note: Watch for social icons on posts by your favorite authors to follow their postings on these and other social sites.

Your profile

    Search

    XML Feeds

    Google Ads

    « SQL Advent 2011 Day 11: DML statements with the OUTPUT clausePresentation Files: EDMPASS and ABQ SQL »
    comments

    In my Are you ready for SQL Server 2012 or are you still partying like it is 1999? post, I wrote about how you should start using SQL Server 2005 and SQL Server 2008 functionality now in order to prepare for SQL Server 2012. I still see tons of code that is written in the pre 2005 style and people still keep using those functions, procs and statements even though SQL Server 2005 and 2008 have much better functionality.

    Today we are going to take a look at how to do an upsert by using the MERGE statement. If you do not know what an upsert is, it basically will update rows in the table if there are already rows that match your condition, otherwise it will insert into the table

    First let's take a look at how you would do this before SQL Server 2008

    Create this simple table

    1. CREATE TABLE TestMerge (id int not null, SomeName varchar(100) not null)
    2. go
    3.  
    4.  
    5. INSERT TestMerge values(1,'Denis1')
    6. INSERT TestMerge values(2,'Denis2')
    7. INSERT TestMerge values(3,'Denis3')
    8. INSERT TestMerge values(4,'Denis4')
    9. INSERT TestMerge values(5,'Denis5')
    10. INSERT TestMerge values(6,'Denis6')
    11. INSERT TestMerge values(7,'Denis7')

    One way to do an upsert in the pre 2008 days is to do the following

    1. UPDATE TestMerge
    2. SET SomeName = 'Denis10'
    3. WHERE id = 10
    4. IF @@ROWCOUNT = 0
    5. INSERT INTO TestMerge VALUES (10,'Denis10')
    6.  
    7. SELECT * FROM TestMerge
    8. DELETE TestMerge WHERE ID  =10

    As you can see, we do an update, if @@ROWCOUNT is 0, there were no rows update, this means we have to do an insert

    Here is another way

    1. IF EXISTS (SELECT 1 FROM TestMerge
    2.             WHERE ID = 10)
    3. BEGIN
    4.     UPDATE TestMerge
    5.     SET SomeName = 'Denis11'
    6.     WHERE id = 10
    7. END
    8. ELSE
    9. BEGIN
    10.     INSERT INTO TestMerge VALUES (10,'Denis10')
    11. END
    12.  
    13. DELETE TestMerge WHERE ID  =10

    As you can see we check if we have a row with the id that we are looking for, if the exists check returns true, we update, otherwise we are doing an insert

    Merge

    Now here is how it is done in SQL Server 2008 and up

    Be aware that when you use the MERGE statement, you need to terminate it with a semi-colon, otherwise you'll get the following message

    Msg 10713, Level 15, State 1, Line 10
    A MERGE statement must be terminated by a semi-colon (;).

    Here is an example to look at, I added comments so that you can see what is going on

    1. DECLARE @ID int = 9, @Name varchar(100) = 'BLA10'
    2.  
    3. MERGE TestMerge AS target -- this is the table you are upadting/insert into
    4.     USING (SELECT @ID, @Name) AS source (id, SomeName) -- variables mapped to the columns
    5.     ON (target.id = source.id)   -- this is the key column we want to join on
    6.     WHEN MATCHED THEN            -- if we have a match then we update
    7.         UPDATE SET SomeName = source.SomeName  -- update the SomeName column
    8.     WHEN NOT MATCHED THEN        -- if we don't match then we do an insert
    9.         INSERT (id, SomeName)    -- do an insert
    10.         VALUES (source.id, source.SomeName);  --these values are the ones in the
    11.                                               -- variables mapped to the column

    Let's run an example, this will add a row with the id 9 and the value in the SomeName column will be BLA

    1. DECLARE @ID int = 9, @Name varchar(100) = 'BLA9'
    2.  
    3. MERGE TestMerge AS target
    4.     USING (SELECT @ID, @Name) AS source (id, SomeName)
    5.     ON (target.id = source.id)
    6.     WHEN MATCHED THEN
    7.         UPDATE SET SomeName = source.SomeName
    8.     WHEN NOT MATCHED THEN   
    9.         INSERT (id, SomeName)
    10.         VALUES (source.id, source.SomeName);
    11.  
    12. SELECT * FROM TestMerge


    Output
    --------------
    1	Denis1
    2	Denis2
    3	Denis3
    4	Denis4
    5	Denis5
    6	Denis6
    7	Denis7
    9	BLA9

    As you can see the row got added.

    Now let's try to use the same ID but we will change the value from BLA9 to BLA10

    1. DECLARE @ID int = 9, @Name varchar(100) = 'BLA10'
    2.  
    3. MERGE TestMerge AS target
    4.     USING (SELECT @ID, @Name) AS source (id, SomeName)
    5.     ON (target.id = source.id)
    6.     WHEN MATCHED THEN
    7.         UPDATE SET SomeName = source.SomeName
    8.     WHEN NOT MATCHED THEN   
    9.         INSERT (id, SomeName)
    10.         VALUES (source.id, source.SomeName);
    11.  
    12. SELECT * FROM TestMerge


    Output
    --------------
    1	Denis1
    2	Denis2
    3	Denis3
    4	Denis4
    5	Denis5
    6	Denis6
    7	Denis7
    9	BLA10

    And as you can see for the row with ID 9, the SomeName column was updated.

    You can actually output from the MERGE statement directly what action it is taking and what the value before and after is, very similar with the inserted and deleted pseudo tables in triggers. All we have to do is add

    OUTPUT deleted.*, $action, inserted.*

    Deleted.* will have the values before the update and inserted will have the value what it was updated to, $action will have the value UPDATE, INSERT or DELETE

    1. DECLARE @ID int, @Name varchar(100)
    2. SELECT @ID = 9,@Name = 'BLA9'
    3.  
    4. MERGE TestMerge AS target
    5.     USING (SELECT @ID, @Name) AS source (id, SomeName)
    6.     ON (target.id = source.id)
    7.     WHEN MATCHED THEN
    8.         UPDATE SET SomeName = source.SomeName
    9.             WHEN NOT MATCHED THEN   
    10.         INSERT (id, SomeName)
    11.         VALUES (source.id, source.SomeName)
    12.         OUTPUT deleted.*, $action, inserted.*;


    Output
    id	SomeName    $action	id	SomeName
    9	BLA10	    UPDATE	9	BLA9

    Let's run the same exact statement again

    1. DECLARE @ID int, @Name varchar(100)
    2. SELECT @ID = 9,@Name = 'BLA9'
    3.  
    4. MERGE TestMerge AS target
    5.     USING (SELECT @ID, @Name) AS source (id, SomeName)
    6.     ON (target.id = source.id)
    7.     WHEN MATCHED THEN
    8.         UPDATE SET SomeName = source.SomeName
    9.             WHEN NOT MATCHED THEN   
    10.         INSERT (id, SomeName)
    11.         VALUES (source.id, source.SomeName)
    12.         OUTPUT deleted.*, $action, inserted.*;


    Output
    id	SomeName    $action	id	SomeName
    9	BLA9	    UPDATE	9	BLA9

    See what happened, an update was fired but nothing needed to change, what if we don't want the update to fire in that case? Here is what we can do
    Instead of

    WHEN MATCHED THEN

    Add a condition that will check if the SomeName is the same in both the source and the target

    WHEN MATCHED AND target.SomeName <> source.SomeName THEN

    Now when you run this, you won't get any output

    1. DECLARE @ID int, @Name varchar(100)
    2. SELECT @ID = 9,@Name = 'BLA9'
    3.  
    4. MERGE TestMerge AS target
    5.     USING (SELECT @ID, @Name) AS source (id, SomeName)
    6.     ON (target.id = source.id)
    7.     WHEN MATCHED AND target.SomeName <> source.SomeName THEN
    8.         UPDATE SET SomeName = source.SomeName
    9.             WHEN NOT MATCHED THEN   
    10.         INSERT (id, SomeName)
    11.         VALUES (source.id, source.SomeName)
    12.         OUTPUT deleted.*, $action, inserted.*;

    Besides variable, you can also use another table as a source

    First create this table

    1. CREATE TABLE TestMerge2 (id int not null, SomeName varchar(100) not null)
    2. go
    3.  
    4.  
    5. INSERT TestMerge2 values(1,'Denis1 from table existing')
    6. INSERT TestMerge2 values(12,'Denis12 from table new')

    Now we are going to change this line

    USING (SELECT @ID, @Name) AS source

    Change it to the following

    USING (SELECT id, SomeName from TestMerge2) AS source

    That will tell the merge statement to use the TestMerge2 table as the source

    Now run the following

    1. MERGE TestMerge AS target
    2.     USING (SELECT id, SomeName from TestMerge2) AS source (id, SomeName)
    3.     ON (target.id = source.id)
    4.     WHEN MATCHED AND target.SomeName <> source.SomeName THEN
    5.         UPDATE SET SomeName = source.SomeName
    6.             WHEN NOT MATCHED THEN   
    7.         INSERT (id, SomeName)
    8.         VALUES (source.id, source.SomeName)
    9.         OUTPUT deleted.*, $action, inserted.*;

    Output

    id	SomeName	$action	id	SomeName
    NULL	NULL		INSERT	12	Denis12 from table new
    1	Denis1		UPDATE	1	Denis1 from table existing

    Now, let's see what is in the table

    1. SELECT * FROM TestMerge
    id	SomeName
    1	Denis1 from table existing
    2	Denis2
    3	Denis3
    4	Denis4
    5	Denis5
    6	Denis6
    7	Denis7
    9	BLA9
    12	Denis12 from table new

    As you can see the table contains what we expected

    Hopefully this post gave you some ideas on how to use the MERGE statement to do upserts. Come back tomorrow for the next post in this series

    About the Author

    User bio imageDenis has been working with SQL Server since version 6.5. Although he worked as an ASP/JSP/ColdFusion developer before the dot com bust, he has been working exclusively as a database developer/architect since 2002. In addition to English, Denis is also fluent in Croatian and Dutch, but he can curse in many other languages and dialects (just ask the SQL optimizer) He lives in Princeton, NJ with his wife and three kids.
    Social SitingsTwitterFacebookLinkedInHomePageFlickrLTD RSS Feed
    2489 views
    InstapaperVote on HN

    No feedback yet

    Leave a comment


    Your email address will not be revealed on this site.

    To mislead the spambots.

    Your URL will be displayed.
    (Line breaks become <br />)
    (Name, email & website)
    (Allow users to contact you through a message form (your email will not be revealed.)