If you’re using the MERGE
statement in SQL Server to merge data between tables, you may or may not be aware that the statement gives us the ability to check which rows were affected by the MERGE
operation, as well as how they were affected.
To get this info, we can use the OUTPUT
clause along with the $action
argument.
About the OUTPUT
Clause
The (optional) OUTPUT
clause captures and returns the rows affected by an INSERT
, UPDATE
, DELETE
, or MERGE
statement. It allows us to access the changes made during the execution, providing insight into how the data was modified.
The OUTPUT
clause can return:
- Inserted values: Data that was added to the target table.
- Deleted values: Data that was removed from the target table.
- Updated values: Data that was modified in the target table (i.e. the “before” and “after” values).
When doing a MERGE
operation, we can use this clause to get a combination of inserted, deleted, and updated rows (depending on which operation was performed on each row).
About the $action
Argument
The $action
argument can be used with the OUTPUT
clause in order to tell us whether the operation resulted in an insert, an update, or a delete for the given row. It returns a varchar(10)
with the action that was performed during the merge (INSERT
, UPDATE
, or DELETE
).
The way it works is that, the OUTPUT
clause returns each row that was affected by the operation. We can get the actual values when we do this (for example, the old and new values in the case of an UPDATE
). But when we’re doing a merge, we can also use $action
to tell us whether it was an INSERT
, UPDATE
, or DELETE
operation.
The $action
argument is exclusive to MERGE
operations (it can’t be used on other operations like INSERT
, or UPDATE
, or DELETE
).
Example of MERGE
with the $action
Argument
This is probably one case where an example is better than words. If you’re struggling to understand my explanation of the $action
argument, it should all become clear once you see the result of the following example.
Setup: Create Sample Tables
Suppose we create the following source and target tables and insert data:
-- Create Target Table
CREATE TABLE TargetTable (
ID INT PRIMARY KEY,
Name NVARCHAR(50),
Quantity INT
);
-- Create Source Table
CREATE TABLE SourceTable (
ID INT PRIMARY KEY,
Name NVARCHAR(50),
Quantity INT
);
-- Insert Data into Target Table
INSERT INTO TargetTable (ID, Name, Quantity)
VALUES
(1, 'Product A', 50),
(2, 'Product B', 30),
(3, 'Product C', 20);
-- Insert Data into Source Table
INSERT INTO SourceTable (ID, Name, Quantity)
VALUES
(2, 'Product B', 45), -- Updated record
(3, 'Product C', 20), -- Same record
(4, 'Product D', 60); -- New record
We’ll use this for our MERGE
operation.
The Merge Operation
Now let’s perform a MERGE
operation. We’ll use the OUTPUT
clause to output the results of the operation:
MERGE TargetTable AS target
USING SourceTable AS source
ON target.ID = source.ID
WHEN MATCHED THEN
UPDATE SET target.Name = source.Name,
target.Quantity = source.Quantity
WHEN NOT MATCHED BY TARGET THEN
INSERT (ID, Name, Quantity)
VALUES (source.ID, source.Name, source.Quantity)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
-- Use the OUTPUT clause to return the result of the MERGE
OUTPUT
$action AS Operation,
DELETED.ID AS OldID,
DELETED.Name AS OldName,
DELETED.Quantity AS OldQuantity,
INSERTED.ID AS NewID,
INSERTED.Name AS NewName,
INSERTED.Quantity AS NewQuantity;
Output:
Operation OldID OldName OldQuantity NewID NewName NewQuantity
--------- ----- --------- ----------- ----- --------- -----------
DELETE 1 Product A 50 null null null
UPDATE 2 Product B 30 2 Product B 45
UPDATE 3 Product C 20 3 Product C 20
INSERT null null null 4 Product D 60
We can see all the details of the merge. Importantly, we can see whether a row was inserted, updated, or deleted. We can see that because we used the $action
argument (first column).
As mentioned, the $action
argument is only available for MERGE
operations.
Output the Changes into a Table
You might be thinking that the information provided by the OUTPUT
clause and its associated $action
argument would be invaluable to have, if only we could save that data somewhere.
Well I’ve got good news for you! We can insert the result of the OUTPUT
clause into a table or a table variable if required. And even better news; it’s very straightforward to do.
For example, we could create a log table like this:
CREATE TABLE MergeLog (
Action NVARCHAR(50),
OldID INT,
OldName NVARCHAR(50),
OldQuantity INT,
NewID INT,
NewName NVARCHAR(50),
NewQuantity INT
);
That contains all the columns required to store the result of our OUTPUT
statement, including the $action
argument.
We can now update our OUTPUT
statement to include INTO MergeLog
:
-- Do the merge operation
MERGE TargetTable AS target
USING SourceTable AS source
ON target.ID = source.ID
WHEN MATCHED THEN
UPDATE SET target.Name = source.Name,
target.Quantity = source.Quantity
WHEN NOT MATCHED BY TARGET THEN
INSERT (ID, Name, Quantity)
VALUES (source.ID, source.Name, source.Quantity)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
-- Use the OUTPUT clause to log the changes in the MergeLog table
OUTPUT
$action AS Operation,
DELETED.ID AS OldID,
DELETED.Name AS OldName,
DELETED.Quantity AS OldQuantity,
INSERTED.ID AS NewID,
INSERTED.Name AS NewName,
INSERTED.Quantity AS NewQuantity
INTO MergeLog;
-- Get the results from the log table
SELECT * FROM MergeLog;
Output:
Action OldID OldName OldQuantity NewID NewName NewQuantity
------ ----- --------- ----------- ----- --------- -----------
DELETE 1 Product A 50 null null null
UPDATE 2 Product B 30 2 Product B 45
UPDATE 3 Product C 20 3 Product C 20
INSERT null null null 4 Product D 60
We can see that the table contains the same result that was returned in the previous example.
Note that I dropped all tables and started again before I ran this example in order to demonstrate that the same data was stored in the table.