Quelle est la différence entre WITH CTE ET WITH CTE (<column_names>)?

11

Comme indiqué dans Utilisation des expressions de table communes sur MSDN, vous pouvez définir un CTE comme:

WITH expression_name [ ( column_name [,...n] ) ]
AS
( CTE_query_definition )

et l'utiliser comme:

SELECT <column_list> FROM expression_name;

Disons que j'ai suivi 2 CTE

with cte1 as(
select name from Table1
)

with cte2(name) as(
select name from Table1
)

Une requête génère les mêmes résultats pour les deux CTE car la requête interne est identique. La seule différence entre ces deux est que cte2 a un nom de colonne ( (name)) défini dans sa déclaration.

Lorsque j'exécute les deux CTE, je ne vois aucune différence dans le plan d'exécution.

Je suis juste curieux de savoir:

  • Quelle différence cela fait-il si je ne spécifie aucun nom de colonne dans la définition CTE?
  • Pourquoi devrais-je / ne dois-je pas spécifier de noms de colonnes lors de la création de CTE?
  • Cela affecte-t-il le plan d'exécution des requêtes par hasard? (Pour autant que je l'ai vu, cela ne fait aucune différence.)
Ketan
la source

Réponses:

25

Vous avez déjà presque la réponse à l'une de vos questions.

Dans la page MSDN , il y a une ligne directement après votre devis qui explique ceci:

La structure de syntaxe de base pour un CTE est:

AVEC nom_expression [(nom_colonne [, ... n])]

COMME

(CTE_query_definition)

La liste des noms de colonne n'est facultative que si des noms distincts pour toutes les colonnes résultantes sont fournis dans la définition de la requête.

(Je souligne)

Cela signifierait que vous auriez besoin de spécifier des noms de colonne dans quelques situations:

  • Cela fonctionnerait:

    WITH [test_table] ([NoName], [CAST], [Function]) 
    AS
    (
        SELECT 
            1
          , CAST('1' AS CHAR(1))
          , dbo.CastToChar(1)
    )
    SELECT * FROM [test_table];
  • comme cela:

    WITH [test_table]  
    AS
    (
        SELECT 
            1 as [NoName]
          , CAST('1' AS CHAR(1)) as [CAST]
          , dbo.CastToChar(1) as [Function]
    )
    SELECT * FROM [test_table];
  • Mais ce ne serait pas le cas car il n'a pas de noms distincts pour les colonnes:

    WITH [test_table] 
    AS
    (
        SELECT 
            1
          , CAST('1' AS CHAR(1))
          , dbo.CastToChar(1)
    )
    SELECT * FROM [test_table];
Shaneis
la source
1
essentiellement, la version sans colonnes est la même que la version avec colonne, sauf que SQL doit "déduire" les noms des colonnes de la requête.
KutuluMike
10

Pour l'anecdote, je préfère nommer les colonnes à l'intérieur du CTE au lieu de l'intérieur de la clause WITH CTE (xxx) AS1 , car vous ne mapperez jamais par inadvertance les noms par rapport au contenu des colonnes.

Prenons par exemple l'exemple suivant:

;WITH MyCTE (x, y)
AS 
(
    SELECT mt.y
         , mt.x
    FROM MySchema.MyTable mt
)
SELECT MyCTE.x
     , MyCTE.y
FROM MyCTE;

Qu'est-ce que cela affiche? Il affiche le contenu de la ycolonne sous l'en-tête xet le contenu de la xcolonne sous l'en-tête y.

Avec cette réalisation, je ne spécifie jamais les noms de colonne dans la (xxx) ASclause, au lieu de cela je le fais comme ceci:

;WITH MyCTE
AS 
(
    SELECT Alias1 = mt.y
         , Alias2 = mt.x
    FROM MySchema.MyTable mt
)
SELECT MyCTE.Alias1
     , MyCTE.Alias2
FROM MyCTE;

Cela supprime tout doute sur les définitions de colonne.

Sur une note latérale totalement indépendante; spécifiez toujours le nom du schéma lors du référencement des noms d'objet et terminez vos instructions par un point-virgule .

Max Vernon
la source
7

En fin de compte, chaque colonne a besoin d'un nom valide et vous pouvez l'attribuer de deux manières:

  1. Liste des colonnes

    ;WITH cte (foo)
    AS
    ( select col from tab )
    select foo from cte;
  2. Utilisation de noms de colonnes ou d'alias originaux

    ;WITH cte
    AS
    ( select col from tab )
    select col from cte;

Lorsque vous effectuez à la fois un alias et une liste de colonnes

  1. Liste des colonnes et alias

    ;WITH cte (foo, bar)
    AS
    ( select col1         -- not valid in outer Select
             col2 as colx -- not valid in outer Select
      from tab )
    select foo, bar from cte;

Ceci est similaire à la définition d'une vue ou d'une table dérivée, où vous pouvez également spécifier une liste de noms de colonnes.

liste des colonnes : lorsque vous avez beaucoup de calculs complexes, il est plus facile de repérer le nom, car ils ne sont pas dispersés dans le code source. Et c'est plus facile si vous avez un cte récursif et vous pouvez assigner deux noms différents pour la même colonne dans # 3.

nom / alias d'origine : vous devez uniquement attribuer un alias si vous effectuez un calcul ou si vous souhaitez / devez renommer une colonne

dnoeth
la source
1
"plus facile à repérer" est peut-être un peu subjectif. Je préfère avoir les alias de colonne au début de la ligne, comme dans SomeAlias = SomeFunction(SomeColumn), avec une seule définition de colonne par ligne. Cela permet une simple analyse dans la partie gauche de la liste des colonnes pour localiser celle que vous recherchez.
Max Vernon
1
@MaxVernon: C'est vrai, et l'ajout de lignes vides entre les calculs couvrant plusieurs lignes aide également. En fait,
j'oublie
2
C'est drôle que vous ayez mentionné des vues. Je n'ai jamais utilisé la liste des colonnes après le nom de la vue lors de la définition d'une vue, comme dans CREATE VIEW SomeView (ColA, ColB, …) AS …. Maintenant que vous en avez parlé, je pense à des scénarios comme CREATE VIEW MyView (G) AS WITH cte (C) AS (SELECT A AS B FROM MyTable) SELECT E AS F FROM (SELECT C AS D FROM cte) AS s (E);- quelle joie de déboguer ce serait!
Andriy M