Khi nào nên sử dụng Biểu thức bảng chung (CTE)


230

Tôi đã bắt đầu đọc về Biểu thức bảng chung và không thể nghĩ ra trường hợp sử dụng mà tôi sẽ cần sử dụng chúng. Chúng dường như là dư thừa vì điều tương tự có thể được thực hiện với các bảng dẫn xuất. Có điều gì tôi đang thiếu hoặc không hiểu rõ? Ai đó có thể cho tôi một ví dụ đơn giản về các hạn chế với các truy vấn bảng chọn, dẫn xuất hoặc tạm thời để tạo ra trường hợp của CTE không? Bất kỳ ví dụ đơn giản sẽ được đánh giá cao.

Câu trả lời:


197

Một ví dụ, nếu bạn cần tham chiếu / tham gia cùng một tập dữ liệu nhiều lần bạn có thể làm như vậy bằng cách xác định CTE. Do đó, nó có thể là một hình thức sử dụng lại mã.

Một ví dụ về tự tham chiếu là đệ quy: Truy vấn đệ quy bằng CTE

Đối với các định nghĩa thú vị của Microsoft Lấy từ Sách trực tuyến:

Một CTE có thể được sử dụng để:

  • Tạo một truy vấn đệ quy. Để biết thêm thông tin, hãy xem Truy vấn đệ quy bằng Biểu thức bảng chung.

  • Thay thế cho một chế độ xem khi việc sử dụng chung của một chế độ xem là không bắt buộc; nghĩa là, bạn không phải lưu trữ định nghĩa trong siêu dữ liệu.

  • Cho phép nhóm theo một cột có nguồn gốc từ một tập hợp con vô hướng hoặc một hàm không xác định hoặc có quyền truy cập bên ngoài.

  • Tham chiếu bảng kết quả nhiều lần trong cùng một tuyên bố.


7
Vâng. Bạn không thể tự tham gia một bảng dẫn xuất. Đáng để đưa ra quan điểm rằng việc tự tham gia vào CTE vẫn sẽ khiến bạn có 2 yêu cầu riêng biệt về nó.
Martin Smith

@Martin - Tôi ngạc nhiên. Bạn có thể sao lưu tuyên bố đó?
RichardTheKiwi

@ John Cảm ơn, tôi đang tìm 4guysfromrolla.com/webtech/071906-1.shtml khá hữu ích
imak

4
@cyberkiwi - Bit nào? Rằng tự tham gia sẽ dẫn đến 2 lời mời khác nhau? Xem ví dụ trong câu trả lời này stackoverflow.com/questions/3362043/ từ
Martin Smith

4
Thực tế thú vị về CTE. Tôi luôn tự hỏi tại sao NEWID () trong CTE thay đổi khi CTE được tham chiếu nhiều lần. 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

Tôi sử dụng chúng để phá vỡ các truy vấn phức tạp, đặc biệt là các phép nối và truy vấn phụ phức tạp. Tôi thấy rằng tôi đang sử dụng chúng ngày càng nhiều hơn dưới dạng 'các khung nhìn giả' để giúp tôi hiểu được ý định của truy vấn.

Khiếu nại duy nhất của tôi về họ là họ không thể được sử dụng lại. Ví dụ: tôi có thể có một Proc được lưu trữ với hai câu lệnh cập nhật có thể sử dụng cùng một CTE. Nhưng 'phạm vi' của CTE chỉ là truy vấn đầu tiên.

Rắc rối là, 'ví dụ đơn giản' có lẽ không thực sự cần CTE!

Tuy nhiên, rất tiện dụng.


đồng ý. Bạn có thể làm cho trường hợp với một số ví dụ tương đối phức tạp có thể giúp tôi suy nghĩ về khái niệm này?
imak

28
"Khiếu nại duy nhất của tôi về chúng là chúng không thể được sử dụng lại" - một CTE mà bạn muốn sử dụng lại nên được coi là ứng cử viên cho một VIEW:)
ngày

