Kapan menggunakan Common Table Expression (CTE)

230

Saya sudah mulai membaca tentang Common Table Expression dan tidak bisa memikirkan use case di mana saya perlu menggunakannya. Mereka tampaknya berlebihan karena hal yang sama dapat dilakukan dengan tabel turunan. Apakah ada sesuatu yang saya lewatkan atau tidak pahami dengan baik? Dapatkah seseorang memberi saya contoh sederhana dari batasan dengan kueri pemilihan, turunan, atau temp sementara untuk membuat kasus CTE? Setiap contoh sederhana akan sangat dihargai.

imak
sumber

Jawaban:

197

Salah satu contoh, jika Anda perlu referensi / bergabung dengan set data yang sama beberapa kali Anda dapat melakukannya dengan mendefinisikan CTE. Oleh karena itu, ini bisa menjadi bentuk penggunaan kembali kode.

Contoh referensi diri adalah rekursi: Permintaan Rekursif Menggunakan CTE

Untuk definisi Microsoft yang menarik Diambil dari Books Online:

CTE dapat digunakan untuk:

  • Buat kueri rekursif. Untuk informasi lebih lanjut, lihat Kueri Rekursif Menggunakan Ekspresi Tabel Umum.

  • Pengganti tampilan saat penggunaan umum tampilan tidak diperlukan; artinya, Anda tidak harus menyimpan definisi dalam metadata.

  • Aktifkan pengelompokan berdasarkan kolom yang berasal dari subselect skalar, atau fungsi yang tidak deterministik atau memiliki akses eksternal.

  • Referensi tabel yang dihasilkan beberapa kali dalam pernyataan yang sama.

John Sansom
sumber
7
Ya. Anda tidak dapat bergabung sendiri dengan tabel turunan. Layak membuat poin bahwa bergabung dengan diri sendiri pada CTE masih akan meninggalkan Anda dengan 2 doa terpisah itu.
Martin Smith
@ Martin - Saya terkejut. Bisakah Anda mendukung pernyataan itu?
RichardTheKiwi
@ John Terima kasih, saya menemukan 4guysfromrolla.com/webtech/071906-1.shtml juga cukup berguna
imak
4
@cyberkiwi - Bit mana? Bahwa bergabung dengan diri sendiri akan menyebabkan 2 doa yang berbeda? Lihat contoh dalam jawaban ini stackoverflow.com/questions/3362043/…
Martin Smith
4
Fakta menarik tentang CTE. Saya selalu bertanya-tanya mengapa NEWID () dalam CTE berubah ketika CTE direferensikan lebih dari sekali. select top 100 * into #tmp from master..spt_values order by 1,2,3,4 select A.number, COUNT(*) from #tmp A inner join #tmp B ON A.number = B.number+1 group by A.numbervswith CTE AS (select top 100 * from master..spt_values order by 1,2,3,4) select A.number, COUNT(*) from CTE A inner join CTE B ON A.number = B.number+1 group by A.number
RichardTheKiwi
50

Saya menggunakannya untuk memecah kueri kompleks, terutama gabungan dan sub-kueri kompleks. Saya menemukan saya semakin sering menggunakannya sebagai 'pseudo-view' untuk membantu saya memahami maksud dari query.

Satu-satunya keluhan saya tentang mereka adalah mereka tidak dapat digunakan kembali. Sebagai contoh, saya mungkin memiliki proc tersimpan dengan dua pernyataan pembaruan yang bisa menggunakan CTE yang sama. Tetapi 'ruang lingkup' CTE adalah permintaan pertama saja.

Masalahnya adalah, 'contoh sederhana' mungkin tidak benar-benar membutuhkan CTE!

Tetap saja, sangat berguna.

