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

Mọi thứ bạn cần biết về SQL CTE tại một điểm

Lần đầu tiên Karl nghe đến SQL Server CTE là khi anh ấy đang tìm kiếm thứ gì đó để làm cho mã SQL của mình dễ nhìn hơn. Thật là đau đầu khi bạn nhìn vào nó. Anton, đồng nghiệp quan tâm của anh ấy, đã hỏi anh ấy về CTE. Karl nghĩ Anton đang ám chỉ đến cơn đau đầu của mình. Có lẽ anh ấy đã nghe nhầm tất cả, nên anh ấy trả lời, "Tất nhiên là không." Điều buồn cười là, anh ấy đang đề cập đến Bệnh não chấn thương mãn tính, cũng là một bệnh CTE - một bệnh thoái hóa thần kinh do chấn thương đầu lặp đi lặp lại. Nhưng dựa trên phản ứng của Karl, Anton biết chắc chắn rằng đồng nghiệp của anh ấy không biết gì về những gì anh ấy đang nói.

Thật là một cách điên rồ để giới thiệu CTE! Vì vậy, trước khi bạn vào chung một con thuyền, hãy cùng làm rõ, SQL CTE hoặc Biểu thức bảng thông dụng trong thế giới SQL là gì?

Bạn có thể đọc những điều cơ bản ở đây. Trong khi đó, chúng ta sẽ tìm hiểu thêm một chút về những gì đã xảy ra trong câu chuyện bất thường này.

4 Nội dung cơ bản về CTE trong SQL Server

“CTE SQL có tên”

Anton bắt đầu với ý tưởng rằng SQL CTE tạm thời được đặt tên là tập kết quả. Là phương tiện tạm thời, CTE bị giới hạn về phạm vi.

"Vì vậy, nó giống như một truy vấn phụ?" Karl hỏi.

“Theo một cách nào đó, có. Nhưng bạn không thể đặt tên cho một truy vấn phụ, ”Anton nói. “Một CTE có một cái tên giống như một bảng có tên. Tuy nhiên, thay vì CREATE, bạn sử dụng WITH để tạo nó. ” Sau đó, anh ấy viết cú pháp trên giấy:

WITH <cte_name>(<column list>)
AS
(
<inner query defining the CTE>
)
<outer query against CTE>

“CTE thành công khi CHỌN xong”

Anton tiếp tục bằng cách giải thích phạm vi của SQL CTE.

“Một bảng tạm thời có thể tồn tại trong phạm vi của quy trình hoặc trên toàn cầu. Nhưng CTE sẽ biến mất khi CHỌN hoàn thành, ”anh ấy nói bằng một vần điệu. “Điều tương tự nếu bạn sử dụng nó để CHÈN, CẬP NHẬT hoặc XÓA,” anh ấy tiếp tục.

“Bạn không thể sử dụng lại nó”

“Không giống như một chế độ xem hoặc một bảng tạm thời, bạn không thể sử dụng lại SQL CTE. Tên ở đó, vì vậy bạn có thể tham khảo nó trong truy vấn bên trong và bên ngoài. Nhưng đó là tất cả, ”Anton nói.

"Vậy, vấn đề lớn về CTE SQL là gì?" Karl hỏi.

“Bạn có thể làm cho mã của mình dễ đọc hơn”

"Vấn đề lớn?" Anton trả lời câu hỏi. “Đó là bạn có thể làm cho mã của mình dễ đọc. Đó không phải là những gì bạn đang tìm kiếm? ”

“Đúng vậy,” Karl thừa nhận.

Vậy, bước hợp lý tiếp theo mà Karl phải làm là gì?

Thông tin bổ sung về CTE trong SQL

Ngày hôm sau, Karl tiếp tục tìm kiếm SQL CTE. Ngoài những điều trên, đây là những gì anh ấy tìm thấy:

  • SQL CTE có thể không đệ quy hoặc đệ quy.
  • Không chỉ SQL Server mà MySQL và Oracle cũng hỗ trợ ý tưởng này. Trên thực tế, nó là một phần của đặc tả SQL-99.
  • Mặc dù nó được sử dụng để đơn giản hóa mã SQL, nhưng nó không cải thiện hiệu suất.
  • Và nó cũng sẽ không thay thế các truy vấn phụ và bảng tạm thời. Mỗi thứ đều có vị trí và công dụng của nó.

