【问题标题】:Merge - Only update if values have changed合并 - 仅在值更改时更新
【发布时间】:2013-04-13 00:05:00
【问题描述】:

我正在 SQL Server 中运行合并。在我的更新中,我只想在值发生变化时更新该行。每次更新都会增加一个版本行。下面是一个例子:

MERGE Employee as tgt USING 
(SELECT Employee_History.Emp_ID
, Employee_History.First_Name
, Employee_History.Last_Name
FROM Employee_History)
as src (Emp_ID,First_Name,Last_Name)
ON tgt.Emp_ID = src.Emp_ID
WHEN MATCHED THEN 
    UPDATE SET
    Emp_ID = src.Emp_ID,
    ,[VERSION] = tgt.VERSION + 1 
    ,First_Name = src.First_Name
    ,Last_Name = src.Last_Name
WHEN NOT MATCHED BY target THEN 
    INSERT (Emp_ID,0,First_Name,Last_Name)
VALUES 
    (src.Emp_ID,[VERSION],src.First_Name,src.Last_Name);

现在,如果我只想更新行,从而增加版本,只有在名称发生变化的情况下。

【问题讨论】:

    标签: sql sql-server merge


    【解决方案1】:

    您可以更改 [VERSION] + 1 代码以在名称匹配时添加零,而不是完全避免更新:

    [VERSION] = tgt.VERSION + (CASE
        WHEN tgt.First_Name <> src.First_Name OR tgt.Last_Name <> src.Last_Name
        THEN 1
        ELSE 0 END)
    

    【讨论】:

    • 这也可以,但@a1ex07 的答案对我的需求有点多,因为如果没有任何变化,则无需更新值。
    • 我正在使用 SQL 版本化表(临时),如果更新,它会放入一个副本行,所以我会使用 @a1ex07 的答案而不是这个
    【解决方案2】:

    WHEN MATCHED 可以有 AND 。此外,无需更新 EMP_ID

    ...
     WHEN MATCHED AND (trg.First_Name <> src.First_Name 
       OR trg.Last_Name <> src.Last_Name) THEN UPDATE
       SET 
       [VERSION] = tgt.VERSION + 1 
        ,First_Name = src.First_Name
        ,Last_Name = src.Last_Name
     ...
    

    如果 Last_Name 或 First_Name 可以为空,则在比较 trg.Last_Name src.Last_Name 时需要注意 NULL 值,例如 ISNULL(trg.Last_Name,'') &lt;&gt; ISNULL(src.Last_Name,'')

    【讨论】:

    • 这应该是“或”吗?如果只是名字变了,我还想更新
    • 我认为这会很完美。我没有意识到我可以在 MATCHED 部分使用“AND”。谢谢。
    • 我试过这样做,但是当我 OUTPUT $action、deleted.*、inserted.* 时,我看到所有内容都已更新,对于某些人来说可能可以忽略不计,但是当有触发器或列内的大数据,这将是一个问题。我目前卡住了,我不想在合并中写一个大型案例
    • @LeeGary,你是说WHEN MATCHED AND (...) 仍将填充inserted.*,即使由于不匹配条件而没有发生更新?
    • @user7733611 :假设不涉及 LOB 字段,在这种情况下更新所有行不应该有很大的性能损失,但无论如何如果字段值发生变化,引擎必须更新索引(如果有的话)可能足够聪明,可以检测到相同的值,但它仍然必须至少找到它)。 WHEN MATCHED 的填充条件似乎不是问题,冗长 - 是的,但它只是在更大程度上重复了 SET 部分。
    【解决方案3】:

    a1ex07提供的答案是正确的答案,但我只是想扩展一下比较大量列、观察空值等的难度。

    我发现我可以在一些带有哈希字节的 CTE 中生成校验和,以合并中的那些 CTE 为目标,然后使用上面指定的“更新和....”条件来比较哈希:

    with SourcePermissions as (
        SELECT 1 as Code, 1013 as ObjectTypeCode, 'Create Market' as ActionName, null as ModuleCode, 1 as AssignableTargetFlags
        union all SELECT 2, 1013, 'View Market', null, 1
        union all SELECT 3, 1013, 'Edit Market', null, 1
        --...shortened....
    )
    ,SourcePermissions2 as (
        select sp.*, HASHBYTES('sha2_256', xmlcol)  as [Checksum] 
        from SourcePermissions sp
        cross apply (select sp.* for xml raw) x(xmlcol)
    )
    ,TargetPermissions as (
        select p.*, HASHBYTES('sha2_256', xmlcol)  as [Checksum] 
        from Permission p
        cross apply (select p.* for xml raw) x(xmlcol)
    ) --select * from SourcePermissions2 sp join TargetPermissions tp on sp.code=tp.code where sp.Checksum = tp.Checksum
    
        MERGE TargetPermissions AS target  
        USING (select * from SourcePermissions2) AS source ([Code] , [ObjectTypeCode] , [ActionName] , [ModuleCode] , [AssignableTargetFlags], [Checksum])  
            ON (target.Code = source.Code)  
        WHEN MATCHED and source.[Checksum] != target.[Checksum] then
            UPDATE SET [ObjectTypeCode] = source.[ObjectTypeCode], [ActionName]=source.[ActionName], [ModuleCode]=source.[ModuleCode], [AssignableTargetFlags] = source.[AssignableTargetFlags]
        WHEN NOT MATCHED THEN  
            INSERT ([Code] , [ObjectTypeCode] , [ActionName] , [ModuleCode] , [AssignableTargetFlags])  
            VALUES (source.[Code] , source.[ObjectTypeCode] , source.[ActionName] , source.[ModuleCode] , source.[AssignableTargetFlags])
        OUTPUT deleted.*, $action, inserted.[Code] 
            --only minor issue is that you can no longer do a inserted.* here since it gives error 404 (sql, not web), complaining about returning checksum which is included in the target cte but not the underlying table
            ,inserted.[ObjectTypeCode] , inserted.[ActionName] , inserted.[ModuleCode] , inserted.[AssignableTargetFlags]
        ;
    

    几点说明:我本可以使用校验和或 binary_checksum 大大简化,但我总是与它们发生冲突。

    至于“为什么”,这是自动部署的一部分,以使查找表保持最新状态。合并的问题是索引视图复杂且使用频繁,因此对相关表的更新非常昂贵。

    【讨论】:

      【解决方案4】:

      @a1ex07 感谢您的回答.. 稍微更正.. 我没有关注 SQL 版本,所以这可能是 SQL 规范的更改

      当匹配和条件然后更新

      以上语法无效

      以下有效

      当匹配然后更新设置... 条件在哪里当不匹配然后插入...

      所以会改成

      WHEN MATCHED THEN UPDATE
         SET 
         [VERSION] = tgt.VERSION + 1 
         ,First_Name = src.First_Name
         ,Last_Name = src.Last_Name
      

      在哪里 trg.First_Name src.First_Name 或 trg.Last_Name src.Last_Name

      https://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_9016.htm#SQLRF01606

      【讨论】:

      • 请阅读并改进您的活动stackoverflow.com/tour谢谢
      • 请注意标签。问题是针对 MS SQL Server 的,您指的是 Oracle 语法。
      猜你喜欢
      • 2021-04-21
      • 2015-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-24
      • 1970-01-01
      相关资源
      最近更新 更多