n8wrl
sumber
baik. Dapatkah Anda menjelaskan beberapa contoh yang relatif kompleks yang dapat membantu saya memahami konsep ini?
imak
28
"Satu-satunya keluhan saya tentang mereka adalah mereka tidak dapat digunakan kembali" - CTE yang ingin Anda gunakan kembali harus dianggap sebagai kandidat untuk VIEW:)
onedaywhen
6
@onedaywhen: Dipahami, tapi itu menyiratkan lingkup global yang tidak selalu nyaman bagi saya. Terkadang dalam lingkup proc saya ingin mendefinisikan CTE dan menggunakannya untuk memilih dan memperbarui, atau memilih data serupa dari tabel yang berbeda.
n8wrl
5
Ketika saya membutuhkan CTE yang sama lebih dari sekali, saya memasukkannya ke tabel sementara dan kemudian menggunakan tabel sementara sebanyak yang saya inginkan.
Fandango68
43

Ada dua alasan mengapa saya menggunakan cte's.

Untuk menggunakan nilai yang dihitung dalam klausa tempat. Bagi saya ini agak lebih bersih daripada tabel turunan.

Misalkan ada dua tabel - Pertanyaan dan Jawaban digabungkan oleh Pertanyaan.ID = Answers.Question_Id (dan id kuis)

WITH CTE AS
(
    Select Question_Text,
           (SELECT Count(*) FROM Answers A WHERE A.Question_ID = Q.ID) AS Number_Of_Answers
    FROM Questions Q
)
SELECT * FROM CTE
WHERE Number_Of_Answers > 0

Inilah contoh lain di mana saya ingin mendapatkan daftar pertanyaan dan jawaban. Saya ingin Jawaban dikelompokkan dengan pertanyaan dalam hasil.

WITH cte AS
(
    SELECT [Quiz_ID] 
      ,[ID] AS Question_Id
      ,null AS Answer_Id
          ,[Question_Text]
          ,null AS Answer
          ,1 AS Is_Question
    FROM [Questions]

    UNION ALL

    SELECT Q.[Quiz_ID]
      ,[Question_ID]
      ,A.[ID] AS  Answer_Id
      ,Q.Question_Text
          ,[Answer]
          ,0 AS Is_Question
        FROM [Answers] A INNER JOIN [Questions] Q ON Q.Quiz_ID = A.Quiz_ID AND Q.Id = A.Question_Id
)
SELECT 
    Quiz_Id,
    Question_Id,
    Is_Question,
    (CASE WHEN Answer IS NULL THEN Question_Text ELSE Answer END) as Name
FROM cte    
GROUP BY Quiz_Id, Question_Id, Answer_id, Question_Text, Answer, Is_Question 
order by Quiz_Id, Question_Id, Is_Question Desc, Name
BrianK
sumber
10
Tidak bisakah contoh pertama Anda disederhanakan menjadi hanya menggunakan kueri bersarang alih-alih CTE?
Sam
2
Kedua contoh itu bisa saja.
Manachi
3
Anda harus menambahkan yang pertama tanpa CTE, maka segera jelas mengapa yang terakhir berguna.
Ufos
HAVINGadalah cara lain untuk melakukan filter tahap akhir yang dapat mirip dengan menggunakan subSELECT
William Entriken
21

Salah satu skenario yang saya temukan berguna untuk menggunakan CTE adalah ketika Anda ingin mendapatkan baris data yang berbeda berdasarkan pada satu atau lebih kolom tetapi kembalikan semua kolom dalam tabel. Dengan kueri standar, Anda mungkin harus terlebih dahulu membuang nilai-nilai yang berbeda ke tabel temp dan kemudian mencoba untuk menggabungkannya kembali ke tabel asli untuk mengambil sisa kolom atau Anda dapat menulis kueri partisi yang sangat kompleks yang dapat mengembalikan hasil dalam sekali jalan tetapi kemungkinan besar, itu tidak akan bisa dibaca dan menyebabkan masalah kinerja.

