Pourquoi les requêtes sont-elles analysées de manière à interdire l'utilisation d'alias de colonne dans la plupart des clauses?

16

En essayant d'écrire une requête, j'ai découvert (à la dure) que SQL Server analyse les WHERE dans une requête bien avant d'analyser les SELECT lors de l'exécution d'une requête.

Les documents MSDN indiquent que l'ordre d'analyse logique général est tel que SELECT est analysé presque en dernier (ce qui entraîne des erreurs «aucun objet [alias]» lors de la tentative d'utilisation d'un alias de colonne dans d'autres clauses). Il a même été suggéré d'autoriser l'utilisation d'alias n'importe où, ce qui a été abattu par l'équipe Microsoft, citant des problèmes de conformité aux normes ANSI (ce qui suggère que ce comportement fait partie de la norme ANSI).

En tant que programmeur (pas un DBA), j'ai trouvé ce comportement quelque peu déroutant, car il me semble qu'il va à l'encontre du but d'avoir des alias de colonne (ou, à tout le moins, les alias de colonne pourraient être rendus beaucoup plus puissants s'ils étaient analysé plus tôt dans l'exécution de la requête), car le seul endroit où vous pouvez réellement utiliser les alias est ORDER BY. En tant que programmeur, il semble qu'il manque une énorme opportunité pour rendre les requêtes plus puissantes, pratiques et SECHES.

Il semble que ce soit un problème tellement flagrant qu'il va de soi, alors, qu'il existe d'autres raisons pour décider que les alias de colonne ne devraient pas être autorisés dans autre chose que SELECT et ORDER BY, mais quelles sont ces raisons?

Shauna
la source

Réponses:

19

Sommaire

Il n'y a aucune raison logique que cela ne puisse pas être fait, mais l'avantage est faible et il y a des pièges qui ne sont pas immédiatement apparents.

Résultats de recherche

J'ai fait quelques recherches et trouvé de bonnes informations. Ce qui suit est une citation directe d'une source principale fiable (qui souhaite rester anonyme) au 2012-08-09 17:49 GMT:

Lorsque SQL a été inventé pour la première fois, il n'avait aucun alias dans la clause SELECT. Il s'agissait d'une grave lacune qui a été corrigée lorsque le langage a été normalisé par l'ANSI vers 1986.

Le langage était censé être "non procédural" - en d'autres termes, pour décrire les données que vous souhaitez sans préciser comment les trouver. Donc, pour autant que je sache, il n'y a aucune raison pour qu'une implémentation SQL ne puisse pas analyser la requête entière avant de la traiter et permettre aux alias d'être définis n'importe où et utilisés partout. Par exemple, je ne vois aucune raison pour laquelle la requête suivante ne devrait pas être valide:

select name, salary + bonus as pay
from employee
where pay > 100000

Bien que je pense qu'il s'agit d'une requête raisonnable, certains systèmes basés sur SQL peuvent introduire des restrictions sur l'utilisation des alias pour une raison liée à l'implémentation. Je ne suis pas surpris d'apprendre que SQL Server fait cela.

Je suis intéressé par d'autres recherches sur la norme SQL-86 et pourquoi les SGBD modernes ne prennent pas en charge la réutilisation des alias, mais je n'ai pas encore eu le temps d'aller très loin avec. Pour commencer, je ne sais pas où obtenir la documentation ni comment savoir exactement qui a constitué le comité. Quelqu'un peut-il m'aider? J'aimerais également en savoir plus sur le produit Sybase d'origine dont SQL Server est issu.

À partir de ces recherches et de certaines réflexions, j'ai fini par soupçonner que l'utilisation d'alias dans d'autres clauses, bien que tout à fait possible, n'a tout simplement jamais été une priorité aussi élevée pour les fabricants de SGBD par rapport aux autres fonctionnalités de langage. Comme ce n'est pas vraiment un obstacle, être facilement contourné par le rédacteur de requêtes, y consacrer des efforts par rapport aux autres avancées n'est pas optimal. De plus, il serait propriétaire car il ne fait évidemment pas partie du standard SQL (bien que j'attende pour en savoir plus à ce sujet) et serait donc une amélioration mineure, brisant la compatibilité SQL entre les SGBD. En comparaison, CROSS APPLY(qui n'est vraiment rien de plus qu'un tableau dérivé permettant des références externes) est un énorme changement, qui, bien que propriétaire, offre une puissance expressive incroyable qui n'est pas facilement exécutée d'autres manières.

Problèmes avec l'utilisation d'alias partout

Si vous autorisez la mise d'éléments SELECT dans la clause WHERE, vous pouvez non seulement exploser la complexité de la requête (et donc la complexité de trouver un bon plan d'exécution), mais il est possible de trouver des choses complètement illogiques. Essayer:

SELECT X + 5 Y FROM MyTable WHERE Y = X

Que faire si MyTable a déjà une colonne Y, à laquelle la clause WHERE fait-elle référence? La solution consiste à utiliser un CTE ou une table dérivée, qui dans la plupart des cas ne devrait pas coûter plus cher mais atteint le même résultat final. Les CTE et les tables dérivées appliquent au moins la résolution de l'ambiguïté en permettant à un alias d'être utilisé une seule fois.

