Jika CTE didefinisikan dalam kueri dan tidak pernah digunakan, apakah CTE mengeluarkan suara?

Jawaban:

21

Tampaknya tidak demikian, tetapi ini hanya berlaku untuk CTE bersarang.

Buat dua tabel temp:

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

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

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

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

Paket pertanyaan:

GILA

Ada overhead, tetapi bagian yang tidak perlu dari query dihilangkan sangat awal (selama parsing dalam kasus ini; tahap penyederhanaan dalam kasus yang lebih kompleks), sehingga pekerjaan tambahan benar-benar minimal, dan tidak berkontribusi terhadap potensi biaya berbasis biaya optimasi.

Erik Darling
sumber
28

+1 ke Erik, tetapi ingin menambahkan dua hal (yang tidak berfungsi dengan baik dalam komentar):

  1. Anda bahkan tidak perlu melihat rencana eksekusi untuk melihat bahwa mereka diabaikan ketika tidak digunakan. Berikut ini akan menghasilkan kesalahan "bagi dengan 0" tetapi tidak karena cte2tidak dipilih sama sekali:

    ;WITH cte1 AS
    (
      SELECT 1 AS [Bob]
    ),
    cte2 AS (
      SELECT 1 / 0 AS [Err]
      FROM cte1
    )
    SELECT *
    FROM   cte1;
  2. CTE dapat diabaikan, bahkan jika mereka adalah satu-satunya CTE, dan bahkan jika mereka dipilih dari, jika secara logis semua baris akan dikecualikan. Berikut ini adalah kasus di mana pengoptimal kueri tahu sebelumnya bahwa tidak ada baris yang dapat dikembalikan dari CTE, sehingga tidak perlu repot untuk mengeksekusinya:

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

Mengenai kinerja, CTE yang tidak digunakan diuraikan dan dikompilasi (atau setidaknya dikompilasi dalam kasus di bawah), sehingga tidak diabaikan 100%, tetapi biaya harus diabaikan dan tidak layak untuk dikhawatirkan.

Saat hanya parsing, tidak ada kesalahan:

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

Ketika melakukan semuanya hanya untuk eksekusi, maka ada masalah:

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
sumber
Seandainya aku bisa menandai lebih dari satu jawaban sebagai yang benar, tetapi Erik mengalahkanmu hingga undian pistol. :) Tapi jawaban Anda sangat informatif dan hebat, terima kasih!
JD
Bagaimana jika CTE berada dalam Tampilan dan tampilan bersarang lebih dari 3 kali? Tidakkah ada titik di mana optimizer menyerah dan menjalankan semua?
Zikato
@Zikato Saya tidak tahu, tapi itu pertanyaan yang bagus. Anda harus dapat membuat tes tanpa terlalu banyak usaha dengan membuat tampilan menggunakan trick divide by zero yang saya tunjukkan dalam dua contoh pertama. Tolong beri tahu saya hasilnya karena saya sangat ingin tahu tentang skenario ini :-).
Solomon Rutzky
@ SolomonRutzky Agar adil, saya mengujinya, tapi itu tidak konklusif. Saya telah membuat tampilan dari contoh cte Anda dan membuatnya bersarang 5 kali, tetapi karena semuanya scan konstan dan tidak terlalu rumit, pengoptimal menanganinya dengan baik. Saya ingin mengujinya lebih menyeluruh di masa depan dan menyembunyikannya di balik logika yang lebih kompleks. Aku akan memberitahu Anda.
Zikato
@Zikato Menarik. Tidak yakin apa yang akan dianggap "kompleks", tapi ya, contoh saya sangat sederhana. Ketika Anda mengatakan "bersarang 5 kali", apakah maksud Anda dalam pandangan / procs lain yang memanggil satu sama lain dan itu 5 dalam, atau dalam subqueries / CTE? Saya pikir ada kemungkinan level yang cukup untuk bersarang mungkin melewatinya, tetapi bukan karena itu tidak direferensikan tetapi karena level sarang yang lebih tinggi tidak menggunakannya dan diasumsikan untuk level yang lebih rendah. Saya telah melihat di mana trik menempatkan NEWID()pandangan untuk digunakan dalam UDF dapat mengembalikan nilai yang sama dari beberapa panggilan karena optimizer caching.
Solomon Rutzky