Merge - Only update if values have changed

The answer provided by a1ex07 is the right answer, but i just wanted to expand on the difficulty in comparing a large number of columns, watching for nulls, etc.

I found that I could generate a checksum in some CTE's with hashbytes, target those CTEs in the merge, and then use the "update and...." condition specified above to compare the hashes:

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

Couple of notes: I could have simplified greatly with checksum or binary_checksum, but I always get collisions with those.

As to the 'why', this is part of an automated deployment to keep a lookup table up to date. The problem with the merge though is there is an indexed view that is complex and heavily used, so updates to the related tables are quite expensive.


Rather than avoiding an update altogether, you could change your [VERSION] + 1 code to add zero when names match:

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

WHEN MATCHED can have AND . Also, no need to update 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
 ...

If Last_Name or First_Name are nullable, you need to take care of NULL values while comparing trg.Last_Name <> src.Last_Name , for instance ISNULL(trg.Last_Name,'') <> ISNULL(src.Last_Name,'')