Zerotoinfinity
Zerotoinfinity

Reputation: 6530

Merge statement error when nulls in the column

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

Answers (1)

Mikael Eriksson
Mikael Eriksson

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

Related Questions