Si un CTE est défini dans une requête et qu'il n'est jamais utilisé, produit-il un son?

Réponses:

21

Cela ne semble pas être le cas, mais cela ne s'applique vraiment qu'aux CTE imbriqués.

Créez deux tables temporaires:

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

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

Requête 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;

Requête 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;

Plans de requête:

DES NOISETTES

Il y a une surcharge, mais la partie inutile de la requête est éliminée très tôt (lors de l'analyse syntaxique dans ce cas; l'étape de la simplification dans les cas plus complexes), de sorte que le travail supplémentaire est vraiment minime et ne contribue pas à des coûts potentiellement onéreux basés sur les coûts. optimisation.

Erik Darling
la source
28

+1 à Erik, mais je voulais ajouter deux choses (qui ne fonctionnaient pas bien dans un commentaire):

  1. Vous n'avez même pas besoin d'examiner les plans d'exécution pour vous assurer qu'ils sont ignorés lorsqu'ils ne sont pas utilisés. Ce qui suit devrait produire une erreur "diviser par 0" mais ne doit cte2pas être sélectionné du tout:

    ;WITH cte1 AS
    (
      SELECT 1 AS [Bob]
    ),
    cte2 AS (
      SELECT 1 / 0 AS [Err]
      FROM cte1
    )
    SELECT *
    FROM   cte1;
  2. Les CTE peuvent être ignorés, même s'ils sont les seuls CTE, et même s'ils sont sélectionnés à partir de, si toutes les lignes seraient logiquement exclues. Voici un cas où l'optimiseur de requêtes sait à l'avance qu'aucune ligne ne peut être renvoyée par le CTE, de sorte qu'il ne se donne même pas la peine de l'exécuter:

    ;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 ce qui concerne les performances, le CTE non utilisé est analysé et compilé (ou au moins compilé dans l’affaire ci-dessous), de sorte qu’il ne soit pas ignoré à 100%, mais que le coût devrait être négligeable et ne mérite pas d’être inquiété.

Quand seulement l'analyse, il n'y a pas d'erreur:

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

Lorsque vous faites tout juste avant l'exécution, il y a un problème:

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'.
*/
Salomon Rutzky
la source
J'aimerais pouvoir marquer plus d'une réponse comme correcte, mais Erik vous a battu au tirage au sort. :) Mais votre réponse est très informative et excellente aussi, merci!
JD
Et si les CTE sont dans une vue et que la vue est imbriquée plus de 3 fois? N'y a-t-il pas un point où l'optimiseur abandonne et exécute tout?
Zikato
@Zikato Je n'en ai aucune idée, mais c'est une excellente question. Vous devriez pouvoir configurer un test sans trop d'effort en créant une vue à l'aide de la méthode de division par zéro que j'ai montrée dans les deux premiers exemples. S'il vous plaît laissez-moi savoir les résultats car je suis très curieux à propos de ce scénario :-).
Salomon Rutzky
@SolomonRutzky Pour être juste, je l'ai testé, mais ce n'était pas concluant. J'ai créé une vue à partir de votre exemple cte et je l'ai imbriquée 5 fois, mais comme il s'agit d'une analyse constante et pas vraiment compliquée, l'optimiseur l'a bien gérée. Je voudrais le tester plus en profondeur à l'avenir et le cacher derrière une logique plus complexe. Je vous tiens au courant.
Zikato
@Zikato Intéressant. Je ne sais pas ce qui serait considéré comme "complexe", mais oui, mon exemple est très simpliste. Lorsque vous dites "imbriqué 5 fois", voulez-vous dire dans d'autres vues / procédures qui s'appellent et que la profondeur était de 5, ou dans des sous-requêtes / CTE? Je pense qu’il est possible que l’imbrication d’un nombre suffisant de niveaux l’ignore, mais pas parce qu’elle n’est pas référencée, mais parce qu’un niveau de nidification supérieur ne l’utilise pas et que cela est supposé pour des niveaux inférieurs. J'ai vu où l'astuce de l' NEWID()utilisation d'une vue dans une fonction utilisateur peut renvoyer la même valeur de plusieurs appels en raison de la mise en cache de l'optimiseur.
Solomon Rutzky