Tóm lại, đó là một cách khác để diễn đạt truy vấn .

Nhưng Karl muốn biết thêm thông tin chi tiết, vì vậy anh ấy tiếp tục tìm kiếm những gì sẽ hoạt động, những gì sẽ không và nó sẽ hoạt động như thế nào so với truy vấn con và bảng tạm thời.

Điều gì sẽ hoạt động trong SQL Server CTE?

Tìm hiểu sâu hơn để làm sáng tỏ thêm về CTE, Karl đã liệt kê bên dưới những gì SQL Server sẽ chấp nhận. Hãy xem qua quá trình học tập của anh ấy.

Chỉ định bí danh cột trong dòng hoặc cột bên ngoài

SQL CTE hỗ trợ hai hình thức gán bí danh cột. Dạng đầu tiên là dạng inline, như ví dụ bên dưới:

-- Use an Inline column alias

USE AdventureWorks
GO;

WITH Sales_CTE
AS  
(  
	SELECT SalesPersonID, COUNT(*) AS NumberOfOrders
	FROM Sales.SalesOrderHeader  
	WHERE SalesPersonID IS NOT NULL  
	GROUP BY SalesPersonID  
)
SELECT
 a.SalesPersonID
,a.NumberOfOrders
FROM Sales_CTE a

Đoạn mã trên sử dụng bí danh cột trong định nghĩa CTE khi nó được gán trong câu lệnh SELECT. Bạn có nhận thấy COUNT (*) AS NumberOfOrders ? Đó là biểu mẫu nội tuyến.

Bây giờ, một ví dụ khác là biểu mẫu bên ngoài:

-- Use an external column alias

USE AdventureWorks
GO;

WITH Sales_CTE(SalesPersonID, NumberOfOrders) 
AS  
(  
	SELECT SalesPersonID, COUNT(*)
	FROM Sales.SalesOrderHeader  
	WHERE SalesPersonID IS NOT NULL  
	GROUP BY SalesPersonID  
)
SELECT
 a.SalesPersonID
,a.NumberOfOrders
FROM Sales_CTE a

Các cột cũng có thể được xác định trong ngoặc đơn sau khi đặt tên CTE. Lưu ý WITH Sales_CTE (SalesPersonID, NumberOfOrders) .

CTE trong SQL Đứng trước CHỌN, CHÈN, CẬP NHẬT hoặc XÓA

Mục tiếp theo này là về việc sử dụng CTE. Ví dụ đầu tiên và phổ biến là khi nó đứng trước câu lệnh SELECT.

-- List down all Salespersons with their all-time number of orders
USE AdventureWorks
GO;

WITH Sales_CTE (SalesPersonID, NumberOfOrders)  
AS  
(  
	SELECT SalesPersonID, COUNT(*)  
	FROM Sales.SalesOrderHeader  
	WHERE SalesPersonID IS NOT NULL  
	GROUP BY SalesPersonID  
)
SELECT
 a.SalesPersonID
,CONCAT(P.LastName,', ',P.FirstName,' ',P.MiddleName) AS SalesPerson
,a.NumberOfOrders
FROM Sales_CTE a
INNER JOIN Person.Person p ON a.SalesPersonID = p.BusinessEntityID

Ví dụ này cho thấy gì?

  • Bán hàng_CTE - tên của CTE.
  • (SalesPersonID, NumberOfOrders) - định nghĩa của các cột CTE.
  • CHỌN SalesPersonID, COUNT (*) TỪ Sales.SalesOrderHeader TRONG ĐÓ SalesPersonID KHÔNG PHẢI LÀ NHÓM ĐẦY ĐỦ BỞI SalesPersonID - SELECT bên trong xác định CTE.
  • CHỌN a.SalesPersonID, CONCAT (P.LastName, ’,‘, P.FirstName, ’‘, P.MiddleName) AS SalesPerson - truy vấn bên ngoài sử dụng CTE. Ví dụ này sử dụng một SELECT để sử dụng CTE.
  • FROM Sales_CTE a - tham chiếu của truy vấn bên ngoài tới CTE.

