Sqlserver
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Sqlserver

Hướng dẫn về CTE trong SQL Server

Biểu thức bảng chung hay còn gọi là CTE trong SQL Server cung cấp tập kết quả tạm thời trong T-SQL. Bạn có thể tham khảo nó trong câu lệnh SQL Select, SQL Insert, SQL Delete hoặc SQL Update.

Tùy chọn có sẵn từ SQL Server 2005 trở đi, giúp các nhà phát triển viết các truy vấn phức tạp và dài liên quan đến nhiều JOIN, tổng hợp và lọc dữ liệu. Thông thường, các nhà phát triển sử dụng truy vấn con để viết mã T-SQL và SQL Server lưu trữ tạm thời các CTE này trong bộ nhớ cho đến khi quá trình thực thi truy vấn kết thúc. Sau khi kết thúc truy vấn, nó sẽ bị xóa khỏi bộ nhớ.

CTE trong SQL Server:Cú pháp

WITH <common_table_expression> ([column names])
AS
(
   <query_definition>
)
<operation>
  • Nó sử dụng tên CTE để chỉ nó nhằm thực hiện các câu lệnh Chọn, Chèn, Cập nhật, Xóa hoặc Hợp nhất.
  • Tên các cột được phân tách bằng dấu phẩy. Chúng phải khớp với các cột được xác định trong định nghĩa truy vấn.
  • Định nghĩa truy vấn liên quan đến các câu lệnh chọn từ một bảng hoặc kết hợp giữa nhiều bảng.
  • Bạn có thể tham khảo tên biểu thức CTE để lấy kết quả.

Ví dụ:truy vấn CTE cơ bản sau sử dụng các phần sau:

  • Tên Biểu thức Bảng Chung - Dữ liệu Bán hàng
  • Danh sách cột - [CustomerID], [FirstName], [LastName], [CompanyName], [EmailAddress], [Phone]
  • Định nghĩa truy vấn bao gồm một câu lệnh chọn lấy dữ liệu từ bảng [SalesLT]. [Khách hàng]
  • Phần cuối cùng sử dụng câu lệnh select trên biểu thức CTE và lọc các bản ghi bằng mệnh đề where.
WITH SalesCustomerdata ([CustomerID],[FirstName],[LastName],[CompanyName],[EmailAddress],[Phone])
AS(
SELECT [CustomerID]
      ,[FirstName]
      ,[LastName]
      ,[CompanyName]
      ,[EmailAddress]
      ,[Phone]
   FROM [SalesLT].[Customer] 
)
SELECT * FROM SalesCustomerdata where Firstname like 'Raj%' 
ORDER BY CustomerID desc

Trong một ví dụ khác, chúng tôi tính toán tổng doanh số trung bình từ CTE. Định nghĩa truy vấn bao gồm mệnh đề GROUP BY. Sau đó, chúng tôi sử dụng hàm AVG () để tính giá trị trung bình.

WITH Salesdata ([SalesOrderID],[Total])
AS(
SELECT [SalesOrderID]
         ,count(*) AS total
          FROM [SalesLT].[SalesOrderHeader]
        GROUP BY [SalesOrderID]
)
SELECT avg(total) FROM salesdata

Bạn cũng có thể sử dụng CTE để chèn dữ liệu vào bảng SQL. Định nghĩa truy vấn CTE bao gồm dữ liệu bắt buộc mà bạn có thể tìm nạp từ các bảng hiện có bằng cách sử dụng các phép nối. Sau đó, truy vấn CTE để chèn dữ liệu vào bảng mục tiêu.

Ở đây chúng tôi sử dụng câu lệnh SELECT INTO để tạo một bảng mới có tên [CTETest] từ đầu ra của câu lệnh CTE select.

WITH CTEDataInsert
AS 
(
SELECT
    p.[ProductID]
    ,p.[Name]
    ,pm.[Name] AS [ProductModel]
    ,pmx.[Culture]
    ,pd.[Description]
FROM [SalesLT].[Product] p
    INNER JOIN [SalesLT].[ProductModel] pm
    ON p.[ProductModelID] = pm.[ProductModelID]
    INNER JOIN [SalesLT].[ProductModelProductDescription] pmx
    ON pm.[ProductModelID] = pmx.[ProductModelID]
    INNER JOIN [SalesLT].[ProductDescription] pd
    ON pmx.[ProductDescriptionID] = pd.[ProductDescriptionID]
)
SELECT * INTO CTETest FROM CTEDataInsert
GO

