【问题标题】:T-SQL MERGE won't handle NULLs as expectedT-SQL MERGE 不会按预期处理 NULL
【发布时间】:2018-09-10 14:10:57
【问题描述】:

如果这个查询:

SELECT CONCAT(SOURCE.OrderNo, '_', SOURCE.OrderLine), 
       SOURCE.ProdOrder, 
       SOURCE.Lvl1, 
       SOURCE.Lvl2, 
       SOURCE.Lvl3, 
       SOURCE.LastDate 
FROM   dbo.SourceTbl AS SOURCE 

返回 11 条记录和这个查询:

SELECT CONCAT(TARGET.OrderNo, '_', TARGET.OrderLine), 
       TARGET.ProdOrder, 
       TARGET.Lvl1, 
       TARGET.Lvl2, 
       TARGET.Lvl3, 
       TARGET.LastDate 
FROM   dbo.TargetTbl AS TARGET 

返回 17 条记录和两者之间的 INTERSECT:

SELECT CONCAT(SOURCE.OrderNo, '_', SOURCE.OrderLine), 
       SOURCE.ProdOrder, 
       SOURCE.Lvl1, 
       SOURCE.Lvl2, 
       SOURCE.Lvl3, 
       SOURCE.LastDate 
FROM   dbo.SourceTbl AS SOURCE 
INTERSECT 
SELECT CONCAT(TARGET.OrderNo, '_', TARGET.OrderLine), 
       TARGET.ProdOrder, 
       TARGET.Lvl1, 
       TARGET.Lvl2, 
       TARGET.Lvl3, 
       TARGET.LastDate 
FROM   dbo.TargetTbl AS TARGET 

当我像这样进行 MERGE 时返回 9 条记录:

MERGE dbo.TargetTbl AS TARGET
USING (
       SELECT   OrderNo, OrderLine, CONCAT(OrderNo, '_', OrderLine) AS OrderNoLine, SomeModel, ProdOrder, Lvl1, Lvl2, Lvl3,
                MAX(LastDate) AS LastDate
       FROM dbo.SourceTbl
       GROUP BY OrderNo, OrderLine, CONCAT(OrderNo, '_', OrderLine), SomeModel, ProdOrder, Lvl1, Lvl2, Lvl3
      ) AS SOURCE 
      ON CONCAT(TARGET.OrderNo, '_', TARGET.OrderLine) = OrderNoLine 
         AND TARGET.ProdOrder = SOURCE.ProdOrder
         AND TARGET.Lvl1 = SOURCE.Lvl1
         AND TARGET.Lvl2 = SOURCE.Lvl2
         AND TARGET.Lvl3 = SOURCE.Lvl3  
         AND TARGET.LastDate = SOURCE.LastDate
WHEN MATCHED AND EXISTS (SELECT CONCAT(SOURCE.OrderNo, '_', SOURCE.OrderLine)
                               ,SOURCE.ProdOrder
                               ,SOURCE.Lvl1
                               ,SOURCE.Lvl2
                               ,SOURCE.Lvl3 
                               ,SOURCE.LastDate
                         INTERSECT 
                         SELECT CONCAT(TARGET.OrderNo, '_', TARGET.OrderLine)
                               ,TARGET.ProdOrder
                               ,TARGET.Lvl1
                               ,TARGET.Lvl2
                               ,TARGET.Lvl3
                               ,TARGET.LastDate
                        )
THEN UPDATE SET TARGET.IsBlocked = 1, TARGET.BlockDate = GETDATE()
WHEN NOT MATCHED BY TARGET 
THEN INSERT (LastDate, UsrID, DepID, OrderNo, OrderLine, SomeModel, ProdOrder, Lvl1, Lvl2, Lvl3, IsBlocked, BlockDate)
     VALUES (SOURCE.LastDate, 999, 999, SOURCE.OrderNo, SOURCE.OrderLine, SOURCE.SomeModel, SOURCE.ProdOrder, SOURCE.Lvl1, SOURCE.Lvl2, SOURCE.Lvl3, 1, GETDATE());