Bên cạnh CHỌN, nó cũng hoạt động với CHÈN, CẬP NHẬT và XÓA. Dưới đây là một ví dụ về việc sử dụng INSERT:

-- add a 10% increase to Employee 16 after 1 year from the previous increase.
USE AdventureWorks
GO;

WITH LatestEmployeePay
AS
(
    SELECT TOP 1
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    ,eph.PayFrequency
    FROM HumanResources.EmployeePayHistory eph 
    WHERE eph.BusinessEntityID = 16
    ORDER BY eph.RateChangeDate DESC
)
INSERT INTO HumanResources.EmployeePayHistory
SELECT
 BusinessEntityID
,DATEADD(d,365,RateChangeDate)
,(Rate * 0.1) + Rate
,PayFrequency
,GETDATE()
FROM LatestEmployeePay

Trong danh sách trên, CTE truy xuất khoản thanh toán mới nhất cho nhân viên 16. Tập hợp kết quả của CTE sau đó được sử dụng để chèn một bản ghi mới trong EmployeePayHistory . Karl đã ghi lại những phát hiện của mình một cách trang nhã. Ngoài ra, anh ấy còn sử dụng các ví dụ phù hợp.

Xác định Nhiều CTE trong 1 Truy vấn

Đúng rồi. Karl nhận thấy rằng có thể có nhiều CTE trong 1 truy vấn. Đây là một ví dụ:

-- Get the present and previous rate of employee 16
USE AdventureWorks
GO;

WITH LatestEmployeePay
AS
(
    SELECT TOP 1
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    FROM HumanResources.EmployeePayHistory eph 
    WHERE eph.BusinessEntityID = 16
    ORDER BY eph.RateChangeDate DESC
),
PreviousEmployeePay AS
(
    SELECT TOP 1
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    FROM HumanResources.EmployeePayHistory eph
    INNER JOIN LatestEmployeePay lep 
      ON eph.BusinessEntityID = lep.BusinessEntityID
    WHERE eph.BusinessEntityID = 16
      AND eph.RateChangeDate < lep.RateChangeDate
    ORDER BY eph.RateChangeDate DESC
)
SELECT
 a.BusinessEntityID
,a.Rate
,a.RateChangeDate
,b.Rate AS PreviousRate
FROM LatestEmployeePay a
INNER JOIN PreviousEmployeePay b 
    ON a.BusinessEntityID = b.BusinessEntityID

Đoạn mã trên sử dụng 2 CTE trong một truy vấn, đó là LatestEpriseePay Trước đó .

Tham khảo CTE Nhiều lần

Còn nhiều hơn nữa đối với ví dụ trước. Cũng lưu ý rằng bạn có thể INNER THAM GIA CTE đầu tiên đến CTE thứ hai. Cuối cùng, truy vấn bên ngoài có thể kết hợp cả 2 CTE. Mới nhất đã được nhắc đến hai lần.

Chuyển đối số tới CTE SQL

Các đối số, chẳng hạn như các biến, có thể được chuyển cùng với một CTE:

DECLARE @SalesPersonID INT = 275;

WITH Sales_CTE
AS  
(  
	SELECT SalesPersonID, COUNT(*) AS NumberOfOrders
	FROM Sales.SalesOrderHeader 
	WHERE SalesPersonID = @SalesPersonID  
	GROUP BY SalesPersonID  
)  
SELECT SalesPersonID, NumberOfOrders
FROM Sales_CTE

Đoạn mã trên bắt đầu bằng cách khai báo và đặt một biến @ SalesPersonID . Giá trị sau đó được chuyển đến CTE để lọc kết quả.

Sử dụng trong CURSOR

Con trỏ SQL có thể sử dụng câu lệnh SELECT và lặp qua các kết quả. Ngoài ra, một CTE SQL có thể được sử dụng với nó:

