【问题标题】:Merge statement error when nulls in the column列中为空时合并语句错误
【发布时间】:2013-01-11 09:31:33
【问题描述】:

我有一个合并语句

目的地表 =~ DST 源表 =~ 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

表结构

Source

Column1 Column2 Column3 Column4
A        A       NULL    NULL
B        B       NULL    NULL

Destination

Column1 Column2 COlumn3 Column4 Column5

错误

MERGE 语句多次尝试更新或删除同一行。当目标行匹配多个源行时会发生这种情况。 MERGE 语句不能多次 UPDATE/DELETE 目标表的同一行。优化 ON 子句以确保目标行最多匹配一个源行,或使用 GROUP BY 子句对源行进行分组。

但是当我使用下面的查询时(即只使用那些有一些值而不是空值的列,如 SRC 中的 Column3 和 Column 4)一切正常

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

编辑:复制场景

源表

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

目的地表

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

数据来源

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)

目的地为空

合并语句

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) ;

运行两次就可以看到错误了。

现在截断目标表

现在是相同的查询,但只有那些有数据的列

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) ;

这运行得非常好,您可以多次运行它。

【问题讨论】:

  • 为了更快地得到这个问题的答案,我认为您应该花一些时间添加一些示例代码,以实际显示您遇到的问题。您在此处发布的代码中有很多错误,因此很难弄清楚您的问题到底是什么。
  • @Mikael Eriksson 我已经用代码和步骤更新了我的问题以复制场景。希望这会有所帮助。
  • 您的源样本包含两行完全相同的数据。我猜它们都是第一次插入的,但是如果目标包含匹配的行,它将匹配两个源行,你会遇到同样的问题。你的真实数据是这样的吗?是否没有一个或多个键列可以让您将每个目标行与不超过一个源行进行匹配?
  • 您解决了这个问题还是还有问题?
  • 使用适当的括号来解决 Mikael 下面提到的问题。这是唯一的问题。

标签: sql-server-2008 join merge sql-server-2008-r2


【解决方案1】:

这是Operator Precedence的问题。

DST.Column1 = SRC.Column1 OR (DST.Column1 IS NULL AND SRC.Column1 IS NULL) .... 将匹配 DST.Column1 = SRC.Column1 所在的所有行,而不管其他列中的值如何。

您需要在 OR:ed 条件周围添加一些括号以覆盖默认优先级。

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) ;

【讨论】:

    猜你喜欢
    • 2017-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-12
    • 2011-12-11
    • 1970-01-01
    相关资源
    最近更新 更多