Jeśli CTE jest zdefiniowane w zapytaniu i nigdy nie jest używane, czy wydaje dźwięk?

Odpowiedzi:

21

Nie wydaje się, że tak, ale tak naprawdę dotyczy to tylko zagnieżdżonych CTE.

Utwórz dwie tabele tymczasowe:

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

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

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

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

Plany zapytań:

ORZECHY

Istnieje narzut, ale niepotrzebna część zapytania jest eliminowana bardzo wcześnie (podczas analizy w tym przypadku; etap uproszczenia w bardziej skomplikowanych przypadkach), więc dodatkowa praca jest naprawdę minimalna i nie przyczynia się do potencjalnie kosztownych kosztów optymalizacja.

Erik Darling
źródło
28

+1 do Erika, ale chciał dodać dwie rzeczy (które nie działały dobrze w komentarzu):

  1. Nie musisz nawet patrzeć na plany wykonania, aby zobaczyć, że są one ignorowane, gdy nie są używane. Poniższe powinno powodować błąd „dziel przez 0”, ale nie wynika to z cte2braku wyboru:

    ;WITH cte1 AS
    (
      SELECT 1 AS [Bob]
    ),
    cte2 AS (
      SELECT 1 / 0 AS [Err]
      FROM cte1
    )
    SELECT *
    FROM   cte1;
  2. CTE można zignorować, nawet jeśli są one jedynymi CTE, a nawet jeśli zostaną wybrane, jeśli logicznie i tak wszystkie wiersze zostaną wykluczone. Poniżej przedstawiono przypadek, w którym optymalizator zapytań wie z wyprzedzeniem, że żadne wiersze nie mogą zostać zwrócone z CTE, więc nawet nie zadaje sobie trudu, aby go wykonać:

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

Jeśli chodzi o wydajność, nieużywane CTE jest analizowane i kompilowane (lub przynajmniej kompilowane w poniższym przypadku), więc nie jest w 100% ignorowane, ale koszt musiałby być znikomy i nie warto się tym przejmować.

Podczas analizowania nie występuje błąd:

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

Gdy robisz wszystko tuż przed wykonaniem, pojawia się problem:

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
źródło
Chciałbym móc oznaczyć więcej niż jedną odpowiedź jako poprawną, ale Erik pokonał cię w losowaniu pistoletów. :) Ale twoja odpowiedź jest również bardzo pouczająca i świetna, dziękuję!
JD
Co jeśli CTE są w widoku, a widok jest zagnieżdżony więcej niż 3 razy? Czy nie ma punktu, w którym optymalizator poddaje się i uruchamia wszystko?
Zikato
@Zikato Nie mam pojęcia, ale to świetne pytanie. Powinieneś być w stanie skonfigurować test bez większego wysiłku, tworząc widok przy użyciu sztuczki dzielenia przez zero pokazanej w dwóch pierwszych przykładach. Daj mi znać, ponieważ jestem bardzo ciekawy tego scenariusza :-).
Solomon Rutzky
@SolomonRutzky Aby być uczciwym, przetestowałem to, ale nie było to rozstrzygające. Stworzyłem widok z twojego przykładu cte i zagnieździłem go 5 razy, ale ponieważ jest to ciągłe skanowanie i niezbyt skomplikowane, optymalizator poradził sobie z nim dobrze. Chciałbym przetestować go dokładniej w przyszłości i ukryć za bardziej złożoną logiką. Dam ci znać.
Zikato,
@Zikato Ciekawe. Nie jestem pewien, co można by uznać za „złożone”, ale tak, mój przykład jest bardzo uproszczony. Kiedy mówisz „zagnieździłem to 5 razy”, czy masz na myśli w innych widokach / procach, które się do siebie nawzajem wołały i było 5 głębokich, czy w podzapytaniach / CTE? Myślę, że istnieje możliwość, że zagnieżdżenie wystarczającej liczby poziomów może go pominąć, ale nie z powodu braku odniesienia do niego, ale zamiast tego z powodu wyższego poziomu zagnieżdżenia, który go nie używa i że zakłada się go dla niższych poziomów. Widziałem, gdzie sztuczka umieszczenia NEWID()w widoku do użycia w UDF może zwrócić tę samą wartość z wielu wywołań ze względu na buforowanie go przez optymalizator.
Solomon Rutzky