DECLARE @SalesPersonID INT
DECLARE @NumberofOrders INT

DECLARE sales_cursor CURSOR FOR
    WITH Sales_CTE (SalesPersonID, NumberOfOrders)  
	AS  
	(  
		SELECT SalesPersonID, COUNT(*)  
		FROM Sales.SalesOrderHeader  
		WHERE SalesPersonID IS NOT NULL  
		GROUP BY SalesPersonID  
	)  
	SELECT salespersonid, numberoforders
	FROM Sales_CTE; 
OPEN sales_cursor
FETCH NEXT FROM sales_cursor INTO @SalesPersonID, @NumberofOrders
WHILE @@FETCH_STATUS = 0  
BEGIN
	PRINT 'SalesPersonID: ' + CAST(@SalesPersonID AS VARCHAR)
	PRINT '# of Orders: ' + CAST(@NumberofOrders AS VARCHAR)
	FETCH NEXT FROM sales_cursor  INTO @SalesPersonID, @NumberofOrders
END
CLOSE sales_cursor
DEALLOCATE sales_cursor;

Sử dụng Bảng tạm thời trong CTE đệ quy

CTE đệ quy sử dụng một thành viên neo và một thành viên đệ quy trong định nghĩa CTE. Nó giúp nhận phân cấp trong một bảng. SQL CTE cũng có thể sử dụng một bảng tạm thời cho mục đích này. Xem ví dụ bên dưới:

-- Create a Crew table.  
CREATE TABLE #EnterpriseDSeniorOfficers  
(  
CrewID SMALLINT NOT NULL,  
FirstName NVARCHAR(30)  NOT NULL,  
LastName  NVARCHAR(40) NOT NULL,  
CrewRank NVARCHAR(50) NOT NULL,  
HigherRankID INT NULL,  
 CONSTRAINT PK_CrewID PRIMARY KEY CLUSTERED (CrewID ASC)   
);  
-- Populate the table with values.  
INSERT INTO #EnterpriseDSeniorOfficers VALUES   
 (1, N'Jean-Luc', N'Picard', N'Captain',NULL)  
,(2, N'William', N'Riker', N'First Officer',1)  
,(3, N'Data', N'', N'Second Officer',1)  
,(4, N'Worf', N'', N'Chief of Security',1)  
,(5, N'Deanna', N'Troi', N'Ship Counselor',1)  
,(6, N'Beveryly', N'Crusher', N'Chief Medical Officer',1)  
,(7, N'Geordi', N'LaForge', N'Chief Engineer',1);  

WITH DirectReports(HigherRankID, CrewID, Title, CrewLevel) AS   
(  
    SELECT HigherRankID, CrewID, CrewRank, 0 as CrewLevel
    FROM #EnterpriseDSeniorOfficers
    WHERE HigherRankID IS NULL  
    UNION ALL  
    SELECT e.HigherRankID, e.CrewID, e.CrewRank, CrewLevel + 1  
    FROM #EnterpriseDSeniorOfficers AS e  
        INNER JOIN DirectReports AS d  
        ON e.HigherRankID = d.CrewID   
)  
SELECT HigherRankID, CrewID, Title, CrewLevel   
FROM DirectReports  
OPTION (MAXRECURSION 2)
ORDER BY HigherRankID;  

DROP TABLE #EnterpriseDSeniorOfficers

Karl giải thích bằng cách mổ xẻ CTE này. Đây là cách nó diễn ra.

Thành viên neo là câu lệnh SELECT đầu tiên với cấp độ phi hành đoàn không (0):

SELECT HigherRankID, CrewID, CrewRank, 0 as CrewLevel
 FROM #EnterpriseDSeniorOfficers
 WHERE HigherRankID IS NULL

Thành viên neo này nhận được nút gốc của hệ thống phân cấp. Mệnh đề WHERE chỉ định rằng cấp gốc ( HigherRankID LÀ NULL ).

Phần tử đệ quy sẽ lấy các nút con được trích xuất bên dưới:

SELECT e.HigherRankID, e.CrewID, e.CrewRank, CrewLevel + 1  
FROM #EnterpriseDSeniorOfficers AS e  
INNER JOIN DirectReports AS d  
        ON e.HigherRankID = d.CrewID

Ngoài ra còn có một TÙY CHỌN (MAXRECURSION 2) được sử dụng trong truy vấn bên ngoài. CTE đệ quy có thể trở nên có vấn đề khi một vòng lặp vô hạn kết quả từ truy vấn đệ quy. MAXRECURSION 2 tránh được sự lộn xộn này - nó giới hạn vòng lặp chỉ ở 2 lần đệ quy.

Điều này kết thúc danh sách của Karl về những gì sẽ hoạt động. Tuy nhiên, không phải mọi thứ chúng ta nghĩ đến đều có thể hoạt động. Phần tiếp theo sẽ thảo luận về những phát hiện của Karl về những điều này.

Điều gì không hoạt động trong SQL CTE?

Ở đây, chúng tôi có danh sách những thứ sẽ tạo ra lỗi khi sử dụng SQL CTE.

Không có dấu chấm phẩy đặt trước SQL CTE

Nếu có một câu lệnh trước CTE, thì câu lệnh đó phải được kết thúc bằng dấu chấm phẩy. Mệnh đề WITH có thể hoạt động cho các mục đích khác như trong gợi ý bảng, do đó, dấu chấm phẩy sẽ loại bỏ sự mơ hồ. Câu lệnh được cung cấp bên dưới sẽ gây ra lỗi:

DECLARE @SalesPersonID INT

SET @SalesPersonID = 275

WITH Sales_CTE
AS  
(  
	SELECT SalesPersonID, COUNT(*) AS NumberOfOrders
	FROM Sales.SalesOrderHeader 
	WHERE SalesPersonID = @SalesPersonID  
	GROUP BY SalesPersonID  
)  
SELECT SalesPersonID, NumberOfOrders
FROM Sales_CTE

Những người lần đầu tiên sử dụng để không kết thúc câu lệnh bằng dấu chấm phẩy gặp phải lỗi này:

Cột không tên

“Quên đặt bí danh cột? Sau đó, bạn đang gặp phải một lỗi khác. " Karl đã nói điều này trong bài báo của mình và cũng cung cấp mã mẫu mà tôi chia sẻ bên dưới:

DECLARE @SalesPersonID INT

SET @SalesPersonID = 275;

WITH Sales_CTE
AS  
(  
	SELECT SalesPersonID, COUNT(*)
	FROM Sales.SalesOrderHeader 
	WHERE SalesPersonID = @SalesPersonID  
	GROUP BY SalesPersonID  
)  
SELECT SalesPersonID, NumberOfOrders
FROM Sales_CTE

Sau đó, hãy xem thông báo lỗi:

Tên cột trùng lặp

Một lỗi khác liên quan đến # 2 ở trên là sử dụng cùng một tên cột trong CTE. Bạn có thể sử dụng nó trong một câu lệnh SELECT bình thường, nhưng không phải với CTE. Karl có một ví dụ khác:

WITH Sales_CTE
AS  
(  
	SELECT SalesPersonID AS col1, COUNT(*) AS col1
	FROM Sales.SalesOrderHeader 
	GROUP BY SalesPersonID  
)  
SELECT *
FROM Sales_CTE

ĐẶT HÀNG THEO mệnh đề không có ĐẦU hoặc TẮT-FETCH

SQL chuẩn không cho phép ORDER BY trong biểu thức bảng khi chúng ta sử dụng nó để sắp xếp các tập kết quả. Tuy nhiên, nếu TOP hoặc OFFSET-FETCH được sử dụng, ORDER BY sẽ trở thành một công cụ hỗ trợ lọc.

Đây là ví dụ của Karl bằng cách sử dụng ORDER BY:

WITH LatestEmployeePay
AS
(
    SELECT
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    ,eph.PayFrequency
    FROM HumanResources.EmployeePayHistory eph 
    WHERE eph.BusinessEntityID = 16
    ORDER BY eph.RateChangeDate DESC
)
INSERT INTO HumanResources.EmployeePayHistory
SELECT
 BusinessEntityID
