web-dev-qa-db-fra.com

Fusionner - Mettre à jour uniquement si les valeurs ont changé

J'exécute une fusion dans SQL Server. Dans ma mise à jour, je souhaite mettre à jour la ligne uniquement si les valeurs ont changé. Il existe une ligne de version qui s'incrémente à chaque mise à jour. Voici un exemple:

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

Maintenant, si je voulais seulement mettre à jour la ligne, et donc incrémenter la version, UNIQUEMENT si le nom a changé.

22
TrialAndError

WHEN MATCHED Peut avoir AND. De plus, pas besoin de mettre à jour 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
 ...

Si Last_Name ou First_Name sont nullables, vous devez prendre soin des valeurs NULL lors de la comparaison de trg.Last_Name <> src.Last_Name, par exemple ISNULL(trg.Last_Name,'') <> ISNULL(src.Last_Name,'')

43
a1ex07

Plutôt que d'éviter complètement une mise à jour, vous pouvez modifier votre [VERSION] + 1 code pour ajouter zéro lorsque les noms correspondent:

[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

La réponse fournie par a1ex07 est la bonne réponse, mais je voulais juste développer la difficulté de comparer un grand nombre de colonnes, de surveiller les valeurs nulles, etc.

J'ai trouvé que je pouvais générer une somme de contrôle dans certains CTE avec des hashbytes, cibler ces CTE dans la fusion, puis utiliser la condition "update and ...." spécifiée ci-dessus pour comparer les hachages:

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

Quelques notes: j'aurais pu simplifier énormément avec checksum ou binary_checksum, mais j'ai toujours des collisions avec celles-ci.

Quant au "pourquoi", cela fait partie d'un déploiement automatisé pour maintenir une table de recherche à jour. Le problème avec la fusion est cependant qu'il existe une vue indexée qui est complexe et très utilisée, donc les mises à jour des tables associées sont assez coûteuses.

1
b_levitt