En outre, ne pas utiliser d'alias dans la clause FROM est parfaitement logique. Vous ne pouvez pas faire ça:

SELECT
   T3.ID + (SELECT Min(Interval) FROM Intervals WHERE IntName = 'T') CalcID
FROM
   Table1 T
   INNER JOIN Table2 T2
      ON T2.ID = CalcID
   INNER JOIN Table3 T3
      ON T2.ID = T3.ID

C'est une référence circulaire (dans le sens où T2 fait secrètement référence à une valeur de T3, avant que ce tableau ait été présenté dans la liste JOIN), et sacrément difficile à voir. Celui-ci, ça va:

INSERT dbo.FinalTransaction
SELECT
   newid() FinalTransactionGUID,
   'GUID is: ' + Convert(varchar(50), FinalTransactionGUID) TextGUID,
   T.*
FROM
   dbo.MyTable T

À quel point voulez-vous parier que la fonction newid () va être placée deux fois dans le plan d'exécution, de manière complètement inattendue, faisant que les deux colonnes affichent des valeurs différentes? Qu'en est-il lorsque la requête ci-dessus est utilisée N niveaux en profondeur dans les CTE ou les tables dérivées Je vous garantis que le problème est pire que vous ne pouvez l'imaginer. Il existe déjà de graves problèmes d'incohérence concernant le moment où les choses sont évaluées une seule fois ou à quel moment dans un plan de requête, et Microsoft a déclaré qu'il ne résoudrait pascertains d'entre eux parce qu'ils expriment correctement l'algèbre de requête - si l'on obtient des résultats inattendus, divisez la requête en plusieurs parties. Autoriser les références chaînées, détecter les références circulaires à travers de très longues chaînes de ce type - ce sont des problèmes assez délicats. Introduisez le parallélisme et vous avez un cauchemar en devenir.

Remarque: L'utilisation de l'alias dans WHERE ou GROUP BY ne va pas faire de différence avec les fonctions telles que newid () ou rand ().

Une façon SQL Server de créer des expressions réutilisables

CROSS APPLY / OUTER APPLY est une façon dans SQL Server de créer des expressions qui peuvent être utilisées n'importe où ailleurs dans la requête (mais pas plus tôt dans la clause FROM):

SELECT
   X.CalcID
FROM
   Table1 T
   INNER JOIN Table3 T3
      ON T.ID = T3.ID
   CROSS APPLY (
      SELECT
         T3.ID + (SELECT Min(Interval) FROM Intervals WHERE IntName = 'T') CalcID
   ) X
   INNER JOIN Table2 T2
      ON T2.ID = X.CalcID

Cela fait deux choses:

  1. Fait en sorte que toutes les expressions dans CROSS APPLY obtiennent un "espace de noms" (un alias de table, ici, X) et soient uniques dans cet espace de noms.
  2. Il est évident partout non seulement que CalcID provient de X, mais rend également évident pourquoi vous ne pouvez pas utiliser quoi que ce soit de X lorsque vous rejoignez les tables T1 et T3, car X n'a ​​pas encore été introduit.

En fait, j'aime beaucoup CROSS APPLY. Il est devenu mon fidèle ami et je l'utilise tout le temps. Besoin d'un UNPIVOT partiel (qui nécessiterait un PIVOT / UNPIVOT ou UNPIVOT / PIVOT utilisant la syntaxe native)? Fait avec CROSS APPLY. Besoin d'une valeur calculée qui sera réutilisée plusieurs fois? Terminé. Besoin d'appliquer rigoureusement l'ordre d'exécution des appels sur un serveur lié? Fait avec une amélioration criante de la vitesse. Besoin d'un seul type de ligne divisée en 2 lignes ou avec des conditions supplémentaires? Terminé.

Donc, à tout le moins, dans DBMS SQL Server 2005 et versions ultérieures, vous n'avez plus de motif de plainte: CROSS APPLY est la façon dont vous SÉCHEZ comme vous le souhaitez.

ErikE
la source
14

Je ne peux pas vous dire les raisons exactes, mais je vais vous dire qu'il existe des solutions de contournement aux expressions répétitives, par exemple en utilisant des CTE, des sous-requêtes, des tables dérivées, etc. pour éviter la répétition.

Si vous affichez une requête avec une expression répétée, nous pouvons probablement vous montrer comment la réécrire afin que l'expression ne soit répertoriée qu'une seule fois. Cependant, cela réduit simplement la complexité de l'écriture / lecture de la requête, il est peu probable que cela change beaucoup en termes d'efficacité. SQL Server est généralement assez bon pour reconnaître que les expressions sont répétées et il n'effectuera pas ce travail deux fois. Il y a des exceptions qui vont dans l'autre sens, mais vous ne devez vous soucier de l'efficacité que lorsque vous observez réellement cela. Je soupçonne que la plupart des expressions répétées que vous écrivez sont vraiment réduites en une seule opération dans le plan.