6
@encedaywhen: Đã hiểu, nhưng điều đó hàm ý phạm vi toàn cầu mà tôi không phải lúc nào cũng thoải mái. Đôi khi trong phạm vi của một Proc tôi muốn xác định CTE và sử dụng nó cho các lựa chọn và cập nhật hoặc chọn các dữ liệu tương tự từ các bảng khác nhau.
n8wrl

5
Khi tôi cần cùng một CTE nhiều lần, tôi đưa nó vào một bảng tạm thời và sau đó sử dụng bảng tạm thời nhiều như tôi muốn.
Fandango68

43

Có hai lý do tôi thấy để sử dụng cte's.

Để sử dụng một giá trị tính toán trong mệnh đề where. Điều này có vẻ sạch sẽ hơn đối với tôi so với một bảng dẫn xuất.

Giả sử có hai bảng - Câu hỏi và Câu trả lời được nối với nhau bằng Câu hỏi.ID = Câu trả lời.Question_Id (và id câu đố)

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

Đây là một ví dụ khác mà tôi muốn nhận danh sách các câu hỏi và câu trả lời. Tôi muốn các câu trả lời được nhóm với các câu hỏi trong kết quả.

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
Ví dụ đầu tiên của bạn có thể được đơn giản hóa để chỉ sử dụng truy vấn lồng nhau thay vì CTE không?
Sam

2
Cả hai ví dụ có thể được.
Manachi

3
Bạn nên thêm cái đầu tiên mà không có CTE, thì ngay lập tức rõ ràng tại sao cái sau lại hữu ích.
Ufos

HAVINGlà một cách khác để thực hiện bộ lọc giai đoạn cuối có thể tương tự như sử dụng một phụSELECT
William Entriken

21

Một trong những tình huống tôi thấy hữu ích khi sử dụng CTE là khi bạn muốn nhận các hàng dữ liệu DISTINCT dựa trên một hoặc nhiều cột nhưng trả về tất cả các cột trong bảng. Với một truy vấn tiêu chuẩn, trước tiên bạn có thể phải đổ các giá trị riêng biệt vào một bảng tạm thời và sau đó thử nối chúng trở lại bảng ban đầu để truy xuất các cột còn lại hoặc bạn có thể viết một truy vấn phân vùng cực kỳ phức tạp có thể trả về kết quả trong một lần chạy nhưng rất có thể, nó sẽ không thể đọc được và gây ra vấn đề về hiệu năng.

Nhưng bằng cách sử dụng CTE (như được trả lời bởi Tim Schmelter trên Chọn phiên bản đầu tiên của bản ghi )

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

Như bạn có thể thấy, điều này dễ đọc và dễ bảo trì hơn nhiều. Và so với các truy vấn khác, hiệu suất tốt hơn nhiều.


16

Có lẽ sẽ có ý nghĩa hơn khi nghĩ về CTE thay thế cho chế độ xem được sử dụng cho một truy vấn duy nhất. Nhưng không yêu cầu chi phí chung, siêu dữ liệu hoặc tính bền vững của chế độ xem chính thức. Rất hữu ích khi bạn cần:

  • Tạo một truy vấn đệ quy.
  • Sử dụng tập kết quả của CTE nhiều lần trong truy vấn của bạn.
  • Thúc đẩy sự rõ ràng trong truy vấn của bạn bằng cách giảm các khối lớn của các truy vấn con giống hệt nhau.
  • Cho phép nhóm theo một cột xuất phát trong tập kết quả của CTE

Đây là một ví dụ cắt và dán để chơi với:

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;

Thưởng thức


7

Hôm nay chúng ta sẽ tìm hiểu về biểu thức Bảng chung là một tính năng mới được giới thiệu trong máy chủ SQL 2005 và cũng có sẵn trong các phiên bản sau.

Biểu thức bảng chung: - Biểu thức bảng chung có thể được định nghĩa là tập kết quả tạm thời hoặc nói cách khác, nó là sự thay thế các khung nhìn trong SQL Server. Biểu thức bảng chung chỉ có giá trị trong lô câu lệnh được xác định và không thể được sử dụng trong các phiên khác.