,DATEADD(d,365,RateChangeDate)
,(Rate * 0.1) + Rate
,PayFrequency
,GETDATE()
FROM LatestEmployeePay

Lưu ý rằng nó giống với ví dụ mà chúng ta đã có trước đó, nhưng lần này, TOP không được chỉ định. Kiểm tra lỗi:

Số lượng cột không giống như định nghĩa danh sách cột

Karl đã sử dụng cùng một ví dụ CTE đệ quy, nhưng anh ấy đã lấy ra một cột trong phần tử neo:

WITH DirectReports(HigherRankID, CrewID, Title, CrewLevel) AS   
(  
    SELECT HigherRankID, CrewID
    FROM #EnterpriseDSeniorOfficers
    WHERE HigherRankID IS NULL  
    UNION ALL  
    SELECT e.HigherRankID, e.CrewID, e.CrewRank, CrewLevel + 1  
    FROM #EnterpriseDSeniorOfficers AS e  
        INNER JOIN DirectReports AS d  
        ON e.HigherRankID = d.CrewID   
)  
SELECT HigherRankID, CrewID, Title, CrewLevel   
FROM DirectReports  
ORDER BY HigherRankID;

Đoạn mã trên sử dụng danh sách ba cột với biểu mẫu bên ngoài, nhưng thành viên liên kết chỉ có 2 cột. Nó không được phép. Việc mắc lỗi như thế này sẽ gây ra lỗi:

Những thứ khác không được phép trong CTE

Ngoài danh sách trên, đây là một số phát hiện khác của Karl gây ra lỗi nếu bạn sử dụng nhầm trong CTE SQL.

  • Sử dụng mệnh đề SELECT INTO, OPTION với gợi ý truy vấn và sử dụng FOR BROWSE.
  • Dữ liệu và kiểu khác nhau trong các cột thành viên cố định so với các cột thành viên đệ quy.
  • Có các từ khóa sau trong thành viên đệ quy của CTE đệ quy:
    • HÀNG ĐẦU
    • OUTER JOIN (Nhưng INNER JOIN được phép)
    • NHÓM THEO và CÓ
    • Truy vấn con
    • CHỌN DISTINCT
  • Sử dụng tính năng tổng hợp vô hướng.
  • Sử dụng truy vấn con trong một thành viên đệ quy.
  • Có các CTE SQL được lồng vào nhau.

SQL CTE so với Bảng tạm thời so với Truy vấn con

Đôi khi, bạn có thể viết lại CTE SQL bằng truy vấn con. Ngoài ra, đôi khi, bạn có thể chia nhỏ một CTE SQL bằng cách sử dụng các bảng tạm thời vì lý do hiệu suất. Giống như bất kỳ truy vấn nào khác, bạn cần kiểm tra Kế hoạch thực thi thực tế và IO THỐNG KÊ để biết nên thực hiện tùy chọn nào. SQL CTE có thể thân thiện với mắt, nhưng nếu bạn gặp phải rào cản về hiệu suất, hãy sử dụng tùy chọn khác . Không có lựa chọn nào nhanh hơn lựa chọn kia.

Chúng ta hãy xem xét ba truy vấn từ các bài báo của Karl mang lại kết quả tương tự. Một sử dụng SQL CTE, một sử dụng truy vấn con và truy vấn thứ ba sử dụng một bảng tạm thời. Để đơn giản, Karl đã sử dụng một tập hợp kết quả nhỏ.

Bộ mã và kết quả

Nó bắt đầu bằng cách sử dụng nhiều CTE SQL.

WITH LatestEmployeePay
AS
(
    SELECT TOP 1
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    FROM HumanResources.EmployeePayHistory eph 
    WHERE eph.BusinessEntityID = 16
    ORDER BY eph.RateChangeDate DESC
),
PreviousEmployeePay AS
(
    SELECT TOP 1
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    FROM HumanResources.EmployeePayHistory eph
    INNER JOIN LatestEmployeePay lep 
        ON eph.BusinessEntityID = lep.BusinessEntityID
    WHERE eph.BusinessEntityID = 16
        AND eph.RateChangeDate < lep.RateChangeDate
    ORDER BY eph.RateChangeDate DESC
)
SELECT
 a.BusinessEntityID
