TRƯỜNG HỢP SQL? Miếng bánh!
Thật không?
Không cho đến khi bạn gặp phải 3 vấn đề rắc rối có thể gây ra lỗi thời gian chạy và hiệu suất chậm.
Nếu bạn đang cố gắng quét các tiêu đề phụ để xem vấn đề là gì, tôi không thể trách bạn. Độc giả, bao gồm cả tôi, đang thiếu kiên nhẫn.
Tôi tin tưởng rằng bạn đã biết những kiến thức cơ bản về SQL CASE, vì vậy, tôi sẽ không làm bạn khó chịu khi phải giới thiệu dài dòng. Hãy cùng tìm hiểu sâu hơn về những gì đang diễn ra.
1. SQL CASE không phải lúc nào cũng đánh giá tuần tự
Các biểu thức trong câu lệnh Microsoft SQL CASE chủ yếu được đánh giá theo thứ tự hoặc từ trái sang phải. Tuy nhiên, đó là một câu chuyện khác khi sử dụng nó với các hàm tổng hợp. Hãy lấy một ví dụ:
-- aggregate function evaluated first and generated an error
DECLARE @value INT = 0;
SELECT CASE WHEN @value = 0 THEN 1 ELSE MAX(1/@value) END;
Đoạn mã trên trông bình thường. Nếu tôi hỏi bạn kết quả của những câu lệnh đó là gì, bạn có thể sẽ trả lời là 1. Kiểm tra bằng mắt cho chúng tôi biết rằng vì @value được đặt thành 0. Khi @value bằng 0, kết quả là 1.
Nhưng đó không phải là trường hợp ở đây. Hãy xem kết quả thực từ SQL Server Management Studio:
Msg 8134, Level 16, State 1, Line 4
Divide by zero error encountered.
Nhưng tại sao?
Khi biểu thức điều kiện sử dụng các hàm tổng hợp như MAX () trong SQL CASE, nó được đánh giá đầu tiên. Do đó, MAX (1 / @ giá trị) sẽ gây ra lỗi chia cho 0 vì @ giá trị bằng 0.
Tình huống này rắc rối hơn khi ẩn. Tôi sẽ giải thích nó sau.
2. Biểu thức SQL CASE đơn giản đánh giá nhiều lần
Vậy thì sao?
Câu hỏi hay. Sự thật là không có bất kỳ vấn đề nào xảy ra nếu bạn sử dụng từ hoặc cụm từ đơn giản. Nhưng nếu bạn sử dụng truy vấn con như một biểu thức điều kiện, bạn sẽ nhận được một sự ngạc nhiên lớn.
Trước khi thử ví dụ bên dưới, bạn có thể muốn khôi phục bản sao của cơ sở dữ liệu từ đây. Chúng tôi sẽ sử dụng nó cho phần còn lại của các ví dụ.
Bây giờ, hãy xem xét truy vấn rất đơn giản này:
SELECT TOP 1 manufacturerID FROM SportsCars
Nó rất đơn giản, phải không? Nó trả về 1 hàng với 1 cột dữ liệu. IO THỐNG KÊ cho thấy số lần đọc logic tối thiểu.
Ghi chú nhanh :Đối với người chưa bắt đầu, việc đọc logic cao hơn làm cho truy vấn chậm. Đọc phần này để biết thêm chi tiết.
Kế hoạch Thực thi cũng tiết lộ một quy trình đơn giản:
Bây giờ, hãy đặt truy vấn đó vào biểu thức CASE dưới dạng truy vấn con:
-- Using a subquery in a SQL CASE
DECLARE @manufacturer NVARCHAR(50)
SET @manufacturer = (CASE (SELECT TOP 1 manufacturerID FROM SportsCars)
WHEN 6 THEN 'Alfa Romeo'
WHEN 21 THEN 'Aston Martin'
WHEN 64 THEN 'Ferrari'
WHEN 108 THEN 'McLaren'
ELSE 'Others'
END)
SELECT @manufacturer;
Phân tích
Hãy khoanh tay lại vì điều này sẽ làm mất đi số lần đọc logic hơn 4 lần.
Bất ngờ! So với Hình 1 chỉ với 2 lần đọc logic, con số này cao hơn 4 lần. Do đó, truy vấn chậm hơn 4 lần. Làm thế nào điều đó có thể xảy ra? Chúng tôi chỉ thấy truy vấn con một lần.
Nhưng đó không phải là kết thúc của câu chuyện. Kiểm tra Kế hoạch Thực hiện:
Chúng ta thấy 4 trường hợp của toán tử Quét Top và Index trong Hình 4. Nếu mỗi Top và Index Scan sử dụng 2 lần đọc logic, điều đó giải thích tại sao các lần đọc logic trở thành 8 trong Hình 3. Và vì mỗi Top và Index Scan có chi phí 25% , nó cũng cho chúng ta biết rằng chúng giống nhau.
Nhưng nó không kết thúc ở đó. Thuộc tính của toán tử Compute Scalar tiết lộ cách xử lý toàn bộ câu lệnh.
Chúng ta thấy 4 biểu thức CASE WHEN đến từ các Giá trị xác định của toán tử Tính vô hướng. Có vẻ như biểu thức CASE đơn giản của chúng ta đã trở thành biểu thức CASE được tìm kiếm như sau:
DECLARE @manufacturer NVARCHAR(50)
SET @manufacturer = (CASE
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 6 THEN 'Alfa Romeo'
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 21 THEN 'Aston Martin'
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 64 THEN 'Ferrari'
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 108 THEN 'McLaren'
ELSE 'Others'
END)
SELECT @manufacturer;
Hãy tóm tắt lại. Có 2 lần đọc logic cho mỗi toán tử Quét chỉ mục và Lên trên. Điều này nhân với 4 sẽ tạo ra 8 lần đọc hợp lý. Chúng tôi cũng đã thấy 4 biểu thức CASE WHEN trong toán tử Compute Scalar.
Cuối cùng, truy vấn con trong biểu thức CASE đơn giản đã được đánh giá 4 lần. Điều này sẽ làm chậm truy vấn của bạn.
Cách tránh nhiều lần đánh giá truy vấn con trong biểu thức trường hợp đơn giản
Để tránh vấn đề hiệu suất như nhiều câu lệnh CASE trong SQL, chúng tôi cần viết lại truy vấn.
Đầu tiên, đặt kết quả của truy vấn con vào một biến. Sau đó, sử dụng biến đó trong điều kiện của biểu thức SQL Server CASE đơn giản, như sau:
DECLARE @manufacturer NVARCHAR(50)
DECLARE @ManufacturerID INT -- create a new variable
-- store the result of the subquery in a variable
SET @ManufacturerID = (SELECT TOP 1 manufacturerID FROM SportsCars)
-- use the new variable in the simple CASE expression
SET @manufacturer = (CASE @ManufacturerID
WHEN 6 THEN 'Alfa Romeo'
WHEN 21 THEN 'Aston Martin'
WHEN 64 THEN 'Ferrari'
WHEN 108 THEN 'McLaren'
ELSE 'Others'
END)
SELECT @manufacturer;
Đây có phải là một sửa chữa tốt? Hãy xem các bài đọc logic trong IO THỐNG KÊ:
Chúng tôi thấy các lần đọc logic thấp hơn từ truy vấn đã sửa đổi. Lấy truy vấn con ra và gán kết quả cho một biến sẽ tốt hơn nhiều. Làm thế nào về kế hoạch thực hiện? Xem nó bên dưới.
Toán tử Quét Top và Index chỉ xuất hiện một lần, không phải 4 lần. Tuyệt vời!
Takeaway :Không sử dụng truy vấn con làm điều kiện trong biểu thức CASE. Nếu bạn cần truy xuất một giá trị, hãy đặt kết quả của truy vấn con vào một biến trước. Sau đó, sử dụng biến đó trong biểu thức CASE.
3. 3 hàm tích hợp này chuyển đổi bí mật sang SQL CASE
Có một bí mật và câu lệnh SQL Server CASE có liên quan đến nó. Nếu bạn không biết 3 chức năng này hoạt động như thế nào, bạn sẽ không biết mình đang phạm phải một sai lầm mà chúng tôi đã cố gắng tránh ở điểm # 1 và # 2 trước đó. Chúng đây:
- IIF
- COALESCE
- CHỌN
Hãy kiểm tra từng cái một.
IIF
Tôi đã sử dụng IF ngay lập tức, hoặc IIF, trong Visual Basic và Visual Basic cho ứng dụng. Điều này cũng tương đương với toán tử bậc ba của C #:
Hàm này đưa ra một điều kiện sẽ trả về 1 trong 2 đối số dựa trên kết quả điều kiện. Và chức năng này cũng có sẵn trong T-SQL. Câu lệnh CASE trong mệnh đề WHERE có thể được sử dụng trong câu lệnh SELECT
Nhưng nó chỉ là lớp phủ của một biểu thức CASE dài hơn. Làm sao mà chúng ta biết được? Hãy xem xét một ví dụ.
SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'McLaren Senna', 'Yes', 'No');
Kết quả của truy vấn này là 'Không.' Tuy nhiên, hãy xem Kế hoạch thực thi cùng với các thuộc tính của Tính vô hướng.
Vì IIF là TRƯỜNG HỢP KHI bạn nghĩ điều gì sẽ xảy ra nếu bạn thực hiện một cái gì đó như thế này?
DECLARE @averageCost MONEY = 1000000.00;
DECLARE @noOfPayments TINYINT = 0; -- intentional to force the error
SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'SF90 Spider', 83333.33,MIN(@averageCost / @noOfPayments));
Điều này sẽ dẫn đến lỗi Chia cho 0 nếu @noOfPayments bằng 0. Điều tương tự đã xảy ra ở điểm # 1 trước đó.
Bạn có thể tự hỏi điều gì gây ra lỗi này vì truy vấn trên sẽ dẫn đến TRUE và sẽ trả về 83333.33. Kiểm tra lại điểm số 1.
Do đó, nếu bạn gặp phải lỗi như thế này khi sử dụng IIF, thì SQL CASE chính là thủ phạm.
THAN
COALESCE cũng là một lối tắt của biểu thức SQL CASE. Nó đánh giá danh sách các giá trị và trả về giá trị không rỗng đầu tiên. Trong bài viết trước về COALESCE, tôi đã trình bày một ví dụ đánh giá một truy vấn con hai lần. Nhưng tôi đã sử dụng một phương pháp khác để tiết lộ SQL CASE trong Kế hoạch thực thi. Đây là một ví dụ khác sẽ sử dụng các kỹ thuật tương tự.
SELECT
COALESCE(m.Manufacturer + ' ','') + sc.Model AS Car
FROM SportsCars sc
LEFT JOIN Manufacturers m ON sc.ManufacturerID = m.ManufacturerID
Hãy xem Kế hoạch thực thi và các giá trị do tính toán vô hướng xác định.
SQL CASE ổn. Từ khóa COALESCE không có trong cửa sổ Giá trị xác định. Điều này chứng tỏ bí mật đằng sau chức năng này.
Nhưng đó không phải là tất cả. Đã bao nhiêu lần bạn nhìn thấy [Xe cộ]. [Dbo]. [Kiểu dáng]. [Kiểu dáng] trong cửa sổ Giá trị xác định? HAI LẦN! Điều này phù hợp với tài liệu chính thức của Microsoft. Hãy tưởng tượng nếu một trong các đối số trong COALESCE là một truy vấn con. Sau đó, nhân đôi số lần đọc logic và tốc độ thực thi cũng chậm hơn.
CHỌN
Cuối cùng, CHỌN. Điều này tương tự với chức năng MS Access CHOOSE. Nó trả về 1 giá trị từ danh sách các giá trị dựa trên vị trí chỉ mục. Nó cũng hoạt động như một chỉ mục trong một mảng.
Hãy xem liệu chúng ta có thể khai thác chuyển đổi thành SQL CASE bằng một ví dụ. Kiểm tra mã bên dưới:
;WITH McLarenCars AS
(
SELECT
CASE
WHEN sc.Model IN ('Artura','Speedtail','P1/ P1 GTR','P1 LM') THEN '1'
ELSE '2'
END AS [type]
,sc.Model
,s.Style
FROM SportsCars sc
INNER JOIN Styles s ON sc.StyleID = s.StyleID
WHERE sc.ManufacturerID = 108
)
SELECT
Model
,Style
,CHOOSE([Type],'Hybrid','Gasoline') AS [type]
FROM McLarenCars
Đó là ví dụ CHOOSE của chúng tôi. Bây giờ, chúng ta hãy kiểm tra Kế hoạch thực thi và Giá trị xác định theo tính toán vô hướng:
Bạn có thấy từ khóa CHỌN trong cửa sổ Giá trị xác định trong Hình 10 không? Còn về TRƯỜNG HỢP KHI NÀO?
Giống như các ví dụ trước, hàm CHOOSE này chỉ là một lớp phủ cho một biểu thức CASE dài hơn. Và vì truy vấn có 2 mục CHO LỰA CHỌN, các từ khóa CASE WHEN đã xuất hiện hai lần. Xem cửa sổ Giá trị xác định nằm trong hộp màu đỏ.
Tuy nhiên, chúng ta có nhiều CASE WHEN trong SQL ở đây. Đó là do biểu thức CASE trong truy vấn bên trong của CTE. Nếu bạn xem xét cẩn thận, phần truy vấn bên trong cũng xuất hiện hai lần.
Bài học rút ra
Bây giờ những bí mật đã được tiết lộ, chúng ta đã học được gì?
- SQL CASE hoạt động khác nhau khi các hàm tổng hợp được sử dụng. Hãy cẩn thận khi chuyển các đối số cho các hàm tổng hợp như MIN, MAX hoặc COUNT.
- Một biểu thức CASE đơn giản sẽ đánh giá nhiều lần. Lưu ý nó và tránh chuyển một truy vấn con. Mặc dù nó đúng về mặt cú pháp nhưng nó sẽ hoạt động kém.
- IIF, CHOOSE và COALESCE có những bí mật bẩn thỉu. Hãy ghi nhớ nó trước khi chuyển các giá trị cho các hàm đó. Nó sẽ chuyển đổi thành SQL CASE. Tùy thuộc vào các giá trị, bạn gây ra lỗi hoặc hình phạt về hiệu suất.
Tôi hy vọng cách sử dụng SQL CASE khác nhau này hữu ích cho bạn. Nếu vậy, bạn bè nhà phát triển của bạn cũng có thể thích nó. Hãy chia sẻ nó trên các nền tảng truyền thông xã hội yêu thích của bạn. Và hãy cho chúng tôi biết bạn nghĩ gì về nó trong phần Nhận xét.