Tetapi dengan menggunakan CTE (seperti dijawab oleh Tim Schmelter pada Pilih contoh pertama dari sebuah rekaman )

WITH CTE AS(
    SELECT myTable.*
    , RN = ROW_NUMBER()OVER(PARTITION BY patientID ORDER BY ID)
    FROM myTable 
)
SELECT * FROM CTE
WHERE RN = 1

Seperti yang Anda lihat, ini jauh lebih mudah dibaca dan dipelihara. Dan dibandingkan dengan pertanyaan lain, kinerjanya jauh lebih baik.

TheDanMan
sumber
16

Mungkin lebih bermakna untuk menganggap CTE sebagai pengganti tampilan yang digunakan untuk satu permintaan. Tetapi tidak memerlukan overhead, metadata, atau kegigihan dari pandangan formal. Sangat berguna saat Anda perlu:

  • Buat kueri rekursif.
  • Gunakan resultset CTE lebih dari sekali dalam permintaan Anda.
  • Promosikan kejelasan dalam kueri Anda dengan mengurangi sejumlah besar subqueries identik.
  • Aktifkan pengelompokan berdasarkan kolom yang diturunkan dalam hasil CTE

Inilah contoh cut-and-paste untuk dimainkan:

WITH [cte_example] AS (
SELECT 1 AS [myNum], 'a num' as [label]
UNION ALL
SELECT [myNum]+1,[label]
FROM [cte_example]
WHERE [myNum] <=  10
)
SELECT * FROM [cte_example]
UNION
SELECT SUM([myNum]), 'sum_all' FROM [cte_example]
UNION
SELECT SUM([myNum]), 'sum_odd' FROM [cte_example] WHERE [myNum] % 2 = 1
UNION
SELECT SUM([myNum]), 'sum_even' FROM [cte_example] WHERE [myNum] % 2 = 0;

Nikmati

Vic
sumber
7

Hari ini kita akan belajar tentang ekspresi tabel umum yang merupakan fitur baru yang diperkenalkan di SQL server 2005 dan tersedia dalam versi yang lebih baru juga.

Ekspresi tabel umum: - Ekspresi tabel umum dapat didefinisikan sebagai hasil sementara yang ditetapkan atau dengan kata lain sebagai pengganti tampilan dalam SQL Server. Ekspresi tabel umum hanya valid dalam kumpulan pernyataan yang didefinisikan dan tidak dapat digunakan dalam sesi lain.

Sintaks menyatakan CTE (ekspresi tabel umum): -

with [Name of CTE]
as
(
Body of common table expression
)

Mari kita ambil contoh: -

CREATE TABLE Employee([EID] [int] IDENTITY(10,5) NOT NULL,[Name] [varchar](50) NULL)

insert into Employee(Name) values('Neeraj')
insert into Employee(Name) values('dheeraj')
insert into Employee(Name) values('shayam')
insert into Employee(Name) values('vikas')
insert into Employee(Name) values('raj')

CREATE TABLE DEPT(EID INT,DEPTNAME VARCHAR(100))
insert into dept values(10,'IT')
insert into dept values(15,'Finance')
insert into dept values(20,'Admin')
insert into dept values(25,'HR')
insert into dept values(10,'Payroll')

Saya telah membuat dua tabel karyawan dan Departemen dan memasukkan 5 baris di setiap tabel. Sekarang saya ingin bergabung dengan tabel ini dan membuat hasil sementara yang diatur untuk menggunakannya lebih lanjut.

With CTE_Example(EID,Name,DeptName)
as
(
select Employee.EID,Name,DeptName from Employee 
inner join DEPT on Employee.EID =DEPT.EID
)
select * from CTE_Example

Mari kita ambil setiap baris pernyataan satu per satu dan mengerti.

Untuk mendefinisikan CTE kita menulis klausa "dengan", lalu kita beri nama untuk ekspresi tabel, di sini saya telah memberikan nama sebagai "CTE_Example"