,a.Rate
,a.RateChangeDate
,b.Rate AS PreviousRate
FROM LatestEmployeePay a
INNER JOIN PreviousEmployeePay b 
    ON a.BusinessEntityID = b.BusinessEntityID

Tiếp theo là một truy vấn con. Như bạn nhận thấy, CTE trông có vẻ mô-đun và có thể đọc được, nhưng truy vấn con bên dưới ngắn hơn:

SELECT TOP 1
 eph.BusinessEntityID
,eph.Rate
,eph.RateChangeDate
,(SELECT TOP 1 eph1.Rate FROM HumanResources.EmployeePayHistory eph1
  WHERE eph1.BusinessEntityID=16
    AND eph1.RateChangeDate < eph.RateChangeDate
  ORDER BY eph1.RateChangeDate DESC) AS PreviousRate
FROM HumanResources.EmployeePayHistory eph
WHERE eph.BusinessEntityID = 16
ORDER BY eph.RateChangeDate DESC;

Karl cũng cố gắng chia nó thành các đoạn mã nhỏ và sau đó kết hợp các kết quả bằng cách sử dụng các bảng tạm thời.

SELECT TOP 1
 eph.BusinessEntityID
,eph.RateChangeDate
,eph.Rate
INTO #LatestPay
FROM HumanResources.EmployeePayHistory eph 
WHERE eph.BusinessEntityID = 16
ORDER BY eph.RateChangeDate DESC

SELECT TOP 1
 eph.BusinessEntityID
,eph.RateChangeDate
,eph.Rate
INTO #PreviousPay
FROM HumanResources.EmployeePayHistory eph
INNER JOIN #LatestPay lep 
    ON eph.BusinessEntityID = lep.BusinessEntityID
WHERE eph.BusinessEntityID = 16
    AND eph.RateChangeDate < lep.RateChangeDate
ORDER BY eph.RateChangeDate DESC

SELECT
 a.BusinessEntityID
,a.Rate
,a.RateChangeDate
,b.Rate AS PreviousRate
FROM #LatestPay a
INNER JOIN #PreviousPay b 
    ON a.BusinessEntityID = b.BusinessEntityID

DROP TABLE #LatestPay
DROP TABLE #PreviousPay

Hãy xem ba cách này dẫn đến kết quả nhận được tiền lương hiện tại và trước đây cho Nhân viên 16. Chúng giống nhau:

Các bài đọc logic

Điều gì tiêu tốn nhiều tài nguyên SQL Server nhất? Hãy xem IO THỐNG KÊ. Karl đã sử dụngatisticsparser.com để định dạng kết quả độc đáo - tốt cho chúng tôi.

Hình 7 cho thấy các lần đọc logic của việc sử dụng CTE so với sử dụng truy vấn con:

Tiếp theo, hãy xem tổng số lần đọc logic khi chia mã thành các đoạn nhỏ bằng cách sử dụng các bảng tạm thời:

Vì vậy, những gì tiêu tốn nhiều tài nguyên hơn? Karl đã xếp hạng chúng cho rõ ràng.

  1. Truy vấn con - 4 lần đọc logic (CHIẾN THẮNG!).
  2. SQL CTE - 6 lần đọc logic.
  3. Bảng tạm thời - 8 lần đọc logic.

Trong ví dụ này, nhanh nhất sẽ là truy vấn con.

Kế hoạch Thực thi Thực tế

Để hiểu rõ về các phép đọc logic mà chúng tôi nhận được từ IO THỐNG KÊ, Karl cũng đã kiểm tra Kế hoạch thực thi thực tế. Anh ấy bắt đầu từ CTE:

Chúng ta có thể quan sát một số điều từ kế hoạch này:

  • Mới nhất CTE đã được đánh giá hai lần khi được sử dụng trong truy vấn bên ngoài và khi nó được kết hợp với PreviousEpriseePay . Vì vậy, chúng tôi thấy 2 nút HÀNG ĐẦU cho việc này.
  • Chúng tôi thấy Trước đó đã đánh giá một lần.

