SQL Server est-il autorisé à évaluer en A <> B
tant que A < B OR A > B
, même si l'une des expressions n'est pas déterministe?
C'est un point quelque peu controversé, et la réponse est un "oui" nuancé.
La meilleure discussion dont j'ai connaissance a été donnée en réponse au rapport de bogue Connect d'Itzik Ben-Gan, Bug avec NEWID et Expressions de table , qui a été fermé car il ne sera pas corrigé. Connect a depuis été retiré, donc le lien est là vers une archive Web. Malheureusement, beaucoup de matériel utile a été perdu (ou rendu plus difficile à trouver) par la disparition de Connect. Quoi qu'il en soit, les citations les plus utiles de Jim Hogg de Microsoft sont:
Cela touche au cœur même du problème - l'optimisation est-elle autorisée à modifier la sémantique d'un programme? C'est-à-dire: si un programme donne certaines réponses, mais s'exécute lentement, est-il légitime qu'un Query Optimizer accélère l'exécution de ce programme, tout en modifiant également les résultats fournis?
Avant de crier "NON!" (ma propre inclination personnelle aussi :-), considérez: la bonne nouvelle est que, dans 99% des cas, les réponses SONT les mêmes. L'optimisation des requêtes est donc clairement une victoire. La mauvaise nouvelle est que, si la requête contient du code à effet secondaire, différents plans PEUVENT en effet produire des résultats différents. Et NEWID () est une de ces «fonctions» à effets secondaires (non déterministes) qui expose la différence. [En fait, si vous expérimentez, vous pouvez en concevoir d'autres - par exemple, l'évaluation en court-circuit des clauses AND: faites en sorte que la deuxième clause lance une division arithmétique divisée par zéro - différentes optimisations peuvent exécuter cette deuxième clause AVANT la première clause] Explication de Craig, ailleurs dans ce fil, que SqlServer ne garantit pas lorsque les opérateurs scalaires sont exécutés.
Nous avons donc le choix: si nous voulons garantir un certain comportement en présence de code non déterministe (à effets secondaires) - de sorte que les résultats de JOIN, par exemple, suivent la sémantique d'une exécution en boucle imbriquée - alors nous peut utiliser des OPTIONS appropriées pour forcer ce comportement - comme le souligne UC. Mais le code résultant fonctionnera lentement - c'est le coût de l'entrave à l'optimiseur de requête.
Cela dit, nous déplaçons l'Optimiseur de requête dans le sens d'un comportement "comme prévu" pour NEWID () - en échangeant les performances contre des "résultats attendus".
Un exemple du changement de comportement à cet égard au fil du temps est NULLIF fonctionne incorrectement avec des fonctions non déterministes telles que RAND () . Il existe également d'autres cas similaires utilisant par exemple COALESCE
une sous-requête qui peuvent produire des résultats inattendus et qui sont également traités progressivement.
Jim poursuit:
Fermer la boucle . . . J'ai discuté de cette question avec l'équipe de développement. Et finalement, nous avons décidé de ne pas changer le comportement actuel, pour les raisons suivantes:
1) L'optimiseur ne garantit pas le timing ou le nombre d'exécutions des fonctions scalaires. Il s'agit d'un principe établi de longue date. C'est la «marge de manœuvre» fondamentale qui laisse à l'optimiseur suffisamment de liberté pour obtenir des améliorations significatives dans l'exécution du plan de requête.
2) Ce "comportement une fois par ligne" n'est pas un nouveau problème, bien qu'il ne soit pas largement discuté. Nous avons commencé à modifier son comportement dans la version du Yukon. Mais il est assez difficile de cerner précisément, dans tous les cas, exactement ce que cela signifie! Par exemple, cela s'applique-t-il aux lignes intermédiaires calculées «en route» vers le résultat final? - auquel cas cela dépend clairement du plan choisi. Ou s'applique-t-il uniquement aux lignes qui apparaîtront éventuellement dans le résultat final? - il y a une récursion désagréable qui se passe ici, comme je suis sûr que vous serez d'accord!
3) Comme je l'ai mentionné précédemment, nous optons par défaut pour "optimiser les performances" - ce qui est bon pour 99% des cas. Les 1% des cas où cela pourrait changer les résultats sont assez faciles à repérer - des «fonctions» à effets secondaires comme NEWID - et faciles à «corriger» (la perf de trading, en conséquence). Cette valeur par défaut pour «optimiser à nouveau les performances» est établie de longue date et acceptée. (Oui, ce n'est pas la position choisie par les compilateurs pour les langages de programmation conventionnels, mais qu'il en soit ainsi).
Nos recommandations sont donc les suivantes:
a) Évitez de vous fier à un calendrier non garanti et à une sémantique de nombre d'exécutions. b) Évitez d'utiliser NEWID () au plus profond des expressions de table. c) Utilisez OPTION pour forcer un comportement particulier (trading perf)
J'espère que cette explication aide à clarifier nos raisons pour fermer ce bogue car "ne résoudra pas".
Fait intéressant, AND NOT (s_guid = NEWID())
donne le même plan d'exécution
Ceci est une conséquence de la normalisation, qui se produit très tôt lors de la compilation des requêtes. Les deux expressions se compilent exactement sous la même forme normalisée, de sorte que le même plan d'exécution est produit.