Se um CTE é definido em uma consulta e nunca é usado, ele soa?

Respostas:

21

Parece que não, mas isso realmente se aplica apenas a CTEs aninhadas.

Crie duas tabelas temporárias:

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;

Planos de consulta:

NUTS

Existe uma sobrecarga, mas a parte desnecessária da consulta é eliminada muito cedo (durante a análise neste caso; o estágio de simplificação em casos mais complexos), portanto, o trabalho adicional é realmente mínimo e não contribui para custos baseados em custos potencialmente caros. otimização.

Erik Darling
fonte
28

+1 a Erik, mas queria adicionar duas coisas (o que não funcionou bem em um comentário):

  1. Você nem precisa examinar os planos de execução para ver se eles são ignorados quando não são usados. O seguinte deve produzir um erro "dividir por 0", mas isso não ocorre por cte2não ter sido selecionado:

    ;WITH cte1 AS
    (
      SELECT 1 AS [Bob]
    ),
    cte2 AS (
      SELECT 1 / 0 AS [Err]
      FROM cte1
    )
    SELECT *
    FROM   cte1;
  2. As CTEs podem ser ignoradas, mesmo que sejam a única CTE, e mesmo se forem selecionadas, se logicamente todas as linhas fossem excluídas de qualquer maneira. A seguir, é apresentado um caso em que o otimizador de consulta sabe antecipadamente que nenhuma linha pode ser retornada do CTE; portanto, nem se preocupa em executá-lo:

    ;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;

Em relação ao desempenho, o CTE não utilizado é analisado e compilado (ou pelo menos compilado no caso abaixo); portanto, não é 100% ignorado, mas o custo deve ser insignificante e não vale a pena se preocupar.

Ao analisar apenas, não há erro:

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

Ao fazer tudo pouco antes da execução, há um 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
fonte
Gostaria de poder marcar mais de uma resposta como correta, mas Erik venceu você no sorteio da pistola. :) Mas sua resposta é muito informativa e ótima também, obrigado!
JD
E se os CTEs estiverem em uma Visualização e a visualização estiver aninhada mais de três vezes? Não existe um ponto em que o otimizador desista e execute tudo?
Zikato 10/04
@Zikato Não faço ideia, mas essa é uma ótima pergunta. Você deve poder configurar um teste sem muito esforço criando uma exibição usando o truque de dividir por zero que mostrei nos dois primeiros exemplos. Por favor, deixe-me saber os resultados, pois estou muito curioso agora sobre este cenário :-).
Solomon Rutzky 10/04
@SolomonRutzky Para ser justo, eu testei, mas não foi conclusivo. Criei uma visualização do seu exemplo cte e aninhei-a 5 vezes, mas como é uma varredura constante e não é realmente complicada, o otimizador lidou bem com isso. Eu gostaria de testá-lo mais detalhadamente no futuro e escondê-lo atrás de uma lógica mais complexa. Eu aviso você.
Zikato 10/04
@Zikato Interesting. Não tenho certeza do que seria considerado "complexo", mas sim, meu exemplo é muito simplista. Quando você diz "aninhou-o 5 vezes", você quer dizer em outras visualizações / procs que se chamam e eram 5 profundos, ou em subconsultas / CTEs? Eu acho que existe a possibilidade de aninhar níveis suficientes pode ignorá-lo, mas não devido ao fato de não ser referenciado, mas devido a um nível mais alto de ninho não usá-lo e que é assumido para níveis mais baixos. Eu vi onde o truque de colocar NEWID()uma exibição para usar em um UDF pode retornar o mesmo valor de várias chamadas devido ao otimizador que o colocou em cache.
Solomon Rutzky 10/04