Bạn cũng có thể chỉ định một bảng hiện có khớp với các cột có dữ liệu được chèn vào.

WITH CTEDataInsert
AS 
(
SELECT
    p.[ProductID]
    ,p.[Name]
    ,pm.[Name] AS [ProductModel]
    ,pmx.[Culture]
    ,pd.[Description]
FROM [SalesLT].[Product] p
    INNER JOIN [SalesLT].[ProductModel] pm
    ON p.[ProductModelID] = pm.[ProductModelID]
    INNER JOIN [SalesLT].[ProductModelProductDescription] pmx
    ON pm.[ProductModelID] = pmx.[ProductModelID]
    INNER JOIN [SalesLT].[ProductDescription] pd
    ON pmx.[ProductDescriptionID] = pd.[ProductDescriptionID]
)
INSERT into CTETest select * FROM CTEDataInsert
GO

Bạn cũng có thể cập nhật hoặc xóa các bản ghi trong bảng SQL bằng cách sử dụng biểu thức bảng chung. Các truy vấn sau sử dụng câu lệnh DELETE và UPDATE với CTE.

Cập nhật tuyên bố trong CTE

WITH Salesdata ([SalesOrderID],[Freight])
AS(
SELECT [SalesOrderID]
         ,[Freight]
          FROM [SalesLT].[SalesOrderHeader]
        )
UPDATE SalesData SET [Freight]=100.00 WHERE [SalesOrderID]=71774
Go

Xóa câu lệnh trong CTE

WITH Salesdata ([SalesOrderID],[Freight])
AS(
SELECT [SalesOrderID]
         ,[Freight]
          FROM [SalesLT].[SalesOrderHeader]
        )
delete SalesData  WHERE [SalesOrderID]=71774
GO
SELECT * FROM [SalesLT].[SalesOrderHeader] WHERE SalesOrderID=71774

Nhiều CTE

Bạn có thể khai báo nhiều CTE trong tập lệnh T-SQL và sử dụng các phép toán nối trên chúng. Đối với nhiều CTE, T-SQL sử dụng dấu phẩy làm dấu phân tách.

Trong truy vấn sau, chúng tôi có hai CTE:

  1. CTESales
  2. CTESalesDescription

Sau đó, trong câu lệnh select, chúng tôi lấy kết quả bằng cách sử dụng INNER JOIN trên cả hai CTE.

WITH CTESales
AS 
(
SELECT
     p.[ProductID]
    ,p.[Name]
    ,pm.[Name] AS [ProductModel]
    ,pmx.[Culture]
    ,pmx.[ProductDescriptionID]
   FROM [SalesLT].[Product] p
    INNER JOIN [SalesLT].[ProductModel] pm
    ON p.[ProductModelID] = pm.[ProductModelID]
    INNER JOIN [SalesLT].[ProductModelProductDescription] pmx
    ON pm.[ProductModelID] = pmx.[ProductModelID]
    INNER JOIN [SalesLT].[ProductDescription] pd
    ON pmx.[ProductDescriptionID] = pd.[ProductDescriptionID]
),CTESalesDescription

AS (

SELECT  description AS describe,[ProductDescriptionID]
from [SalesLT].[ProductDescription]  
)

SELECT  productid, [Name],[ProductModel],describe
FROM CTESales 
INNER JOIN CTESalesDescription 
    ON 
CTESales.[ProductDescriptionID] = CTESalesDescription.[ProductDescriptionID]

Biểu thức bảng chung đệ quy

CTE đệ quy chạy trong một vòng lặp thủ tục lặp lại cho đến khi điều kiện thỏa mãn. Ví dụ T-SQL sau sử dụng bộ đếm ID và chọn các bản ghi cho đến khi điều kiện WHERE được thỏa mãn.

Declare @ID int =1;
;with RecursiveCTE as  
   (  
      SELECT @ID as ID
        UNION ALL  
      SELECT  ID+ 1
  FROM  RecursiveCTE  
  WHERE ID <5
    )  
 
