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!
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!
Share Improve this question edited Nov 19, 2024 at 14:47 Charlieface 73.6k8 gold badges33 silver badges64 bronze badges asked Nov 19, 2024 at 12:08 JohnnyJohnny 455 bronze badges 3 |1 Answer
Reset to default 2No, 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;
db<>fiddle
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:
- You should really use a
HOLDLOCK
hint onMERGE
if you are using production tables rather than temp tables. - Be careful with
WHEN NOT MATCHED BY SOURCE
, because you need to ensure that theON
clause only specifies the matching criteria, not any extra filters, or you will end up deleting more than intended.
If you need to merge only a subset then pre-filter the target with a CTE.
MERGE
, eh? Honestly, you are likely better off withINSERT
/UPDATE
/DELETE
statements, with appropriate lock hints, inside a transaction. – Thom A Commented Nov 19, 2024 at 12:16delete output
? But I now understand at least the underlying issue, thanks! – Johnny Commented Nov 19, 2024 at 15:18