เมื่อใดจึงควรใช้ Common Table Expression (CTE)


230

ฉันเริ่มอ่านเกี่ยวกับCommon Table Expressionและไม่สามารถนึกถึงกรณีการใช้งานที่ฉันจะต้องใช้ พวกเขาดูเหมือนจะซ้ำซ้อนเหมือนกันสามารถทำได้ด้วยตารางที่ได้รับ มีบางอย่างที่ฉันขาดหายไปหรือไม่เข้าใจใช่ไหม ใครสามารถให้ตัวอย่างง่ายๆของข้อ จำกัด กับแบบสอบถามแบบธรรมดา, มาหรือ temp ตารางเพื่อให้กรณีของ CTE? ตัวอย่างง่ายๆใด ๆ ที่จะได้รับการชื่นชมอย่างมาก

คำตอบ:


197

ตัวอย่างหนึ่งถ้าคุณต้องการอ้างอิง / เข้าร่วมชุดข้อมูลเดียวกันหลาย ๆ ครั้งคุณสามารถทำได้โดยการกำหนด CTE ดังนั้นจึงอาจเป็นรูปแบบของการใช้รหัสซ้ำ

ตัวอย่างของการอ้างอิงตนเองคือการสอบถามซ้ำ: การสอบถามซ้ำโดยใช้ CTE

สำหรับคำจำกัดความที่น่าตื่นเต้นของ Microsoft ที่ นำมาจาก Books Online:

CTE สามารถใช้เพื่อ:

  • สร้างแบบสอบถามแบบเรียกซ้ำ สำหรับข้อมูลเพิ่มเติมให้ดูที่เคียวรีแบบเรียกซ้ำโดยใช้นิพจน์ตารางทั่วไป

  • ทดแทนมุมมองเมื่อไม่จำเป็นต้องใช้มุมมองโดยทั่วไป นั่นคือคุณไม่จำเป็นต้องจัดเก็บคำนิยามในข้อมูลเมตา

  • เปิดใช้งานการจัดกลุ่มตามคอลัมน์ที่ได้มาจากการเลือกย่อยแบบสเกลาร์หรือฟังก์ชั่นที่ไม่ได้กำหนดไว้หรือมีการเข้าถึงจากภายนอก

  • อ้างอิงตารางผลลัพธ์หลาย ๆ ครั้งในข้อความเดียวกัน


7
อ๋อ คุณไม่สามารถเข้าร่วมตารางที่ได้รับด้วยตนเอง คุ้มค่าที่จะชี้ให้เห็นว่าการเข้าร่วม CTE ด้วยตนเองจะยังทำให้คุณมี 2 การเรียกร้องแยกต่างหาก
Martin Smith

@ มาร์ติน - ฉันประหลาดใจ คุณสามารถสำรองคำสั่งนั้นได้หรือไม่?
RichardTheKiwi

@ John ขอบคุณฉันกำลังหา4guysfromrolla.com/webtech/071906-1.shtmlประโยชน์มากเกินไป
imak

4
@cyberkiwi - อันไหน? การเข้าร่วมด้วยตนเองจะนำไปสู่การเรียกร้องที่แตกต่างกัน 2 รายการ ดูตัวอย่างในคำตอบนี้stackoverflow.com/questions/3362043/…
Martin Smith

4
ข้อเท็จจริงที่น่าสนใจเกี่ยวกับ CTE ฉันมักจะสงสัยว่าทำไม NEWID () ใน CTE เปลี่ยนแปลงเมื่อ CTE ถูกอ้างอิงมากกว่าหนึ่งครั้ง 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

ฉันใช้มันเพื่อแยกย่อยข้อความค้นหาที่ซับซ้อนโดยเฉพาะอย่างยิ่งการรวมที่ซับซ้อนและแบบสอบถามย่อย ฉันพบว่าฉันใช้พวกเขามากขึ้นเรื่อย ๆ ในฐานะ 'มุมมองหลอก' เพื่อช่วยให้ฉันได้รับความสนใจจากเจตนาของแบบสอบถาม

การร้องเรียนเพียงอย่างเดียวของฉันคือพวกเขาไม่สามารถนำกลับมาใช้ ตัวอย่างเช่นฉันอาจมี proc ที่เก็บไว้พร้อมกับคำสั่งการอัพเดตสองคำที่สามารถใช้ CTE เดียวกันได้ แต่ 'ขอบเขต' ของ CTE เป็นข้อความค้นหาแรกเท่านั้น

ปัญหาคือ 'ตัวอย่างง่ายๆ' อาจไม่ต้องการ CTE จริงๆ!

ยังมีประโยชน์มาก


ตกลง. คุณสามารถพูดถึงตัวอย่างที่ค่อนข้างซับซ้อนซึ่งสามารถช่วยหัวของฉันเกี่ยวกับแนวคิดนี้ได้หรือไม่?
imak

