web-dev-qa-db-ja.com

マージ-値が変更された場合のみ更新

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

ここで、行を更新するだけでバージョンをインクリメントしたい場合は、名前が変更された場合のみ。

22
TrialAndError

_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がnull可能である場合、trg.Last_Name <> src.Last_Nameを比較するときにNULLの値に注意する必要があります。たとえば、ISNULL(trg.Last_Name,'') <> ISNULL(src.Last_Name,'')

43
a1ex07

更新を完全に回避するのではなく、[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)
1
dasblinkenlight

A1ex07によって提供される答えは正しい答えですが、多数の列の比較、nullの監視などの難しさをさらに詳しく説明したいと思います。

ハッシュバイトを使用して一部の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を使用して大幅に簡略化できたかもしれませんが、常にそれらと衝突します。

「理由」については、これはルックアップテーブルを最新に保つための自動展開の一部です。ただし、マージの問題は、複雑で頻繁に使用されるインデックス付きビューが存在するため、関連するテーブルの更新に非常にコストがかかることです。

1
b_levitt