它应该根据thisthis 更新 TargetTbl 的 9 条 INTERSECT 记录,并将 SourceTbl 中的剩余 2 条记录(总共 11 条)插入到同一个表中。相反,它更新 4 条记录并插入 6 条记录(总共 10 条)。 SourceTbl 中有两条记录重复,这就是 10 而不是 11 的原因,这也是我使用 MAX & GROUP BY 的原因。

我认为这是查询的第一部分,即 USING 部分,即使 INTERSECT 部分完成了它的工作,它也无法正确处理 NULL。我尝试了我能做的一切,但没有成功。我相信这很容易做到,所以请帮助我。谢谢。

编辑:SourceTbl 数据使用SELECT OrderNo, OrderLine, CONCAT(OrderNo, '_', OrderLine) AS OrderNoLine, SomeModel, ProdOrder, Lvl1, Lvl2, Lvl3, LastDate AS LastDate FROM dbo.SourceTbl ORDER BY OrderNo, OrderLine, SomeModel, ProdOrder,省略不相关的列:

OrderNo OrderLine   OrderNoLine SomeModel   ProdOrder   Lvl1    Lvl2    Lvl3    LastDate
123c08637   10  123c08637_10    4321525175_004321   A5C008837   Abcd    Efgh    Olol    04/03/2030
123c11214   10  123c11214_10    4321532622_000391   NULL    NULL    NULL    NULL    07/07/2018
123c13039   10  123c13039_10    4321525175_002611   A5C014838   NULL    NULL    NULL    18/05/2018
123c16059   10  123c16059_10    4321541488_001111   A5C018611   NULL    NULL    NULL    18/05/2018
123c17482   10  123c17482_10    4321506480_001711   A5C019227   Asdf    Ghjk    Cvnm    12/12/2018
123c17482   10  123c17482_10    4321506480_001711   A5C047712   Asdf    Ghjk    Cvnm    12/12/2018
123c17482   20  123c17482_20    4321506480_001712   A5B072554   aaaa    bbbb    cccc    18/05/2018
123c17482   20  123c17482_20    4321506480_001712   A5B072554   aaaa    bbbb    cccc    18/05/2018
123c17482   20  123c17482_20    4321506480_001712   A5B072554   aaaa    bbbb    xxxx    18/05/2018
123c17482   20  123c17482_20    4321506480_001712   A5B200472   NULL    NULL    NULL    18/05/2018
123c32405   10  123c32405_10    8765525667_005301   NULL    Qwer    Uiop    Tygh    12/12/2018

【问题讨论】:

  • 如果您添加了一个包含示例数据的脚本,这将使事情更容易讨论和理解。但是我认为您对“处理空值”的想法是错误的。 NULL 不能与任何返回布尔值(真/假)的值进行比较。它返回 。尝试使用空值加入(这是匹配发生的方式)根本行不通。 (待续)
  • 此外,考虑到聚合和交集,您的更新逻辑似乎很可疑。我建议您将其分解为更简单的步骤。首先使用标准更新语句单独尝试更新部分。

标签: sql-server tsql merge null intersect


【解决方案1】:

GROUP BY 可能会将记录数减少到只有一条(如果 11 条记录仅在 LastDate 列中不同,并且如果 SomeModel 包含所有 11 条相同的值记录)或者它可能导致所有 11 条记录(如果 SomeModel 包含唯一值),因此 GROUP BY 不必返回 10 个不同的行。为此,请使用 SELECT DISTINCT 而不是按列的子集进行分组。

此外,如果 ON 条件按您预期的那样工作,则附加的 EXISTS 条件已过时。显然,找到了 4 个匹配项,而 6 个记录没有匹配项。在这 6 条记录中,可能有 2 条记录确实不匹配,4 条记录因为 NULL 值不匹配。

为了处理 NULL 值,我建议将整个语句更改为如下内容:

MERGE dbo.TargetTbl AS TARGET
USING (
       SELECT DISTINCT OrderNo, OrderLine, ProdOrder, Lvl1, Lvl2, Lvl3, LastDate
       FROM dbo.SourceTbl
      ) AS SOURCE 
      ON     (TARGET.OrderNo = SOURCE.OrderNo OR TARGET.OrderNo IS NULL AND SOURCE.OrderNo IS NULL)
         AND (TARGET.OrderLine = SOURCE.OrderLine OR TARGET.OrderLine IS NULL AND SOURCE.OrderLine IS NULL)
         AND (TARGET.ProdOrder = SOURCE.ProdOrder OR TARGET.ProdOrder IS NULL AND SOURCE.ProdOrder IS NULL)
         AND (TARGET.Lvl1 = SOURCE.Lvl1 OR TARGET.Lvl1 IS NULL AND SOURCE.Lvl1 IS NULL)
         AND (TARGET.Lvl2 = SOURCE.Lvl2 OR TARGET.Lvl2 IS NULL AND SOURCE.Lvl2 IS NULL)
         AND (TARGET.Lvl3 = SOURCE.Lvl3 OR TARGET.Lvl3 IS NULL AND SOURCE.Lvl3 IS NULL)
         AND (TARGET.LastDate = SOURCE.LastDate OR TARGET.LastDate IS NULL AND SOURCE.LastDate IS NULL)
WHEN MATCHED 
THEN UPDATE SET TARGET.IsBlocked = 1, TARGET.BlockDate = GETDATE()
WHEN NOT MATCHED BY TARGET 
THEN INSERT (LastDate, UsrID, DepID, OrderNo, OrderLine, SomeModel, ProdOrder, Lvl1, Lvl2, Lvl3, IsBlocked, BlockDate)
     VALUES (LastDate, 999, 999, OrderNo, OrderLine, SomeModel, ProdOrder, Lvl1, Lvl2, Lvl3, 1, GETDATE());

【讨论】:

  • 问到这里我想通了,解决方案就在我的stackoverflow.com/questions/4509722/…nose 前面-第二个答案,和你的一样,所以我将它标记为答案。跨度>
  • 使用 DISTINCT 更改 GROUP BY 会产生相同的结果。他们俩都更新了所有重复项。我不清楚你试图解释什么。
  • 您的 GROUP BY 和 my DISTINCT 在您的特定场景中可能会产生相同的结果,但不一定如此,因为您没有按 LastDate 进行分组。我无法看到两者都产生相同的结果,因为您没有提供数据。主要改变的是匹配条件。
  • 由于这个错误,我不得不使用 MAX & GROUP by:MERGE 语句多次尝试更新或删除同一行。当目标行匹配多个源行时会发生这种情况。 MERGE 语句不能多次 UPDATE/DELETE 目标表的同一行。优化 ON 子句以确保目标行最多匹配一个源行,或使用 GROUP BY 子句对源行进行分组。
  • 是的,可以理解,但是如果源记录仅在 LastDate 列中不同(并且有一个重复),那么您的 GROUP BY 将只生成一行(带有LastDate 值的最大值)。 DISTINCT 将产生 10 个唯一行。
【解决方案2】:

SQL 语言的某些特性使用了独特性的概念(特别是 DISTINCTGROUP BY),值得注意的是 NULL IS NOT DISTINCT FROM NULL 是正确的。这也出现在UNION (ALL)EXCEPTINTERSECT等中。

不幸的是,SQL Server 本身还没有实现标准 SQL 中的 IS (NOT) DISTINCT FROM 运算符;所以你只能使用相等比较,在 SQL 中著名的 NULL = NULL 是未知的(不是真或假)。因此,您必须在 ON 子句中显式执行 NULL 检查(直到 SQL Server 的未来版本支持 DISTINCT FROM 运算符)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-10-10
    • 1970-01-01
    • 2021-06-02
    • 1970-01-01
    • 2018-02-10
    • 1970-01-01
    • 1970-01-01
    • 2017-06-12
    相关资源
    最近更新 更多