28
"ข้อร้องเรียนเดียวของฉันเกี่ยวกับพวกเขาคือพวกเขาไม่สามารถนำมาใช้ซ้ำได้" - CTE ที่คุณต้องการนำกลับมาใช้ใหม่ควรได้รับการพิจารณาให้เป็นผู้สมัครสำหรับVIEW:)
oneday เมื่อ

6
@onedaywhen: เข้าใจ แต่นั่นก็หมายถึงขอบเขตระดับโลกที่ฉันไม่ค่อยพอใจ บางครั้งอยู่ในขอบเขตของ proc ฉันต้องการกำหนด CTE และใช้มันเพื่อเลือกและอัปเดตหรือเลือกข้อมูลที่คล้ายกันจากตารางที่แตกต่างกัน
n8wrl

5
เมื่อฉันต้องการ CTE เดียวกันมากกว่าหนึ่งครั้งฉันป้อนมันลงในตารางชั่วคราวจากนั้นใช้ตารางชั่วคราวมากเท่าที่ฉันต้องการ
Fandango68

43

มีสองเหตุผลที่ฉันเห็นการใช้ cte

เมื่อต้องการใช้ค่าที่คำนวณได้ในส่วนคำสั่ง where ดูเหมือนว่าฉันจะสะอาดกว่าโต๊ะเล็ก ๆ

สมมติว่ามีสองตาราง - คำถามและคำตอบเข้าด้วยกันโดย Questions.ID = Answers.Question_Id (และรหัสคำถาม)

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

นี่เป็นอีกตัวอย่างที่ฉันต้องการรับรายการคำถามและคำตอบ ฉันต้องการจัดกลุ่มคำตอบพร้อมคำถามในผลลัพธ์

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

10
ตัวอย่างแรกของคุณไม่สามารถทำให้ง่ายลงเพียงแค่ใช้แบบสอบถามที่ซ้อนกันแทน CTE หรือไม่
แซม

2
ทั้งสองตัวอย่างอาจเป็น
Manachi

3
คุณควรเพิ่มอันแรกโดยไม่มี CTE ดังนั้นจึงเห็นได้ชัดทันทีว่าทำไมประโยชน์หลัง
Ufos

HAVINGเป็นอีกวิธีในการทำตัวกรองขั้นตอนปลายซึ่งสามารถคล้ายกับการใช้ย่อยSELECT
William Entriken

21

หนึ่งในสถานการณ์ที่ฉันพบว่ามีประโยชน์ในการใช้ CTE คือเมื่อคุณต้องการรับแถว DISTINCT ของข้อมูลตามคอลัมน์ตั้งแต่หนึ่งคอลัมน์ขึ้นไป แต่กลับคอลัมน์ทั้งหมดในตาราง ด้วยแบบสอบถามมาตรฐานคุณอาจต้องถ่ายโอนค่าที่แตกต่างกันลงในตาราง temp ก่อนจากนั้นลองเชื่อมกลับไปที่ตารางเดิมเพื่อดึงคอลัมน์ที่เหลือหรือคุณอาจเขียนแบบสอบถามพาร์ติชันที่ซับซ้อนมากซึ่งสามารถส่งคืนผลลัพธ์ใน การวิ่งครั้งเดียว แต่ในโอกาสส่วนใหญ่มันจะไม่สามารถอ่านได้และทำให้เกิดปัญหาประสิทธิภาพ

แต่โดยใช้ CTE (ตอบโดย Tim Schmelter บนเลือกตัวอย่างแรกของบันทึก )

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

อย่างที่คุณเห็นนี่เป็นเรื่องง่ายกว่าที่จะอ่านและบำรุงรักษา และเมื่อเปรียบเทียบกับข้อความค้นหาอื่น ๆ ประสิทธิภาพจะดีขึ้นมาก


16

บางทีอาจมีความหมายมากกว่าที่จะคิดว่า CTE เป็นสิ่งทดแทนสำหรับมุมมองที่ใช้สำหรับการสืบค้นเดียว แต่ไม่ต้องการค่าใช้จ่ายข้อมูลเมตาหรือการคงอยู่ของมุมมองที่เป็นทางการ มีประโยชน์มากเมื่อคุณต้องการ:

  • สร้างแบบสอบถามแบบเรียกซ้ำ
  • ใช้ชุดผลลัพธ์ CTE มากกว่าหนึ่งครั้งในแบบสอบถามของคุณ
  • ส่งเสริมความชัดเจนในการค้นหาของคุณโดยการลดชุดย่อยของแบบสอบถามย่อยที่เหมือนกันจำนวนมาก
  • เปิดใช้งานการจัดกลุ่มตามคอลัมน์ที่ได้รับในชุดผลลัพธ์ของ CTE

ต่อไปนี้เป็นตัวอย่างแบบตัดและวางเพื่อเล่นกับ:

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;

สนุก


7

วันนี้เราจะเรียนรู้เกี่ยวกับนิพจน์ตารางทั่วไปซึ่งเป็นคุณสมบัติใหม่ซึ่งเปิดตัวใน SQL Server 2005 และมีให้ในรุ่นต่อ ๆ ไปเช่นกัน

