What I'm trying to achieve is a combined merge statement, that updates/deletes/moves data based on related metadata, somewhat like the following (MWS at the end):
merge #data as target
using #metadata as source
on (...)
when matched and source.state = 1
then update ...
when matched and source.state = 0
then delete
when matched and source.state = -1
then delete output deleted... into *somewhere else*
;
This produces the following error:
An action of the WHEN MATCHED type may not occur more than once in a DELETE clause of a MERGE statement.
(translated)
Could someone elaborate on the error? How can I achieve the desired behaviour?
Here is a MWS:
CREATE TABLE #data
(
id int IDENTITY(1,1),
description nvarchar(100) NOT NULL,
metadata int NULL
)
CREATE TABLE #anotherTable
(
id int identity(1,1),
description nvarchar(100) NOT NULL
)
INSERT INTO #data (description)
VALUES (N'data'),
(N'more data'),
(N'example'),
(N'unknown')
CREATE TABLE #metadata
(
id int IDENTITY(1,1),
description nvarchar(100) NOT NULL,
metadata int NOT NULL,
state int NOT NULL DEFAULT -1
)
INSERT INTO #metadata (description, metadata, state)
VALUES (N'data', 10, 1),
(N'more data', 11, 0),
(N'example', 12, -1)
MERGE #data AS target
USING #metadata AS source
ON (target.description = source.description)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
WHEN MATCHED AND source.state = 1 THEN
UPDATE SET target.metadata = source.metadata
WHEN MATCHED AND source.state = 0 THEN
DELETE
WHEN MATCHED AND source.state = -1 THEN
DELETE OUTPUT deleted.description INTO #anotherTable (description);
SELECT * FROM #data
SELECT * FROM #anotherTable
SELECT * FROM #metadata
DROP TABLE #data, #metadata, #anotherTable
Appreciate your help!
No, you cannot do this directly. The OUTPUT
clause, contrary to your confusing formatting, actually applies to the whole MERGE
, and you should format it separate from the rest of the MERGE
.
You are also not allowed to have multiple WHEN MATCHED
clauses, unless one has an AND
filter and one does not (so no more than two). You'd need to remove the filter on state
, or pre-filter the source.
One option is to put the MERGE...OUTPUT
in a derived table (not a CTE as that's not supported) then INSERT
off that with a WHERE
.
INSERT #anotherTable (description)
SELECT description
FROM (
MERGE #data AS target
USING #metadata AS source
ON (target.description = source.description)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
WHEN MATCHED AND source.state = 1 THEN
UPDATE SET target.metadata = source.metadata
WHEN MATCHED THEN
DELETE
OUTPUT $action as action, deleted.*, source.state
) MergeOutput
WHERE action = 'DELETE'
AND state = -1;
The above solution only works on simple WHERE
filters, you cannot use joins etc.
Another option is to just insert into a table variable then filter and re-insert from there.
A couple of side notes:
HOLDLOCK
hint on MERGE
if you are using production tables rather than temp tables.WHEN NOT MATCHED BY SOURCE
, because you need to ensure that the ON
clause only specifies the matching criteria, not any extra filters, or you will end up deleting more than intended.