The MERGE statement doesn’t have a WHERE clause.
5September 30, 2013 by Kenneth Fisher
What the heck? Even indexes have WHERE clauses these days. I can’t remember what I was reading when I saw this but it completely flabbergasted me. If you go look at MERGE in BOL you will see what I mean. So does this mean you can’t restrict data in a MERGE?
No, of course not. There are in fact several different ways I know of off-hand that you can handle it. You can use subqueries and CTEs to modify your source or you can add search conditions to your WHEN MATCHED and WHEN NOT MATCHED clauses.
Here is an example.
Adventureworks is having a major reorg. First we set up the update table (note this is just a sample of the extensive changes they are making).
USE AdventureWorks2008; GO SELECT TOP 0 CAST(NULL AS smallint) AS DepartmentID, Name, GroupName INTO dbo.DepartmentReorg FROM HumanResources.Department; GO INSERT INTO dbo.DepartmentReorg VALUES (10, 'Accounting', 'Executive General and Administration'); INSERT INTO dbo.DepartmentReorg VALUES (11, 'Information Technology', 'Executive General and Administration'); INSERT INTO dbo.DepartmentReorg VALUES (12, 'Document Control', 'Executive General and Administration'); INSERT INTO dbo.DepartmentReorg VALUES (13, 'Quality Assurance', 'Executive General and Administration'); INSERT INTO dbo.DepartmentReorg VALUES (NULL, 'Foreign Sales', 'Sales and Marketing'); GO
Now the MERGE statement
MERGE INTO HumanResources.Department Dep USING DepartmentReorg Reorg ON Dep.DepartmentID = Reorg.DepartmentID WHEN MATCHED THEN UPDATE SET Name = Reorg.Name, GroupName = Reorg.GroupName, ModifiedDate = GetDate() WHEN NOT MATCHED BY TARGET THEN INSERT (Name, GroupName, ModifiedDate) VALUES (Reorg.Name, Reorg.GroupName, GetDate());
But wait, management has decided they aren’t ready for the whole reorg yet. They want just the changes to the “Executive General and Administration” group. They don’t want us to get rid of the reorg table since it took weeks to set up correctly, but they do want only the appropriate changes made. So here an example of possible changes to the MERGE command using search commands.
MERGE INTO HumanResources.Department Dep USING DepartmentReorg Reorg ON Dep.DepartmentID = Reorg.DepartmentID WHEN MATCHED AND Reorg.GroupName = 'Executive General and Administration' THEN UPDATE SET Name = Reorg.Name, GroupName = Reorg.GroupName, ModifiedDate = GetDate() WHEN NOT MATCHED BY TARGET AND Reorg.GroupName = 'Executive General and Administration' THEN INSERT (Name, GroupName, ModifiedDate) VALUES (Reorg.Name, Reorg.GroupName, GetDate());
Of course that requires putting the condition in two places, and if it changes it has to be modified in two places. Although this does give you more fine control if you need it, in our case it makes it easier for mistakes to happen. So here is an example of putting the condition in one place using a subquery.
MERGE INTO HumanResources.Department Dep USING (SELECT * FROM DepartmentReorg WHERE GroupName = 'Executive General and Administration') AS Reorg ON Dep.DepartmentID = Reorg.DepartmentID WHEN MATCHED THEN UPDATE SET Name = Reorg.Name, GroupName = Reorg.GroupName, ModifiedDate = GetDate() WHEN NOT MATCHED BY TARGET THEN INSERT (Name, GroupName, ModifiedDate) VALUES (Reorg.Name, Reorg.GroupName, GetDate());
Still not my favorite since I find in line queries to be somewhat messy and make the code a bit harder to read. So here is an example using a CTE.
WITH Reorg AS (SELECT * FROM DepartmentReorg WHERE GroupName = 'Executive General and Administration') MERGE INTO HumanResources.Department Dep USING Reorg ON Dep.DepartmentID = Reorg.DepartmentID WHEN MATCHED THEN UPDATE SET Name = Reorg.Name, GroupName = Reorg.GroupName, ModifiedDate = GetDate() WHEN NOT MATCHED BY TARGET THEN INSERT (Name, GroupName, ModifiedDate) VALUES (Reorg.Name, Reorg.GroupName, GetDate());
Take your pick of course.
Wouldn’t the better place be the ON clause for additional filtering? That’s how I do it.
MERGE INTO trg
USING src
ON trg.col = src.col
AND src.Something = ‘something else’
Good point. I tend to try to keep my filtering out of the ON clause by habit so I didn’t even think of that. In this case that probably is a good place to put it.
Per Microsoft, any row filtering should be handled in the WHEN clause, not in the ON clause as it may cause unexpected results.
http://technet.microsoft.com/en-us/library/bb522522(v=sql.105).aspx
Hmm. I hadn’t thought of that. If I read it correctly basically what it is saying that if you try to use filtering in the ON clause (which can be tricky even in a SELECT statement) that you may incorrectly push rows out of the WHEN MATCHED clause and into one of the WHEN NOT MATCHED clauses. I think I will stick with my preferred CTE method, combined with ANDs on the MATCHED/NOT MATCHED clauses as needed.
I’ve used a where after the update clause inside a merge once. Not sure if its a ‘good’ practice but it worked 🙂