Reputation: 6530
I have a merge statement
DESTINMATION TABLE =~ DST SOURCE TABLE =~ SRC
MERGE DST
USING (SELECT * FROM SRC WHERE <Some_Condition>) SRC
ON SCR.COL1 = DST.COL1 OR (SRC.COL1 IS NULL AND DST.COl1 IS NULL) AND
ON SCR.COL2 = DST.COL2 OR (SRC.COL2 IS NULL AND DST.COl2 IS NULL) AND
ON SCR.COL3 = DST.COL3 OR (SRC.COL3 IS NULL AND DST.COl3 IS NULL) AND
ON SCR.COL4 = DST.COL4 OR (SRC.COL4 IS NULL AND DST.COl4 IS NULL)
WHEN MATCHED UPDATE DST
WHEN NOT MATCHED BY SOURCE THEN UPDATE DST
WHEN NOT MATCHED INSERT IN DST
Table Structure
Source
Column1 Column2 Column3 Column4
A A NULL NULL
B B NULL NULL
Destination
Column1 Column2 COlumn3 Column4 Column5
Error
The MERGE statement attempted to UPDATE or DELETE the same row more than once. This happens when a target row matches more than one source row. A MERGE statement cannot UPDATE/DELETE the same row of the target table multiple times. Refine the ON clause to ensure a target row matches at most one source row, or use the GROUP BY clause to group the source rows.
But when I am using below query (i.e. using only those columns which has some values and not null values like Column3 and Column 4 in SRC) Everything works fine
MERGE DST
USING (SELECT * FROM SRC WHERE <Some_Condition>) SRC
ON SCR.COL1 = DST.COL1 OR (SRC.COL1 IS NULL AND DST.COl1 IS NULL) AND
ON SCR.COL2 = DST.COL2 OR (SRC.COL2 IS NULL AND DST.COl2 IS NULL)
WHEN MATCHED UPDATE DST
WHEN NOT MATCHED BY SOURCE THEN UPDATE DST
WHEN NOT MATCHED INSERT IN DST
EDIT: Replicate the scenario
SOURCE TABLE
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[tblSource](
[Column1] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Column2] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Column3] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Column4] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Column5] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
DESTINATION TABLE
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[tblDestination](
[Column1] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Column2] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Column3] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Column4] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Column5] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Column6] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
DATA IN SOURCE
INSERT [dbo].[tblSource] ([Column1], [Column2], [Column3], [Column4], [Column5]) VALUES (N'A', N'A', NULL, NULL, NULL)
INSERT [dbo].[tblSource] ([Column1], [Column2], [Column3], [Column4], [Column5]) VALUES (N'B', N'B', NULL, NULL, NULL)
Destination is empty
Merge Statement
MERGE dbo.tblDestination DST
USING (SELECT * FROM dbo.tblSource) SRC
ON DST.Column1 = SRC.Column1 OR (DST.Column1 IS NULL AND SRC.Column1 IS NULL) AND
DST.Column2 = SRC.Column2 OR (DST.Column2 IS NULL AND SRC.Column2 IS NULL) AND
DST.Column3 = SRC.Column3 OR (DST.Column3 IS NULL AND SRC.Column3 IS NULL) AND
DST.Column4 = SRC.Column4 OR (DST.Column4 IS NULL AND SRC.Column4 IS NULL) AND
DST.Column5 = SRC.Column5 OR (DST.Column5 IS NULL AND SRC.Column5 IS NULL)
WHEN MATCHED THEN
UPDATE SET COLUMN5 = 'A'
WHEN NOT MATCHED BY SOURCE THEN
UPDATE SET Column5 = 'B'
WHEN NOT MATCHED THEN
INSERT (Column1, Column2, Column3, Column4, Column5)
VALUES (Column1, Column2, Column3, Column4, Column5) ;
Run it two times to see the error.
Now truncate the destination table
Now the same query but having only those columns which has data
MERGE dbo.tblDestination DST
USING (SELECT * FROM dbo.tblSource) SRC
ON DST.Column1 = SRC.Column1 OR (DST.Column1 IS NULL AND SRC.Column1 IS NULL) AND
DST.Column2 = SRC.Column2 OR (DST.Column2 IS NULL AND SRC.Column2 IS NULL)
WHEN MATCHED THEN
UPDATE SET COLUMN5 = 'A'
WHEN NOT MATCHED BY SOURCE THEN
UPDATE SET Column5 = 'B'
WHEN NOT MATCHED THEN
INSERT (Column1, Column2, Column3, Column4, Column5)
VALUES (Column1, Column2, Column3, Column4, Column5) ;
This is working absouletly fine, you can run it as many times you want to run.
Upvotes: 0
Views: 4239
Reputation: 138960
It is a matter of Operator Precedence.
DST.Column1 = SRC.Column1 OR (DST.Column1 IS NULL AND SRC.Column1 IS NULL) ....
will match all rows where DST.Column1 = SRC.Column1
regardless of the values in the other columns.
You need to add some parentheses around the OR:ed conditions to override the default precedence.
MERGE dbo.tblDestination DST
USING (SELECT * FROM dbo.tblSource) SRC
ON (DST.Column1 = SRC.Column1 OR (DST.Column1 IS NULL AND SRC.Column1 IS NULL)) AND
(DST.Column2 = SRC.Column2 OR (DST.Column2 IS NULL AND SRC.Column2 IS NULL)) AND
(DST.Column3 = SRC.Column3 OR (DST.Column3 IS NULL AND SRC.Column3 IS NULL)) AND
(DST.Column4 = SRC.Column4 OR (DST.Column4 IS NULL AND SRC.Column4 IS NULL)) AND
(DST.Column5 = SRC.Column5 OR (DST.Column5 IS NULL AND SRC.Column5 IS NULL))
WHEN MATCHED THEN
UPDATE SET COLUMN5 = 'A'
WHEN NOT MATCHED BY SOURCE THEN
UPDATE SET Column5 = 'B'
WHEN NOT MATCHED THEN
INSERT (Column1, Column2, Column3, Column4, Column5)
VALUES (Column1, Column2, Column3, Column4, Column5) ;
Upvotes: 2