Cela dit, je vais également répéter une partie de ma réponse à cette question:

/dba/19762/why-is-the-select-clause-listed-first


Voici l'explication de Joe Celko sur la façon dont une requête est traitée conformément à la norme (j'ai volé cela dans mon propre article aspfaq.com , qui a volé la citation probablement dans un message de groupe de discussion par Celko):

Voici comment fonctionne SELECT en SQL ... du moins en théorie. Les vrais produits optimiseront les choses quand ils le pourront.

Commencez dans la clause FROM et créez une table de travail à partir de toutes les jointures, unions, intersections et quels que soient les autres constructeurs de table. L'option AS vous permet de donner un nom à cette table de travail que vous devez ensuite utiliser pour le reste de la requête contenant.

Accédez à la clause WHERE et supprimez les lignes qui ne répondent pas aux critères; c'est-à-dire qui ne testent pas VRAI (rejetez INCONNU et FAUX). La clause WHERE est appliquée au travail dans la clause FROM.

Accédez à la clause facultative GROUP BY, créez des groupes et réduisez chaque groupe en une seule ligne, en remplaçant la table de travail d'origine par la nouvelle table groupée. Les lignes d'un tableau groupé doivent être des caractéristiques de groupe: (1) une colonne de regroupement (2) une statistique sur le groupe (c.-à-d. Fonctions agrégées) (3) une fonction ou (4) une expression composée de ces trois éléments.

Accédez à la clause HAVING facultative et appliquez-la à la table de travail groupée; s'il n'y avait pas de clause GROUP BY, traitez la table entière comme un seul groupe.

Accédez à la clause SELECT et construisez les expressions dans la liste. Cela signifie que les sous-requêtes scalaires, les appels de fonction et les expressions dans SELECT sont effectués une fois toutes les autres clauses terminées. L'opérateur AS peut également donner un nom aux expressions de la liste SELECT. Ces nouveaux noms prennent naissance d'un seul coup, mais après l'exécution de la clause WHERE; vous ne pouvez pas les utiliser dans la liste SELECT ou la clase WHERE pour cette raison.

Les expressions de requête imbriquées suivent les règles de portée habituelles que vous attendez d'un langage structuré en blocs comme C, Pascal, Algol, etc. À savoir, les requêtes les plus internes peuvent référencer des colonnes et des tables dans les requêtes dans lesquelles elles sont contenues.

Cela signifie qu'un SELECT ne peut pas avoir plus de colonnes qu'un GROUP BY; mais il peut certainement avoir moins de colonnes.

Désormais, Celko était l'un des principaux contributeurs aux versions antérieures des normes. Je ne sais pas si vous allez obtenir une réponse définitive à la WHY?question, sauf pour des spéculations. Je suppose que lister l'opération réelle en premier permet à l'analyseur de savoir très facilement quel sera le type d'opération. Imaginez une jointure de 20 tables qui pourrait finir par être un SELECTou UPDATEou DELETE, et rappelez-vous que le code de ces moteurs a été écrit à l'origine à l'époque où l'analyse de chaîne était assez coûteuse.

Notez que si le standard SQL a dicté FROMde venir en premier, les fournisseurs peuvent avoir indépendamment décidé d'analyser la grammaire dans un ordre différent, il peut donc ne pas être logique de s'attendre à ce que l'ordre des clauses tel qu'écrit obéisse complètement à l'ordre de traitement de 100% de le temps.

La même chose est vraie pour des choses comme CASE. Nous avons vu des scénarios ici même sur ce site , par exemple, où le mythe précédemment cru qui CASEtraite toujours dans l'ordre et les courts-circuits, est faux. Et cela s'étend également à d'autres croyances courantes, telles que l'évaluation des jointures par SQL Server dans l'ordre dans lequel elles ont été écrites, les WHEREclauses de court-circuitage de gauche à droite , ou le traitement des CTE une fois ou dans un certain ordre, même s'ils sont référencés plusieurs fois. Les produits sont libres d'optimiser la façon dont ils jugent bon, même si cela ne reflète pas exactement la façon dont vous avez déclaré que la requête devrait fonctionner de manière déclarative.

Aaron Bertrand
la source
2
Notez également que la possibilité d'utiliser ou de ne pas utiliser d'alias dans différentes parties de la requête est appliquée par l'analyseur, et non par l'optimiseur ou le moteur d'exécution. La façon dont le moteur exécute réellement la requête ne reflète pas nécessairement les restrictions qui affectent la syntaxe.
Aaron Bertrand
2

Dans Entity SQL , vous pouvez utiliser des alias à partir d'expressions situées à d'autres endroits de la requête dans certaines situations:

select k1, count(t.a), sum(t.a)
from T as t
group by t.b + t.c as k1

Notez qu'ici, vous DEVEZ définir l'expression dans la GROUP BYclause afin de l'utiliser dans la SELECTclause.

Il est évidemment possible d'autoriser une partie de ce type d'alias-comme-expression-réutilisable dans les requêtes SQL.

ErikE
la source