Kemudian kita menulis "Sebagai" dan melampirkan kode kita dalam dua tanda kurung (---), kita dapat bergabung dengan beberapa tabel dalam tanda kurung terlampir.

Pada baris terakhir, saya telah menggunakan "Select * from CTE_Example", kita merujuk ekspresi tabel Common di baris terakhir kode, Jadi kita dapat mengatakan bahwa ini seperti tampilan, di mana kita mendefinisikan dan menggunakan tampilan dalam satu batch dan CTE tidak disimpan dalam database sebagai objek permanen. Tapi itu berperilaku seperti pandangan. kita dapat melakukan hapus dan perbarui pernyataan pada CTE dan itu akan berdampak langsung pada tabel yang direferensikan yang sedang digunakan dalam CTE. Mari kita ambil contoh untuk memahami fakta ini.

With CTE_Example(EID,DeptName)
as
(
select EID,DeptName from DEPT 
)
delete from CTE_Example where EID=10 and DeptName ='Payroll'

Dalam pernyataan di atas kami menghapus baris dari CTE_Example dan itu akan menghapus data dari tabel yang direferensikan "DEPT" yang sedang digunakan dalam CTE.

Neeraj Kumar Yadav
sumber
Saya masih belum mengerti intinya. Apa perbedaan antara ini dan hanya menghapus dari DEPT dengan kondisi yang persis sama? Sepertinya tidak membuat apa pun lebih mudah.
Holger Jakobs
Tolong koreksi saya jika saya salah, tetapi rencana pelaksanaannya mungkin berbeda, dan saya pikir itu adalah poin Neeraj, bahwa ada banyak cara untuk mencapai tujuan yang sama, tetapi beberapa akan memiliki keunggulan dibandingkan yang lain tergantung pada situasinya. Sebagai contoh, mungkin lebih mudah untuk membaca CTE melalui pernyataan DELETE FROM dalam beberapa keadaan, juga kebalikannya mungkin terjadi pada orang lain. Kinerja dapat meningkat atau memburuk. dll.
WonderWorker
7

Ini sangat berguna ketika Anda ingin melakukan "pembaruan yang dipesan".

MS SQL tidak memungkinkan Anda untuk menggunakan ORDER BY dengan UPDATE, tetapi dengan bantuan CTE Anda dapat melakukannya dengan cara itu:

WITH cte AS
(
    SELECT TOP(5000) message_compressed, message, exception_compressed, exception
    FROM logs
    WHERE Id >= 5519694 
    ORDER BY Id
)
UPDATE  cte
SET     message_compressed = COMPRESS(message), exception_compressed = COMPRESS(exception)

Lihat di sini untuk info lebih lanjut: Cara memperbarui dan memesan dengan menggunakan ms sql

bside
sumber
0

Satu hal yang belum ditunjukkan, adalah kecepatan . Saya tahu ini adalah pertanyaan lama yang dijawab, tetapi saya pikir ini layak mendapat komentar / jawaban langsung:

Mereka tampaknya berlebihan karena hal yang sama dapat dilakukan dengan tabel turunan

Ketika saya menggunakan CTE pertama kali saya benar-benar terpana oleh kecepatannya. Itu adalah kasus seperti dari buku teks, sangat cocok untuk CTE, tetapi di semua kesempatan saya pernah menggunakan CTE, ada peningkatan kecepatan yang signifikan. Permintaan pertama saya rumit dengan tabel turunan, membutuhkan waktu beberapa menit untuk dieksekusi. Dengan CTE, butuh waktu sepersekian detik dan membuat saya kaget, itu bahkan mungkin.

Oak_3260548
sumber
-4
 ;with cte as
  (
  Select Department, Max(salary) as MaxSalary
  from test
  group by department
  )  
  select t.* from test t join cte c on c.department=t.department 
  where t.salary=c.MaxSalary;

coba ini

Sudhir Panda
sumber