The MERGE statement
is a powerful new feature but, again, we need to be mindful of its use
alongside existing code. In this example, I will demonstrate how a
previously working trigger may start to misbehave when called from a MERGE statement.
Consider the trigger shown in Listing 1, designed to prevent the deletion of more than one row at a time from the FrontPageArticles table.
We can test out our trigger using the code shown in Listing 2.
So, we have proven that the trigger works when it is fired by a DELETE command. Note that our trigger implicitly assumes that @@ROWCOUNT is equal to the number of rows being deleted. Prior to SQL Server 2008, issuing a DELETE command is the only way to fire an AFTER DELETE trigger, so this assumption is correct.
In SQL Server 2008, however, we can also fire this trigger by executing a MERGE command. After a MERGE command completes, @@ROWCOUNT
is set to the total number of rows affected by it, including inserted,
updated, and deleted rows. Therefore, our assumption is no longer
correct and our trigger is broken, as is shown in the following example.
The intent of the MERGE command shown in Listing 3
is to modify one row and delete exactly one row, yet the trigger
mistakenly interprets this command as an attempt to delete two rows,
because @@ROWCOUNT is set to 2.
Let us drop the trigger altogether, as shown in Listing 4.
And now we can rerun the code from Listing 3 and see that this MERGE command modified one row and deleted only one row, so it should not have failed.
We have proved that, when we start using MERGE,
some of our triggers may malfunction. Again, assuming this trigger was
developed prior to SQL Server 2008, I would argue that there is little
the developer could have done at the time to anticipate that it might no
longer work in a later version. However, at the point where we upgrade
to SQL Server 2008, the defensive programmer must get to work
investigating how the use of MERGE might affect existing code, and fixing the issues that are found.
In this case, the fix is quite straightfoward, as shown in Listing 5. Rather than rely on @@ROWCOUNT, we simply query the deleted table to ensure that we are not deleting more than one row at a time.
To test this trigger, just rerun Listings 2 and 3; they both produce the expected results, proving that now the trigger works when it is fired by our MERGE command as well as when it is fired by DELETE.