Phần trả lời
Có nhiều cách khác nhau để viết lại điều này bằng các cấu trúc T-SQL khác nhau. Chúng ta sẽ xem xét những ưu và nhược điểm và làm một so sánh tổng thể dưới đây.
Lần đầu lên : Sử dụngOR
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;
Việc sử dụng OR
mang lại cho chúng ta một kế hoạch Tìm kiếm hiệu quả hơn, đọc số lượng hàng chính xác mà chúng ta cần, tuy nhiên, nó bổ sung những gì thế giới kỹ thuật gọi a whole mess of malarkey
vào kế hoạch truy vấn.
Cũng lưu ý rằng Seek được thực thi hai lần ở đây, điều này thực sự rõ ràng hơn từ toán tử đồ họa:
Table 'Users'. Scan count 2, logical reads 8233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 469 ms, elapsed time = 473 ms.
Thứ hai : Sử dụng các bảng dẫn xuất với UNION ALL
truy vấn của chúng tôi cũng có thể được viết lại như thế này
SELECT SUM(Records)
FROM
(
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records);
Điều này mang lại cùng một loại kế hoạch, với ít malarkey hơn, và mức độ trung thực rõ ràng hơn về số lần chỉ số được tìm kiếm (tìm kiếm?).
Nó thực hiện cùng số lần đọc (8233) như OR
truy vấn, nhưng sẽ tắt khoảng 100ms thời gian CPU.
CPU time = 313 ms, elapsed time = 315 ms.
Tuy nhiên, bạn phải thực sự cẩn thận ở đây, bởi vì nếu kế hoạch này cố gắng đi song song, hai COUNT
hoạt động riêng biệt sẽ được nối tiếp, bởi vì chúng được coi là tổng hợp vô hướng toàn cầu. Nếu chúng ta buộc một kế hoạch song song sử dụng Trace Flag 8649, vấn đề sẽ trở nên rõ ràng.
SELECT SUM(Records)
FROM
(
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
Điều này có thể tránh được bằng cách thay đổi truy vấn của chúng tôi một chút.
SELECT SUM(Records)
FROM
(
SELECT 1
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
Bây giờ cả hai nút thực hiện Tìm kiếm đều được song song hóa hoàn toàn cho đến khi chúng ta nhấn toán tử ghép.
Đối với những gì nó có giá trị, phiên bản song song hoàn toàn có một số lợi ích tốt. Với chi phí khoảng hơn 100 lần đọc và khoảng 90ms thời gian CPU bổ sung, thời gian trôi qua co lại thành 93ms.
Table 'Users'. Scan count 12, logical reads 8317, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 500 ms, elapsed time = 93 ms.
CROSS ỨNG DỤNG thì sao?
Không có câu trả lời là hoàn thành mà không có phép thuật CROSS APPLY
!
Thật không may, chúng tôi gặp nhiều vấn đề hơn với COUNT
.
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT COUNT(Id)
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
Kế hoạch này thật kinh khủng. Đây là loại kế hoạch bạn kết thúc khi bạn xuất hiện lần cuối vào Ngày Thánh Patrick. Mặc dù độc đáo song song, vì một số lý do, nó quét PK / CX. Ew. Kế hoạch có chi phí 2198 đô la truy vấn.
Table 'Users'. Scan count 7, logical reads 31676233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 29532 ms, elapsed time = 5828 ms.
Đó là một lựa chọn kỳ lạ, bởi vì nếu chúng ta buộc nó sử dụng chỉ mục không bao gồm, chi phí giảm đáng kể xuống còn 1798 đô la truy vấn.
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT COUNT(Id)
FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
Này, tìm kiếm! Kiểm tra bạn ra ở đó. Cũng lưu ý rằng với sự kỳ diệu của CROSS APPLY
, chúng ta không cần phải làm bất cứ điều gì ngớ ngẩn để có một kế hoạch hoàn toàn song song.
Table 'Users'. Scan count 5277838, logical reads 31685303, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 27625 ms, elapsed time = 4909 ms.
Áp dụng chéo không kết thúc tốt hơn mà không có những COUNT
thứ trong đó.
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT 1
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
Kế hoạch có vẻ tốt, nhưng đọc và CPU không phải là một cải tiến.
Table 'Users'. Scan count 20, logical reads 17564, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 4844 ms, elapsed time = 863 ms.
Viết lại chữ thập áp dụng để trở thành kết quả tham gia xuất phát trong chính xác mọi thứ. Tôi sẽ không đăng lại kế hoạch truy vấn và thông tin thống kê - chúng thực sự không thay đổi.
SELECT COUNT(u.Id)
FROM dbo.Users AS u
JOIN
(
SELECT u.Id
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT u.Id
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x ON x.Id = u.Id;
Đại số quan hệ : Để thấu đáo và để Joe Celko không ám ảnh những giấc mơ của tôi, ít nhất chúng ta cần thử một số thứ quan hệ kỳ lạ. Ở đây không đi đâu!
Một nỗ lực với INTERSECT
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
INTERSECT
SELECT u.Age WHERE u.Age IS NOT NULL );
Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 1094 ms, elapsed time = 1090 ms.
Và đây là một nỗ lực với EXCEPT
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
EXCEPT
SELECT u.Age WHERE u.Age IS NULL);
Table 'Users'. Scan count 7, logical reads 9247, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 2126 ms, elapsed time = 376 ms.
Có thể có những cách khác để viết những thứ này, nhưng tôi sẽ để nó cho những người có lẽ sử dụng EXCEPT
và INTERSECT
thường xuyên hơn tôi.
Nếu bạn thực sự chỉ cần một số đếm
tôi sử dụng COUNT
trong các truy vấn của mình như một chút tốc ký (đọc: Đôi khi tôi quá lười để đưa ra các kịch bản liên quan nhiều hơn). Nếu bạn chỉ cần một số đếm, bạn có thể sử dụng một CASE
biểu thức để làm điều tương tự.
SELECT SUM(CASE WHEN u.Age < 18 THEN 1
WHEN u.Age IS NULL THEN 1
ELSE 0 END)
FROM dbo.Users AS u
SELECT SUM(CASE WHEN u.Age < 18 OR u.Age IS NULL THEN 1
ELSE 0 END)
FROM dbo.Users AS u
Cả hai đều có cùng một kế hoạch và có cùng đặc điểm CPU và đọc.
Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 719 ms, elapsed time = 719 ms.
Người chiến thắng?
Trong các thử nghiệm của tôi, kế hoạch song song bắt buộc với SUM trên bảng dẫn xuất thực hiện tốt nhất. Và vâng, nhiều truy vấn trong số này có thể được hỗ trợ bằng cách thêm một vài chỉ mục được lọc vào tài khoản cho cả hai vị từ, nhưng tôi muốn để lại một số thử nghiệm cho người khác.
SELECT SUM(Records)
FROM
(
SELECT 1
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
Cảm ơn!
NOT EXISTS ( INTERSECT / EXCEPT )
truy vấn có thể hoạt động mà không cần cácINTERSECT / EXCEPT
bộ phận:WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );
Một cách khác - sử dụngEXCEPT
:SELECT COUNT(*) FROM (SELECT UserID FROM dbo.Users EXCEPT SELECT UserID FROM dbo.Users WHERE u.Age >= 18) AS u ;
(trong đó UserID là PK hoặc bất kỳ cột không null nào (s)).