นิพจน์ตารางทั่วไป: - นิพจน์ตารางทั่วไปสามารถกำหนดเป็นชุดผลลัพธ์ชั่วคราวหรือกล่าวอีกนัยหนึ่งคือแทนที่มุมมองใน SQL Server นิพจน์ตารางทั่วไปใช้ได้เฉพาะในชุดคำสั่งที่กำหนดไว้และไม่สามารถใช้ในเซสชันอื่นได้

ไวยากรณ์ของการประกาศ CTE (นิพจน์ตารางทั่วไป): -

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

ให้ยกตัวอย่าง: -

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')

ฉันได้สร้างพนักงานสองตารางและ Dept และแทรก 5 แถวในแต่ละตาราง ตอนนี้ฉันต้องการเข้าร่วมตารางเหล่านี้และสร้างชุดผลลัพธ์ชั่วคราวเพื่อใช้งานต่อไป

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

ให้รับแต่ละบรรทัดของคำสั่งทีละรายการและเข้าใจ

ในการกำหนด CTE เราเขียนประโยค "with" จากนั้นเราตั้งชื่อให้กับนิพจน์ตารางที่นี่ฉันได้ตั้งชื่อเป็น "CTE_Example"

จากนั้นเราเขียน "เป็น" และใส่รหัสของเราในสองวงเล็บ (---) เราสามารถเข้าร่วมหลายตารางในวงเล็บปิด

ในบรรทัดสุดท้ายฉันใช้ "เลือก * จาก CTE_Example" เราอ้างอิงนิพจน์ตารางทั่วไปในบรรทัดสุดท้ายของรหัสดังนั้นเราจึงสามารถพูดได้ว่ามันเป็นมุมมองที่เรากำหนดและใช้มุมมองในที่เดียว batch และ CTE ไม่ได้ถูกจัดเก็บในฐานข้อมูลเป็นวัตถุถาวร แต่มันทำตัวเหมือนเป็นมุมมอง เราสามารถดำเนินการลบและอัปเดตคำสั่งใน CTE และจะมีผลกระทบโดยตรงต่อตารางอ้างอิงที่ใช้ใน CTE ลองยกตัวอย่างเพื่อทำความเข้าใจความจริงข้อนี้

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

ในคำสั่งด้านบนเราจะลบแถวออกจาก CTE_Example และมันจะลบข้อมูลจากตารางอ้างอิง "DEPT" ที่ใช้ใน CTE


ฉันยังไม่เข้าใจ ความแตกต่างระหว่างสิ่งนี้และเพิ่งลบจาก DEPT ด้วยเงื่อนไขเดียวกันทั้งหมดหรือไม่ ดูเหมือนว่าจะไม่ทำให้อะไรง่ายขึ้น
Holger Jakobs

โปรดแก้ไขให้ฉันถ้าฉันผิด แต่แผนการดำเนินการอาจแตกต่างกันและฉันคิดว่านั่นเป็นประเด็นของ Neeraj ว่ามีหลายวิธีในการบรรลุวัตถุประสงค์เดียวกัน แต่บางคนก็มีข้อได้เปรียบเหนือผู้อื่นขึ้นอยู่กับสถานการณ์ ตัวอย่างเช่นมันอาจจะง่ายกว่าในการอ่าน CTE ผ่านคำสั่ง DELETE FROM ในบางสถานการณ์และการย้อนกลับอาจเป็นจริงในผู้อื่น ประสิทธิภาพอาจดีขึ้นหรือแย่ลง ฯลฯ
WonderWorker

7

มันมีประโยชน์มากเมื่อคุณต้องการทำการ "สั่งซื้ออัปเดต"

MS SQL ไม่อนุญาตให้คุณใช้ ORDER BY กับ UPDATE แต่ด้วยความช่วยเหลือของ CTE คุณสามารถทำได้ดังนี้:

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)

ดูที่นี่สำหรับข้อมูลเพิ่มเติม: วิธีอัปเดตและสั่งซื้อโดยใช้ ms sql


0

จุดหนึ่งที่ไม่ได้ชี้ให้เห็นยังเป็นความเร็ว ฉันรู้ว่ามันเป็นคำถามที่ตอบแล้ว แต่ฉันคิดว่านี่สมควรได้รับความเห็น / คำตอบโดยตรง:

พวกเขาดูเหมือนจะซ้ำซ้อนเหมือนกันสามารถทำได้ด้วยตารางที่ได้รับ

เมื่อฉันใช้ CTE เป็นครั้งแรกที่ฉันรู้สึกทึ่งกับความเร็ว มันเหมือนกรณีจากตำราเรียนเหมาะมากสำหรับ CTE แต่ในทุกกรณีที่ฉันเคยใช้ CTE นั้นมีความเร็วเพิ่มขึ้นอย่างมาก แบบสอบถามแรกของฉันซับซ้อนกับตารางที่ได้รับซึ่งใช้เวลาดำเนินการนานหลายนาที ด้วย CTE มันใช้เวลาเสี้ยววินาทีและทำให้ฉันตกใจจนเป็นไปได้


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

ลองนี้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.