Ceci est la 9e partie d'une série sur les expressions de table nommées. Dans la partie 1, j'ai fourni le contexte des expressions de table nommées, qui incluent les tables dérivées, les expressions de table communes (CTE), les vues et les fonctions de table en ligne (iTVF). Dans les parties 2, 3 et 4, je me suis concentré sur les tables dérivées. Dans les parties 5, 6, 7 et 8, je me suis concentré sur les CTE. Comme je l'ai expliqué, les tables dérivées et les CTE sont des expressions de table nommées à portée d'instruction. Une fois que la déclaration qui les définit est terminée, ils sont partis.
Nous sommes maintenant prêts à passer à la couverture des expressions de table nommées réutilisables. C'est-à-dire ceux qui sont créés en tant qu'objet dans la base de données et y restent en permanence à moins qu'ils ne soient supprimés. En tant que tels, ils sont accessibles et réutilisables par tous ceux qui ont les bonnes autorisations. Les vues et les iTVF entrent dans cette catégorie. La différence entre les deux est principalement que le premier ne prend pas en charge les paramètres d'entrée et que le second le fait.
Dans cet article, je commence la couverture des vues. Comme je l'ai fait auparavant, je vais d'abord me concentrer sur les aspects logiques ou conceptuels, puis procéder ensuite aux aspects d'optimisation. Avec le premier article sur les vues, je veux commencer léger, en me concentrant sur ce qu'est une vue, en utilisant une terminologie correcte, et en comparant les considérations de conception des vues avec celles des tables dérivées et des CTE discutés précédemment.
Dans mes exemples, j'utiliserai un exemple de base de données appelé TSQLV5. Vous pouvez trouver le script qui le crée et le remplit ici, et son diagramme ER ici.
Qu'est-ce qu'une vue ?
Comme d'habitude lorsque nous discutons de la théorie relationnelle, nous, les praticiens de SQL, entendons souvent dire que la terminologie que nous utilisons est erronée. Donc, dans cet esprit, dès le départ, je commencerai par dire que lorsque vous utilisez le terme tables et vues , c'est faux. J'ai appris cela de Chris Date.
Rappelez-vous qu'une table est la contrepartie SQL d'une relation (en simplifiant un peu la discussion autour des valeurs et des variables). Une table peut être une table de base définie comme un objet dans la base de données ou une table renvoyée par une expression, plus précisément une expression de table. C'est similaire au fait qu'une relation pourrait être celle qui est renvoyée à partir d'une expression relationnelle. Une expression de table peut être une requête.
Maintenant, qu'est-ce qu'une vue ? C'est une expression de table nommée, tout comme un CTE est une expression de table nommée. C'est juste que, comme je l'ai dit, une vue est une expression de table nommée réutilisable qui est créée en tant qu'objet dans la base de données et qui est accessible à ceux qui ont les bonnes autorisations. C'est tout pour dire qu'une vue est une table. Ce n'est pas une table de base, mais une table quand même. Donc, tout comme dire "un rectangle et un carré" ou "un whisky et un Lagavulin" semblerait étrange (à moins que vous n'ayez trop de Lagavulin !), utiliser "tableaux et vues" est tout aussi inapproprié.
Syntaxe
Voici la syntaxe T-SQL pour une instruction CREATE VIEW :
CREATE [ OR ALTER ] VIEW [[ WITH
AS
[ WITH CHECK OPTION ]
[; ]
L'instruction CREATE VIEW doit être la première et la seule instruction du lot.
Notez que la partie CREATE OR ALTER a été introduite dans SQL Server 2016 SP1, donc si vous utilisez une version antérieure, vous devrez travailler avec des instructions CREATE VIEW et ALTER VIEW distinctes selon que l'objet existe déjà ou non. Comme vous le savez probablement bien, la modification d'un objet existant conserve les autorisations attribuées. C'est l'une des raisons pour lesquelles il est généralement judicieux de modifier un objet existant plutôt que de le supprimer et de le recréer. Ce qui surprend certaines personnes, c'est que la modification d'une vue ne conserve pas les attributs de la vue existante ; ceux-ci doivent être respécifiés si vous souhaitez les conserver.
Voici un exemple de définition de vue simple représentant les clients américains :
USE TSQLV5; GO CREATE OR ALTER VIEW Sales.USACustomers AS SELECT custid, companyname FROM Sales.Customers WHERE country = N'USA'; GO
Et voici une déclaration qui interroge la vue :
SELECT custid, companyname FROM Sales.USACustomers;
Entre l'instruction qui crée la vue et l'instruction qui l'interroge, vous trouverez les trois mêmes éléments qui sont impliqués dans une instruction sur une table dérivée ou un CTE :
- L'expression de la table interne (la requête interne de la vue)
- Le nom de table attribué (le nom de la vue)
- L'instruction avec la requête externe par rapport à la vue
Ceux d'entre vous qui ont un œil attentif auront remarqué qu'il y a en fait deux expressions de table impliquées ici. Il y a la requête interne (la requête interne de la vue) et la requête externe (la requête dans l'instruction contre la vue). Dans l'instruction avec la requête par rapport à la vue, la requête elle-même est une expression de table, et une fois que vous ajoutez le terminateur, elle devient une instruction. Cela peut sembler pointilleux, mais si vous obtenez cela et appelez les choses par leurs bons noms, cela reflète vos connaissances. Et n'est-ce pas formidable de savoir que l'on sait ?
En outre, toutes les exigences de l'expression de table dans les tables dérivées et les CTE dont nous avons parlé précédemment dans la série s'appliquent à l'expression de table sur laquelle la vue est basée. Pour rappel, les pré-requis sont :
- Toutes les colonnes de l'expression de table doivent avoir des noms
- Tous les noms de colonne de l'expression de table doivent être uniques
- Les lignes de l'expression de table n'ont pas d'ordre
Si vous avez besoin de rafraîchir votre compréhension de ce qui se cache derrière ces exigences, consultez la section "Une expression de table est une table" dans la partie 2 de la série. Assurez-vous de bien comprendre la partie "pas de commande". Pour rappel, une expression de table est une table et, en tant que telle, n'a pas d'ordre. C'est pourquoi vous ne pouvez pas créer une vue basée sur une requête avec une clause ORDER BY, sauf si cette clause est là pour prendre en charge un filtre TOP ou OFFSET-FETCH. Et même avec cette exception permettant à la requête interne d'avoir une clause ORDER BY, vous voulez vous rappeler que si la requête externe contre la vue n'a pas sa propre clause ORDER BY, vous n'obtenez pas de garantie que la requête retournera les lignes dans un ordre particulier, quel que soit le comportement observé. C'est super important à comprendre !
Imbrication et références multiples
Lors de l'examen des considérations de conception des tables dérivées et des CTE, j'ai comparé les deux en termes d'imbrication et de références multiples. Voyons maintenant comment se comportent les vues dans ces départements. Je vais commencer par la nidification. À cette fin, nous comparerons le code qui renvoie les années au cours desquelles plus de 70 clients ont passé des commandes à l'aide de tables dérivées, de CTE et de vues. Vous avez déjà vu le code avec les tables dérivées et les CTE plus tôt dans la série. Voici le code qui gère la tâche à l'aide de tables dérivées :
SELECT orderyear, numcusts FROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ) AS D1 GROUP BY orderyear ) AS D2 WHERE numcusts > 70;
J'ai souligné que le principal inconvénient que je vois ici avec les tables dérivées est le fait que vous imbriquez des définitions de tables dérivées, ce qui peut compliquer la compréhension, la maintenance et le dépannage de ce code.
Voici le code qui gère la même tâche en utilisant les CTE :
WITH C1 AS ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ), C2 AS ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM C1 GROUP BY orderyear ) SELECT orderyear, numcusts FROM C2 WHERE numcusts > 70;
J'ai fait remarquer que pour moi, cela ressemble à un code beaucoup plus clair en raison du manque d'imbrication. Vous pouvez voir chaque étape de la solution du début à la fin séparément dans sa propre unité, la logique de la solution coulant clairement de haut en bas. Par conséquent, je considère l'option CTE comme une amélioration par rapport aux tables dérivées à cet égard.
Passons maintenant aux vues. N'oubliez pas que l'un des principaux avantages des vues est leur réutilisabilité. Vous pouvez également contrôler les autorisations d'accès. Le développement des unités impliquées est un peu plus similaire aux CTE dans le sens où vous pouvez concentrer votre attention sur une unité à la fois du début à la fin. De plus, vous avez la possibilité de décider de créer une vue distincte par unité dans la solution, ou peut-être une seule vue basée sur une requête impliquant des expressions de table nommées à portée d'instruction.
Vous iriez avec le premier lorsque chacune des unités doit être réutilisable. Voici le code que vous utiliseriez dans un tel cas, créant trois vues :
-- Sales.OrderYears CREATE OR ALTER VIEW Sales.OrderYears AS SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders; GO -- Sales.YearlyCustCounts CREATE OR ALTER VIEW Sales.YearlyCustCounts AS SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM Sales.OrderYears GROUP BY orderyear; GO -- Sales.YearlyCustCountsMin70 CREATE OR ALTER VIEW Sales.YearlyCustCountsAbove70 AS SELECT orderyear, numcusts FROM Sales.YearlyCustCounts WHERE numcusts > 70; GO
Vous pouvez interroger chacune des vues indépendamment, mais voici le code que vous utiliseriez pour renvoyer ce que la tâche d'origine était après.
SELECT orderyear, numcusts FROM Sales.YearlyCustCountsAbove70;
S'il existe une exigence de réutilisabilité uniquement pour la partie la plus externe (ce que la tâche d'origine exigeait), il n'est pas vraiment nécessaire de développer trois vues différentes. Vous pouvez créer une vue basée sur une requête impliquant des CTE ou des tables dérivées. Voici comment procéder avec une requête impliquant des CTE :
CREATE OR ALTER VIEW Sales.YearlyCustCountsAbove70 AS WITH C1 AS ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ), C2 AS ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM C1 GROUP BY orderyear ) SELECT orderyear, numcusts FROM C2 WHERE numcusts > 70; GO
Soit dit en passant, si ce n'était pas évident, les CTE sur lesquels la requête interne de la vue est basée peuvent être récursifs.
Passons aux cas où vous avez besoin de plusieurs références à la même expression de table à partir de la requête externe. La tâche de cet exemple consiste à calculer le nombre de commandes annuelles par an et à comparer le nombre de chaque année avec l'année précédente. Le moyen le plus simple d'y parvenir consiste en fait à utiliser la fonction de fenêtre LAG, mais nous utiliserons une jointure entre deux instances d'une expression de table représentant le nombre de commandes annuelles uniquement pour comparer un cas à références multiples parmi les trois outils.
Voici le code que nous avons utilisé plus tôt dans la série pour gérer la tâche avec les tables dérivées :
SELECT CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diff FROM ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate) ) AS CUR LEFT OUTER JOIN ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate) ) AS PRV ON CUR.orderyear = PRV.orderyear + 1;
Il y a un inconvénient très clair ici. Vous devez répéter la définition de l'expression de table deux fois. Vous définissez essentiellement deux expressions de table nommées basées sur le même code de requête.
Voici le code qui gère la même tâche en utilisant les CTE :
WITH OrdCount AS ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate) ) SELECT CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diff FROM OrdCount AS CUR LEFT OUTER JOIN OrdCount AS PRV ON CUR.orderyear = PRV.orderyear + 1;
Il y a un avantage clair ici; vous définissez une seule expression de table nommée basée sur une seule instance de la requête interne et y faites référence deux fois à partir de la requête externe.
Les vues sont plus similaires aux CTE en ce sens. Vous définissez une seule vue basée sur une seule copie de la requête, comme ceci :
CREATE OR ALTER VIEW Sales.YearlyOrderCounts AS SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate); GO
Mais mieux qu'avec les CTE, vous n'êtes pas limité à réutiliser l'expression de table nommée uniquement dans l'instruction externe. Vous pouvez réutiliser le nom de la vue autant de fois que vous le souhaitez, avec n'importe quel nombre de requêtes non liées, tant que vous disposez des autorisations appropriées. Voici le code pour accomplir la tâche en utilisant plusieurs références à la vue :
SELECT CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diff FROM Sales.YearlyOrderCounts AS CUR LEFT OUTER JOIN Sales.YearlyOrderCounts AS PRV ON CUR.orderyear = PRV.orderyear + 1;
Il semble que les vues ressemblent davantage aux CTE qu'aux tables dérivées, avec la fonctionnalité supplémentaire d'être un outil plus réutilisable, avec la possibilité de contrôler les autorisations. Ou pour inverser la tendance, il est probablement approprié de considérer un CTE comme une vue à portée d'instruction. Maintenant, ce qui pourrait être vraiment merveilleux, c'est si nous avions aussi une expression de table nommée avec une portée plus large que celle d'un CTE, plus étroite que celle d'une vue. Par exemple, n'aurait-il pas été formidable d'avoir une expression de table nommée au niveau de la session ?
Résumé
J'adore ce sujet. Il y a tellement de choses dans les expressions de table qui sont enracinées dans la théorie relationnelle, qui à son tour est enracinée dans les mathématiques. J'aime savoir quels sont les bons termes pour les choses, et généralement m'assurer que j'ai bien compris les fondations, même si pour certains cela peut sembler être pointilleux et trop pédant. En regardant mon processus d'apprentissage au fil des ans, je peux voir un chemin très clair entre insister sur une bonne compréhension des fondations, utiliser une terminologie correcte et vraiment connaître votre travail plus tard quand il s'agit de choses beaucoup plus avancées et complexes.
Alors, quels sont les éléments critiques en matière de vues ?
- Une vue est une table.
- Il s'agit d'une table dérivée d'une requête (une expression de table).
- On lui attribue un nom qui apparaît à l'utilisateur comme un nom de table, puisqu'il s'agit d'un nom de table.
- Il est créé en tant qu'objet permanent dans la base de données.
- Vous pouvez contrôler les autorisations d'accès par rapport à la vue.
Les vues sont similaires aux CTE à plusieurs égards. En ce sens que vous développez vos solutions de manière modulaire, en vous concentrant sur une unité à la fois du début à la fin. Également dans le sens où vous pouvez avoir plusieurs références au nom de la vue à partir de la requête externe. Mais mieux que les CTE, les vues ne sont pas limitées uniquement à la portée de la déclaration externe, elles sont plutôt réutilisables jusqu'à ce qu'elles soient supprimées de la base de données.
Il y a beaucoup plus à dire sur les vues, et je poursuivrai la discussion le mois prochain. En attendant, je veux vous laisser avec une pensée. Avec les tables dérivées et les CTE, vous pouvez plaider en faveur de SELECT * dans une requête interne. Voir le cas que j'ai fait pour cela dans la partie 3 de la série pour plus de détails. Pourriez-vous faire un cas similaire avec des vues, ou est-ce une mauvaise idée avec celles-ci ?