¿Cuál es la diferencia entre WITH CTE y WITH CTE (<column_names>)?

11

Como se muestra en Uso de expresiones de tabla comunes en MSDN, puede definir un CTE como:

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

y úsalo como:

SELECT <column_list> FROM expression_name;

Digamos que tengo los siguientes 2 CTE

with cte1 as(
select name from Table1
)

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

Una consulta genera los mismos resultados para ambos CTE que la consulta interna es la misma. La única diferencia entre estos dos es que cte2 tiene el nombre de columna ( (name)) definido en su declaración.

Cuando ejecuto ambos CTE, no veo ninguna diferencia en el plan de ejecución.

Tengo curiosidad por saber:

  • ¿Qué diferencia hay si no especifico ningún nombre de columna en la definición de CTE?
  • ¿Por qué debería / no debería especificar nombres de columna al crear CTE?
  • ¿Afecta el plan de ejecución de consultas por casualidad? (Por lo que he visto, no hace ninguna diferencia).
Ketan
fuente

Respuestas:

25

Ya casi tienes la respuesta para una de tus preguntas.

En la página de MSDN , hay una línea directamente después de su presupuesto que explica esto:

La estructura de sintaxis básica para un CTE es:

WITH nombre_expresión [(nombre_columna [, ... n])]

COMO

(CTE_query_definition)

La lista de nombres de columna es opcional solo si se proporcionan nombres distintos para todas las columnas resultantes en la definición de la consulta.

(Énfasis añadido)

Esto significaría que necesitaría especificar nombres de columna en algunas situaciones:

  • Esto funcionaría:

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

    WITH [test_table]  
    AS
    (
        SELECT 
            1 as [NoName]
          , CAST('1' AS CHAR(1)) as [CAST]
          , dbo.CastToChar(1) as [Function]
    )
    SELECT * FROM [test_table];
    
  • Pero esto no sería así, ya que no tiene nombres distintos para las columnas:

    WITH [test_table] 
    AS
    (
        SELECT 
            1
          , CAST('1' AS CHAR(1))
          , dbo.CastToChar(1)
    )
    SELECT * FROM [test_table];
    
Shaneis
fuente
1
esencialmente, la versión sin columnas es la misma que la versión con columna, excepto que SQL tiene que "inferir" los nombres de columna de la consulta.
KutuluMike
10

Como anécdota, prefiero nombrar las columnas dentro del CTE en lugar de dentro de la cláusula WITH CTE (xxx) AS1, ya que nunca desajustarás los nombres con el contenido de la columna.

Tomemos por ejemplo el siguiente ejemplo:

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

¿Qué muestra esto? Muestra el contenido de la ycolumna debajo del encabezado xy el contenido de la xcolumna debajo del encabezado y.

Con esta realización, nunca especifico los nombres de columna en la (xxx) AScláusula, sino que lo hago así:

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

Esto elimina toda duda sobre cuáles son las definiciones de columna.

En una nota al margen totalmente no relacionada; siempre especifique el nombre del esquema cuando haga referencia a nombres de objetos y finalice sus declaraciones con un punto y coma .

Max Vernon
fuente
7

Finalmente, cada columna necesita un nombre válido y puede asignarlo de dos maneras:

  1. Lista de columnas

    ;WITH cte (foo)
    AS
    ( select col from tab )
    select foo from cte;
    
  2. Usar nombres de columna o alias originales

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

Cuando haces tanto el alias como la lista de columnas

  1. Lista de columnas y 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;
    

Esto es similar a la definición de una Vista o una Tabla derivada, donde también puede especificar una lista de nombres de columna.

lista de columnas : cuando tiene muchos cálculos complejos, es más fácil detectar el nombre, ya que no están dispersos en el código fuente. Y es más fácil si tienes un cte recursivo y puedes asignar dos nombres diferentes para la misma columna en el n. ° 3.

nombre / alias originales : solo debe asignar un alias si realiza un cálculo o desea / debe cambiar el nombre de una columna

dnoeth
fuente
1
"más fácil de detectar" es quizás un poco subjetivo. Prefiero tener los alias de columna al comienzo de la línea, como en SomeAlias = SomeFunction(SomeColumn), con solo una definición de columna por línea. Esto permite un escaneo simple en el lado izquierdo de la lista de columnas para localizar el que está buscando.
Max Vernon
1
@MaxVernon: Eso es correcto, y agregar líneas en blanco entre los cálculos que abarcan varias líneas también ayuda. De hecho que en su mayoría Omito la lista de columnas, también ...
dnoeth
2
Es curioso que hayas mencionado puntos de vista. Nunca he usado la lista de columnas después del nombre de la vista al definir una vista, como en CREATE VIEW SomeView (ColA, ColB, …) AS …. Ahora que lo mencionó, estoy pensando en escenarios como 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);: ¡qué alegría sería depurar eso!
Andriy M