SELECT * FROM RecursiveCTE

Một cách sử dụng khác của CTE đệ quy trong SQL Server là để hiển thị dữ liệu phân cấp. Giả sử rằng chúng ta có nhân viên và nó có các bản ghi cho tất cả nhân viên, bộ phận của họ và ID của người quản lý của họ.

--Script Reference: Microsoft Docs

CREATE TABLE dbo.MyEmployees  
(  
EmployeeID SMALLINT NOT NULL,  
FirstName NVARCHAR(30)  NOT NULL,  
LastName  NVARCHAR(40) NOT NULL,  
Title NVARCHAR(50) NOT NULL,  
DeptID SMALLINT NOT NULL,  
ManagerID INT NULL,  
 CONSTRAINT PK_EmployeeID PRIMARY KEY CLUSTERED (EmployeeID ASC)   
);  
INSERT INTO dbo.MyEmployees VALUES   
 (1, N'Ken', N'Sánchez', N'Chief Executive Officer',16,NULL)  
,(273, N'Brian', N'Welcker', N'Vice President of Sales',3,1)  
,(274, N'Stephen', N'Jiang', N'North American Sales Manager',3,273)  
,(275, N'Michael', N'Blythe', N'Sales Representative',3,274)  
,(276, N'Linda', N'Mitchell', N'Sales Representative',3,274)  
,(285, N'Syed', N'Abbas', N'Pacific Sales Manager',3,273)  
,(286, N'Lynn', N'Tsoflias', N'Sales Representative',3,285)  
,(16,  N'David',N'Bradley', N'Marketing Manager', 4, 273)  
,(23,  N'Mary', N'Gibson', N'Marketing Specialist', 4, 16);

Bây giờ, chúng ta cần tạo dữ liệu phân cấp nhân viên. Chúng ta có thể sử dụng CTE đệ quy với UNION ALL trong câu lệnh select.

WITH DirectReports(Name, Title, EmployeeID, EmployeeLevel, Sort)  
AS (SELECT CONVERT(VARCHAR(255), e.FirstName + ' ' + e.LastName),  
        e.Title,  
        e.EmployeeID,  
        1,  
        CONVERT(VARCHAR(255), e.FirstName + ' ' + e.LastName)  
    FROM dbo.MyEmployees AS e  
    WHERE e.ManagerID IS NULL  
    UNION ALL  
    SELECT CONVERT(VARCHAR(255), REPLICATE ('|    ' , EmployeeLevel) +  
        e.FirstName + ' ' + e.LastName),  
        e.Title,  
        e.EmployeeID,  
        EmployeeLevel + 1,  
        CONVERT (VARCHAR(255), RTRIM(Sort) + '|    ' + FirstName + ' ' +   
                 LastName)  
    FROM dbo.MyEmployees AS e  
    JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID  
    )  
SELECT EmployeeID, Name, Title, EmployeeLevel  
FROM DirectReports   
ORDER BY Sort;

CTE trả về chi tiết cấp độ nhân viên như được hiển thị bên dưới.

Những điểm quan trọng về biểu thức bảng chung

  • Chúng tôi không thể sử dụng lại CTE. Phạm vi của nó được giới hạn trong các câu lệnh SELECT, INSERT, UPDATE hoặc MERGE bên ngoài.
  • Bạn có thể sử dụng nhiều CTES; tuy nhiên, họ nên sử dụng các toán tử UNION ALL, UNION, INTERSECT hoặc EXCERPT.
  • Chúng tôi có thể xác định nhiều định nghĩa truy vấn CTE trong CTE không đệ quy.
  • Chúng tôi không thể sử dụng mệnh đề ORDER BY (không có TOP), INTO, OPTIONS với gợi ý truy vấn và FOR BROWSE trong định nghĩa truy vấn CTE.

Ví dụ:tập lệnh dưới đây sử dụng mệnh đề ORDER BY mà không có mệnh đề TOP.

