Lors de la compilation et de l'exécution de la requête, SQL Server ne prend pas le temps de déterminer si une instruction UPDATE modifiera réellement des valeurs ou non. Il effectue simplement les écritures comme prévu, même si elles ne sont pas nécessaires.
Dans le scénario comme
update table1 set col1 = 'hello'
vous pourriez penser que SQL ne fera rien, mais il le fera - il effectuera toutes les écritures nécessaires comme si vous aviez réellement changé la valeur. Cela se produit à la fois pour la table physique (ou l'index clusterisé) ainsi que pour tous les index non clusterisés définis sur cette colonne. Cela entraîne des écritures dans les tables/index physiques, le recalcul des index et des écritures dans le journal des transactions. Lorsque vous travaillez avec de grands ensembles de données, il y a d'énormes avantages en termes de performances à ne mettre à jour que les lignes qui recevront une modification.
Si nous voulons éviter la surcharge de ces écritures lorsqu'elles ne sont pas nécessaires, nous devons trouver un moyen de vérifier la nécessité d'être mis à jour. Une façon de vérifier la nécessité d'une mise à jour serait d'ajouter quelque chose comme "where col <> 'hello'.
update table1 set col1 = 'hello' where col1 <> 'hello'
Mais cela ne fonctionnerait pas bien dans certains cas, par exemple si vous mettiez à jour plusieurs colonnes dans une table avec de nombreuses lignes et que seul un petit sous-ensemble de ces lignes verrait réellement ses valeurs modifiées. Cela est dû à la nécessité de filtrer ensuite sur toutes ces colonnes, et les prédicats de non-égalité ne sont généralement pas en mesure d'utiliser les recherches d'index, et la surcharge des écritures de table et d'index et des entrées du journal des transactions comme mentionné ci-dessus.
Mais il existe une bien meilleure alternative en utilisant une combinaison d'une clause EXISTS avec une clause EXCEPT. L'idée est de comparer les valeurs de la ligne de destination aux valeurs de la ligne source correspondante pour déterminer si une mise à jour est réellement nécessaire. Regardez la requête modifiée ci-dessous et examinez le filtre de requête supplémentaire commençant par EXISTS. Notez qu'à l'intérieur de la clause EXISTS, les instructions SELECT n'ont pas de clause FROM. Cette partie est particulièrement importante car cela ne fait qu'ajouter une analyse constante supplémentaire et une opération de filtrage dans le plan de requête (le coût des deux est insignifiant). Vous vous retrouvez donc avec une méthode très légère pour déterminer si une MISE À JOUR est même nécessaire en premier lieu, évitant ainsi une surcharge d'écriture inutile.
update table1 set col1 = 'hello'
/* AVOID NET ZERO CHANGES */
where exists
(
/* DESTINATION */
select table1.col1
except
/* SOURCE */
select col1 = 'hello'
)
Cela semble trop compliqué par rapport à la vérification des mises à jour dans une simple clause WHERE pour le scénario simple de la question d'origine lorsque vous mettez à jour une valeur pour toutes les lignes d'une table avec une valeur littérale. Cependant, cette technique fonctionne très bien si vous mettez à jour plusieurs colonnes dans une table, que la source de votre mise à jour est une autre requête et que vous souhaitez minimiser les écritures et les entrées des journaux de transactions. Il est également plus performant que de tester chaque champ avec <>.
Un exemple plus complet pourrait être
update table1
set col1 = 'hello',
col2 = 'hello',
col3 = 'hello'
/* Only update rows from CustomerId 100, 101, 102 & 103 */
where table1.CustomerId IN (100, 101, 102, 103)
/* AVOID NET ZERO CHANGES */
and exists
(
/* DESTINATION */
select table1.col1
table1.col2
table1.col3
except
/* SOURCE */
select z.col1,
z.col2,
z.col3
from #anytemptableorsubquery z
where z.CustomerId = table1.CustomerId
)