Tôi thường đọc khi người ta phải kiểm tra sự tồn tại của một hàng nên luôn luôn được thực hiện với EXISTS thay vì với COUNT.
Rất hiếm khi mọi thứ luôn luôn đúng, đặc biệt là khi nói đến cơ sở dữ liệu. Có bất kỳ số cách nào để diễn đạt cùng một ngữ nghĩa trong SQL. Nếu có một quy tắc hữu ích, có thể là viết các truy vấn bằng cách sử dụng cú pháp tự nhiên nhất có sẵn (và, vâng, đó là chủ quan) và chỉ xem xét viết lại nếu kế hoạch truy vấn hoặc hiệu suất bạn nhận được là không thể chấp nhận được.
Đối với những gì nó có giá trị, riêng tôi về vấn đề là các truy vấn tồn tại được thể hiện một cách tự nhiên nhất bằng cách sử dụng EXISTS
. Đó cũng là kinh nghiệm của tôi EXISTS
có xu hướng tối ưu hóa tốt hơn so với lựa chọn OUTER JOIN
từ chối NULL
. Sử dụng COUNT(*)
và lọc trên =0
là một cách khác, có thể có một số hỗ trợ trong trình tối ưu hóa truy vấn SQL Server, nhưng cá nhân tôi thấy điều này không đáng tin cậy trong các truy vấn phức tạp hơn. Trong mọi trường hợp, EXISTS
dường như tự nhiên hơn (đối với tôi) so với một trong những lựa chọn thay thế đó.
Tôi đã tự hỏi nếu có một lỗ hổng không mong muốn với EXISTS có ý nghĩa hoàn hảo với các phép đo tôi đã thực hiện
Ví dụ cụ thể của bạn rất thú vị, vì nó làm nổi bật cách trình tối ưu hóa xử lý các truy vấn con trong các CASE
biểu thức (và EXISTS
đặc biệt là các bài kiểm tra).
Truy vấn con trong biểu thức CASE
Hãy xem xét các truy vấn (hoàn toàn hợp pháp) sau đây:
DECLARE @Base AS TABLE (a integer NULL);
DECLARE @When AS TABLE (b integer NULL);
DECLARE @Then AS TABLE (c integer NULL);
DECLARE @Else AS TABLE (d integer NULL);
SELECT
CASE
WHEN (SELECT W.b FROM @When AS W) = 1
THEN (SELECT T.c FROM @Then AS T)
ELSE (SELECT E.d FROM @Else AS E)
END
FROM @Base AS B;
Các ngữ nghĩa củaCASE
là rằng WHEN/ELSE
điều khoản được thường được đánh giá theo thứ tự văn bản. Trong truy vấn trên, SQL Server sẽ không trả về lỗi nếu ELSE
truy vấn con trả về nhiều hơn một hàng, nếu WHEN
mệnh đề được thỏa mãn. Để tôn trọng các ngữ nghĩa này, trình tối ưu hóa tạo ra một kế hoạch sử dụng các biến vị ngữ đi qua:
Phía bên trong của các phép nối vòng lặp lồng nhau chỉ được đánh giá khi vị từ truyền qua trả về false. Hiệu quả tổng thể là các CASE
biểu thức được kiểm tra theo thứ tự và các truy vấn con chỉ được đánh giá nếu không có biểu thức nào trước đó được thỏa mãn.
Biểu thức CASE với truy vấn con EXISTS
Khi CASE
sử dụng truy vấn con EXISTS
, kiểm tra tồn tại logic được triển khai dưới dạng bán tham gia, nhưng các hàng thường bị loại bỏ bởi liên kết bán phải được giữ lại trong trường hợp mệnh đề sau cần chúng. Các dòng chảy qua loại bán tham gia đặc biệt này có được một cờ để cho biết liệu bán tham gia có tìm thấy kết quả khớp hay không. Cờ này được gọi là cột thăm dò .
Các chi tiết của việc triển khai là truy vấn con logic được thay thế bằng phép nối tương quan ('áp dụng') bằng cột thăm dò. Công việc được thực hiện theo quy tắc đơn giản hóa trong trình tối ưu hóa truy vấn được gọi RemoveSubqInPrj
(loại bỏ truy vấn con trong phép chiếu). Chúng ta có thể xem chi tiết bằng cờ theo dõi 8606:
SELECT
T1.ID,
CASE
WHEN EXISTS
(
SELECT 1
FROM #T2 AS T2
WHERE T2.ID = T1.ID
) THEN 1
ELSE 0
END AS DoesExist
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000
OPTION (QUERYTRACEON 3604, QUERYTRACEON 8606);
Một phần của cây đầu vào hiển thị EXISTS
thử nghiệm được hiển thị bên dưới:
ScaOp_Exists
LogOp_Project
LogOp_Select
LogOp_Get TBL: #T2
ScaOp_Comp x_cmpEq
ScaOp_Identifier [T2].ID
ScaOp_Identifier [T1].ID
Điều này được chuyển đổi bởi RemoveSubqInPrj
thành một cấu trúc đứng đầu bởi:
LogOp_Apply (x_jtLeftSemi probe PROBE:COL: Expr1008)
Đây là bán tham gia bên trái áp dụng với thăm dò được mô tả trước đây. Chuyển đổi ban đầu này là duy nhất có sẵn trong các trình tối ưu hóa truy vấn SQL Server cho đến nay và quá trình biên dịch sẽ thất bại nếu chuyển đổi này bị vô hiệu hóa.
Một trong những hình dạng kế hoạch thực hiện có thể có cho truy vấn này là triển khai trực tiếp cấu trúc logic đó:
Hàm vô hướng tính toán cuối cùng đánh giá kết quả của CASE
biểu thức bằng giá trị cột thăm dò:
Hình dạng cơ bản của cây kế hoạch được bảo tồn khi tối ưu hóa xem xét các loại kết nối vật lý khác cho bán tham gia. Chỉ hợp nhất tham gia hỗ trợ một cột thăm dò, do đó, một phép nối băm, mặc dù có thể hợp lý, không được xem xét:
Lưu ý kết hợp đầu ra một biểu thức được gắn nhãn Expr1008
(rằng tên giống như trước là trùng hợp ngẫu nhiên) mặc dù không có định nghĩa nào cho nó xuất hiện trên bất kỳ toán tử nào trong kế hoạch. Đây chỉ là cột thăm dò một lần nữa. Như trước đây, vô hướng tính toán cuối cùng sử dụng giá trị thăm dò này để đánh giáCASE
.
Vấn đề là trình tối ưu hóa không khám phá đầy đủ các lựa chọn thay thế chỉ trở nên đáng giá khi kết hợp (hoặc băm) semi jo. Trong kế hoạch các vòng lặp lồng nhau, không có lợi thế để kiểm tra nếu các hàng trongT2
khớp với phạm vi trên mỗi lần lặp không. Với gói hợp nhất hoặc hàm băm, đây có thể là một tối ưu hóa hữu ích.
Nếu chúng ta thêm một biến BETWEEN
vị ngữ phù hợp T2
vào truy vấn, thì tất cả những gì xảy ra là kiểm tra này được thực hiện cho mỗi hàng dưới dạng phần dư trên phép nối bán kết hợp (khó phát hiện trong kế hoạch thực hiện, nhưng nó ở đó):
SELECT
T1.ID,
CASE
WHEN EXISTS
(
SELECT 1
FROM #T2 AS T2
WHERE T2.ID = T1.ID
AND T2.ID BETWEEN 5000 AND 7000 -- New
) THEN 1
ELSE 0
END AS DoesExist
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000;
Chúng tôi hy vọng rằng BETWEEN
vị ngữ thay vào đó sẽ được đẩy xuống để T2
dẫn đến một tìm kiếm. Thông thường, trình tối ưu hóa sẽ xem xét thực hiện việc này (ngay cả khi không có biến vị ngữ phụ trong truy vấn). Nó nhận ra các biến vị ngữ ngụ ý ( BETWEEN
trên T1
và biến vị ngữ nối giữa T1
và T2
với nhau ngụ ý BETWEEN
bậtT2
) mà không có chúng trong văn bản truy vấn ban đầu. Thật không may, mẫu thăm dò ứng dụng có nghĩa là điều này không được khám phá.
Có nhiều cách để viết truy vấn để tạo ra các tìm kiếm trên cả hai đầu vào thành một phép nối bán kết hợp. Một cách liên quan đến việc viết truy vấn theo cách khá không tự nhiên (đánh bại lý do tôi thường thích EXISTS
):
WITH T2 AS
(
SELECT TOP (9223372036854775807) *
FROM #T2 AS T2
WHERE ID BETWEEN 5000 AND 7000
)
SELECT
T1.ID,
DoesExist =
CASE
WHEN EXISTS
(
SELECT * FROM T2
WHERE T2.ID = T1.ID
) THEN 1 ELSE 0 END
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000;
Tôi sẽ không vui khi viết truy vấn đó trong môi trường sản xuất, nó chỉ để chứng minh rằng hình dạng kế hoạch mong muốn là có thể. Nếu truy vấn thực sự bạn cần viết sử dụng CASE
theo cách cụ thể này và hiệu suất bị ảnh hưởng bởi không có tìm kiếm ở phía thăm dò của bán kết hợp, bạn có thể xem xét viết truy vấn bằng cách sử dụng cú pháp khác nhau tạo ra kết quả đúng và kế hoạch thực hiện hiệu quả hơn.