WITH CTEDataInsert
AS 
(
SELECT
    p.[ProductID]
    ,p.[Name]
    ,pm.[Name] AS [ProductModel]
    ,pmx.[Culture]
    ,pd.[Description]
FROM [SalesLT].[Product] p
    INNER JOIN [SalesLT].[ProductModel] pm
    ON p.[ProductModelID] = pm.[ProductModelID]
    INNER JOIN [SalesLT].[ProductModelProductDescription] pmx
    ON pm.[ProductModelID] = pmx.[ProductModelID]
    INNER JOIN [SalesLT].[ProductDescription] pd
    ON pmx.[ProductDescriptionID] = pd.[ProductDescriptionID]
    ORDER BY productid
)
select * FROM CTEDataInsert 
GO

Nó đưa ra lỗi sau:

  • Chúng tôi không thể tạo chỉ mục trên CTE.
  • Tên cột được xác định trong CTE phải khớp với các cột được trả về trong câu lệnh select.

CTE không có cột [Điện thoại] trong mã bên dưới trong khi câu lệnh select trả về giá trị của nó. Do đó, bạn nhận được thông báo lỗi được đánh dấu.

  • Nếu bạn có nhiều câu lệnh trong tập lệnh T-SQL, câu lệnh trước đó trước CTE sẽ kết thúc bằng dấu chấm phẩy.

Ví dụ:câu lệnh select đầu tiên không bao gồm dấu chấm phẩy. Do đó, bạn gặp phải lỗi cú pháp không chính xác trong tập lệnh CTE.

Tập lệnh hoạt động tốt nếu chúng ta kết thúc câu lệnh chọn đầu tiên bằng toán tử dấu chấm phẩy.

  • Chúng tôi không thể sử dụng một cột trùng lặp trong câu lệnh select nếu chúng tôi không khai báo tên cột bên ngoài.

Ví dụ:định nghĩa CTE sau chỉ định cột trùng lặp [Điện thoại]. Nó trả về một lỗi.

Tuy nhiên, nếu bạn xác định các cột bên ngoài, nó sẽ không gây ra lỗi. Nó được yêu cầu khi bạn cần một cột duy nhất nhiều lần trong đầu ra cho các phép tính khác nhau.

Quan trọng:Biểu thức bảng chung (CTE) không thay thế cho bảng tạm thời hoặc biến bảng.

  • Các bảng tạm thời được tạo trong TempDB và chúng ta có thể xác định các ràng buộc chỉ mục tương tự như một bảng thông thường. Chúng tôi không thể tham chiếu bảng tạm thời nhiều lần trong một phiên
  • Các biến bảng cũng tồn tại trong TempDB và hoạt động giống như các biến tồn tại trong quá trình thực thi hàng loạt. Chúng tôi không thể xác định chỉ mục trên các biến bảng.
  • CTE dành cho một mục đích tham chiếu duy nhất và chúng tôi không thể xác định chỉ mục trên đó. Nó tồn tại trong bộ nhớ và bị loại bỏ sau khi tham chiếu được thực hiện.

Kết luận

Biểu thức bảng chung (CTE) cho phép các nhà phát triển viết mã rõ ràng và hiệu quả. Nói chung, bạn có thể sử dụng CTE khi bạn không yêu cầu nhiều tham chiếu như một bảng tạm thời và chúng tôi đã khám phá nhiều trường hợp khác nhau cho câu lệnh SELECT, INSERT, UPDATE, DETELTE và các CTE đệ quy.

Với sự trợ giúp của các công cụ hiện đại, chẳng hạn như SQL Complete SSMS Add-in, việc xử lý CTE thậm chí còn trở nên dễ dàng hơn. Phần bổ trợ có thể gợi ý CTE khi đang di chuyển, do đó làm cho các nhiệm vụ liên quan đến CTE trở nên đơn giản hơn nhiều. Ngoài ra, hãy tham khảo tài liệu của Microsoft để biết thêm chi tiết về CTE.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Phương pháp tốt nhất để chèn bản ghi nếu bản ghi đó chưa tồn tại là gì?

  2. Điều gì xảy ra nếu bạn không thực hiện một giao dịch với cơ sở dữ liệu (chẳng hạn như SQL Server)?

  3. Câu lệnh SQL Server RAISERROR với các ví dụ đơn giản

  4. Trả lại tất cả các hàng từ một phân vùng cụ thể trong SQL Server (T-SQL)

  5. Cách kiểm tra cài đặt ANSI_NULLS của phiên trong SQL Server