Si un CTE se define en una consulta y nunca se usa, ¿suena?

Respuestas:

21

No parece que lo hagan, pero esto realmente solo se aplica a los CTE anidados.

Crea dos tablas temporales:

CREATE TABLE #t1 (id INT);
INSERT #t1 ( id )
VALUES ( 1 );

CREATE TABLE #t2 (id INT);
INSERT #t2 ( id )
VALUES ( 1 );

Consulta 1:

WITH your_mom AS (
    SELECT TOP 1 *
    FROM #t1 AS t 
),
also_your_mom AS (
    SELECT TOP 1 *
    FROM #t2 AS t
)
SELECT *
FROM your_mom;

Consulta 2:

WITH your_mom AS (
    SELECT TOP 1 *
    FROM #t1 AS t 
),
also_your_mom AS (
    SELECT TOP 1 *
    FROM #t2 AS t
)
SELECT *
FROM also_your_mom;

Planes de consulta:

NUECES

Hay una sobrecarga, pero la parte innecesaria de la consulta se elimina muy pronto (durante el análisis en este caso; la etapa de simplificación en casos más complejos), por lo que el trabajo adicional es realmente mínimo y no contribuye a costos potencialmente costosos. mejoramiento.

Erik Darling
fuente
28

+1 a Erik, pero quería agregar dos cosas (que no funcionaron bien en un comentario):

  1. Ni siquiera necesita mirar los planes de ejecución para ver que se ignoran cuando no se usan. Lo siguiente debería producir un error "dividir por 0" pero no se debe a que cte2no se haya seleccionado en absoluto:

    ;WITH cte1 AS
    (
      SELECT 1 AS [Bob]
    ),
    cte2 AS (
      SELECT 1 / 0 AS [Err]
      FROM cte1
    )
    SELECT *
    FROM   cte1;
  2. Los CTE se pueden ignorar, incluso si son los únicos CTE, e incluso si se seleccionan de ellos, si lógicamente todas las filas se excluirían de todos modos. El siguiente es un caso en el que el optimizador de consultas sabe de antemano que no se pueden devolver filas del CTE, por lo que ni siquiera se molesta en ejecutarlo:

    ;WITH cte AS
    (
      SELECT 1 / 0 AS [Bob]
    )
    SELECT TOP (1) [object_id]
    FROM   sys.objects
    UNION ALL
    SELECT cte.[Bob]
    FROM   cte
    WHERE  1 = 0;

En cuanto al rendimiento, el CTE no utilizado se analiza y compila (o al menos se compila en el caso a continuación), por lo que no se ignora al 100%, pero el costo debería ser insignificante y no merece la pena preocuparse.

Cuando solo se analiza, no hay error:

SET PARSEONLY ON;

;WITH cte1 AS
(
  SELECT obj.[NotHere]
  FROM   sys.objects obj
)
SELECT TOP (1) so.[name]
FROM   sys.objects so

GO
SET PARSEONLY OFF;
GO

Al hacer todo justo antes de la ejecución, entonces hay un problema:

GO
SET NOEXEC ON;
GO

;WITH cte1 AS
(
  SELECT obj.[NotHere]
  FROM   sys.objects obj
)
SELECT TOP (1) so.[name]
FROM   sys.objects so

GO
SET NOEXEC OFF;
GO
/*
Msg 207, Level 16, State 1, Line XXXXX
Invalid column name 'NotHere'.
*/
Solomon Rutzky
fuente
Ojalá pudiera marcar más de una respuesta como correcta, pero Erik te ganó en el sorteo de la pistola. :) Pero tu respuesta es muy informativa y genial también, ¡gracias!
JD
¿Qué sucede si los CTE están en una vista y la vista está anidada más de 3 veces? ¿No hay un punto donde el optimizador se rinde y ejecuta todo?
Zikato
@Zikato No tengo idea, pero esa es una gran pregunta. Debería poder configurar una prueba sin demasiado esfuerzo creando una vista usando el truco de dividir por cero que mostré en los primeros dos ejemplos. Por favor, hágame saber los resultados, ya que tengo mucha curiosidad sobre este escenario :-).
Solomon Rutzky
@SolomonRutzky Para ser justos, lo probé, pero no fue concluyente. Creé una vista a partir de su ejemplo cte y la anidé 5 veces, pero como todo es un escaneo constante y no realmente complicado, el optimizador lo manejó bien. Me gustaría probarlo más a fondo en el futuro y ocultarlo detrás de una lógica más compleja. Yo lo haré saber.
Zikato
@Zikato Interesante. No estoy seguro de lo que se consideraría "complejo", pero sí, mi ejemplo es muy simplista. Cuando dices "anidado 5 veces", ¿te refieres a otras vistas / procesos que se llaman entre sí y tenía 5 profundidades, o en subconsultas / CTE? Creo que existe la posibilidad de que anidar suficientes niveles podría omitirlo, pero no debido a que no se hace referencia a él, sino a un nivel de anidación más alto que no lo usa y que se asume para niveles más bajos. He visto dónde el truco de poner NEWID()una vista para usar en un UDF puede devolver el mismo valor de múltiples llamadas debido al caché del optimizador.
Solomon Rutzky