Cú pháp khai báo CTE (Biểu thức bảng chung): -

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

Hãy lấy một ví dụ: -

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

Tôi đã tạo hai nhân viên bảng và Phòng và chèn 5 hàng vào mỗi bảng. Bây giờ tôi muốn tham gia các bảng này và tạo một tập kết quả tạm thời để sử dụng nó thêm.

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

Hãy lần lượt lấy từng dòng của câu lệnh và hiểu.

Để định nghĩa CTE, chúng ta viết mệnh đề "with", sau đó chúng ta đặt tên cho biểu thức bảng, ở đây tôi đã đặt tên là "CTE_Example"

Sau đó, chúng tôi viết "As" và đặt mã của chúng tôi trong hai dấu ngoặc (---), chúng tôi có thể tham gia nhiều bảng trong ngoặc đơn kèm theo.

Trong dòng cuối cùng, tôi đã sử dụng "Chọn * từ CTE_Example", chúng tôi đang đề cập đến biểu thức Bảng chung trong dòng mã cuối cùng, vì vậy chúng tôi có thể nói rằng nó giống như một khung nhìn, trong đó chúng tôi đang xác định và sử dụng chế độ xem trong một lô và CTE không được lưu trữ trong cơ sở dữ liệu dưới dạng đối tượng vĩnh viễn. Nhưng nó hành xử như một quan điểm. chúng ta có thể thực hiện xóa và cập nhật câu lệnh trên CTE và điều đó sẽ có tác động trực tiếp đến bảng được tham chiếu đang được sử dụng trong CTE. Hãy lấy một ví dụ để hiểu thực tế này.

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

Trong tuyên bố trên, chúng tôi đang xóa một hàng khỏi CTE_Example và nó sẽ xóa dữ liệu khỏi bảng tham chiếu "DEPT" đang được sử dụng trong CTE.


Tôi vẫn không nhận được điểm. Có gì khác biệt giữa điều này và chỉ xóa khỏi DEPT với cùng một điều kiện? Nó dường như không làm cho mọi thứ dễ dàng hơn.
Holger Jakobs

Xin hãy sửa tôi nếu tôi sai, nhưng kế hoạch thực hiện có thể khác và tôi nghĩ đó là quan điểm của Neeraj, rằng có nhiều cách để đạt được cùng một mục tiêu, nhưng một số sẽ có lợi thế hơn những người khác tùy theo tình huống. Ví dụ, có thể dễ dàng hơn để đọc CTE qua câu lệnh XÓA TỪ trong một số trường hợp, điều ngược lại có thể đúng ở những người khác. Hiệu suất có thể cải thiện hoặc xấu đi. vv
WonderWorker

7

Nó rất hữu ích khi bạn muốn thực hiện "cập nhật theo thứ tự".

MS SQL không cho phép bạn sử dụng ORDER BY với CẬP NHẬT, nhưng với sự trợ giúp của CTE, bạn có thể làm theo cách đó:

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)

Xem ở đây để biết thêm thông tin: Cách cập nhật và đặt hàng bằng cách sử dụng ms sql


0

Một điểm chưa được chỉ ra, là tốc độ . Tôi biết đó là một câu hỏi đã được trả lời cũ, nhưng tôi nghĩ rằng đây xứng đáng nhận xét / trả lời trực tiếp:

Chúng dường như là dư thừa vì điều tương tự có thể được thực hiện với các bảng dẫn xuất

Khi tôi sử dụng CTE, lần đầu tiên tôi hoàn toàn choáng váng vì tốc độ của nó. Đó là một trường hợp giống như từ sách giáo khoa, rất phù hợp với CTE, nhưng trong tất cả các trường đại học tôi từng sử dụng CTE, đã có một tốc độ tăng đáng kể. Truy vấn đầu tiên của tôi rất phức tạp với các bảng dẫn xuất, mất nhiều phút để thực hiện. Với CTE, nó mất vài giây và khiến tôi bị sốc, điều đó thậm chí có thể xảy ra.


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

thử cái này

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.