Sau đó, hãy xem Kế hoạch thực thi thực tế của truy vấn với một truy vấn con:

Có một số điều rõ ràng ở đây:

  • Kế hoạch đơn giản hơn.
  • Đơn giản hơn vì truy vấn con để nhận khoản thanh toán mới nhất chỉ được đánh giá một lần.
  • Không có gì ngạc nhiên khi số lần đọc logic ít hơn so với số lần đọc logic của truy vấn với CTE.

Cuối cùng, đây là Kế hoạch thực thi thực tế khi Karl sử dụng các bảng tạm thời:

Vì đó là một loạt ba câu lệnh, chúng tôi cũng thấy ba sơ đồ trong kế hoạch. Cả ba đều đơn giản, nhưng kế hoạch chung không đơn giản như kế hoạch của truy vấn với truy vấn con.

Thống kê thời gian

Sử dụng giải pháp dbForge Studio cho SQL Server, bạn có thể so sánh thống kê thời gian trong Hồ sơ truy vấn. Để làm điều đó, hãy giữ phím CTRL và nhấp vào tên kết quả của từng truy vấn trong lịch sử thực thi:

Số liệu thống kê về thời gian phù hợp với số lần đọc logic và Kế hoạch thực hiện thực tế. Truy vấn phụ chạy nhanh nhất (88 mili giây). Tiếp theo là CTE (199ms). Cuối cùng là việc sử dụng các bảng tạm thời (536ms).

Vậy, chúng ta đã học được gì từ Karl?

Trong ví dụ cụ thể này, chúng tôi thấy rằng sử dụng truy vấn con sẽ tốt hơn nhiều khi chúng tôi muốn có tùy chọn nhanh nhất. Tuy nhiên, nó có thể là một câu chuyện khác nếu bộ yêu cầu không phải như vậy.

Luôn kiểm tra IO THỐNG KÊ và Kế hoạch thực hiện thực tế để biết nên sử dụng kỹ thuật nào.

Bài học rút ra

Tôi hy vọng bạn không đụng đầu vào tường để hiểu CTE (Biểu thức bảng thông thường) là gì. Nếu không, bạn có thể bị CTE (Bệnh não do chấn thương mãn tính). Đùa sang một bên, chúng ta đã tiết lộ điều gì?

  • Biểu thức Bảng Thông thường là các tập kết quả được đặt tên tạm thời. Nó gần với truy vấn con hơn về hành vi và phạm vi nhưng rõ ràng hơn và mang tính mô-đun hơn. Nó cũng có một cái tên.
  • SQL CTE là để đơn giản hóa mã, không phải để làm cho truy vấn của bạn nhanh hơn.
  • Bảy điều chúng ta đã học sẽ hoạt động trên SQL CTE.
  • Năm điều sẽ gây ra lỗi.
  • Chúng tôi cũng đã xác nhận một lần nữa rằng IO THỐNG KÊ và Kế hoạch thực thi thực tế sẽ luôn cung cấp cho bạn một đánh giá tốt hơn. Điều này đúng nếu bạn so sánh CTE với một truy vấn con và một lô bằng cách sử dụng các bảng tạm thời.

Rất thích nó? Sau đó, các nút mạng xã hội đang chờ được nhấn. Chọn một trong những yêu thích của bạn và chia sẻ tình yêu!

Đọc thêm

CTE có thể hỗ trợ như thế nào trong việc viết các truy vấn phức tạp, mạnh mẽ:Quan điểm về hiệu suất


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Sự khác biệt giữa Tuyên bố JDBC và Tuyên bố chuẩn bị

  2. Bắt đầu với Cloud Firestore cho iOS

  3. Làm thế nào để tạo các thủ tục được lưu trữ trong SQL?

  4. 3 Thống kê I / O khó chịu làm trễ hiệu suất truy vấn SQL

  5. CTE có thể hỗ trợ như thế nào trong việc viết các truy vấn phức tạp, mạnh mẽ:Quan điểm về hiệu suất