Các cách khác nhau để thay thế ISNULL () trong mệnh đề WHERE chỉ sử dụng các giá trị bằng chữ là gì?


55

Điều này không phải là về:

Đây không phải là một câu hỏi về các truy vấn bắt tất cả chấp nhận đầu vào của người dùng hoặc sử dụng các biến.

Đây hoàn toàn là về các truy vấn ISNULL()được sử dụng trong WHEREmệnh đề để thay thế NULLcác giá trị bằng giá trị canary để so sánh với một vị từ và các cách khác nhau để viết lại các truy vấn đó thành SARGable trong SQL Server.

Tại sao bạn không có chỗ ngồi ở đó?

Truy vấn ví dụ của chúng tôi chống lại một bản sao cục bộ của cơ sở dữ liệu Stack Overflow trên SQL Server 2016 và tìm kiếm người dùng có NULLđộ tuổi hoặc tuổi <18.

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

Kế hoạch truy vấn hiển thị Quét một chỉ mục không bao gồm khá chu đáo.

Quả hạch

Toán tử quét hiển thị (nhờ bổ sung vào XML của kế hoạch thực hiện thực tế trong các phiên bản SQL Server gần đây hơn) mà chúng tôi đọc mọi hàng của stinkin.

Quả hạch

Nhìn chung, chúng tôi thực hiện 9157 lần đọc và sử dụng khoảng nửa giây thời gian của CPU:

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 = 485 ms,  elapsed time = 483 ms.

Câu hỏi: các cách để viết lại truy vấn này để làm cho nó hiệu quả hơn và thậm chí có thể là SARGable?

Hãy đưa ra những gợi ý khác. Tôi không nghĩ câu trả lời của tôi nhất thiết phải câu trả lời, và có đủ những người thông minh ngoài kia để đưa ra những lựa chọn thay thế có thể tốt hơn.

Nếu bạn muốn chơi cùng trên máy tính của riêng mình, hãy vào đây để tải xuống cơ sở dữ liệu SO .

Cảm ơn!

Câu trả lời:


57

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 ORmang 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 malarkeyvào kế hoạch truy vấn.

Quả hạch

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:

Quả hạch

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?).

Quả hạch

Nó thực hiện cùng số lần đọc (8233) như ORtruy 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 COUNThoạ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);

Quả hạch

Đ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.

Quả hạch

Đố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.

Quả hạch

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.

Quả hạch

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 COUNTthứ 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.

Quả hạch

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 );

Quả hạch

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);

Quả hạch

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 EXCEPTINTERSECTthườ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 COUNTtrong 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 CASEbiể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.

Quả hạch

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!


1
Các NOT EXISTS ( INTERSECT / EXCEPT )truy vấn có thể hoạt động mà không cần các INTERSECT / EXCEPTbộ phận: WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );Một cách khác - sử dụng EXCEPT: 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)).
ypercubeᵀᴹ

Điều này đã được thử nghiệm? SELECT result = (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age IS NULL) ;Xin lỗi nếu tôi bỏ lỡ trong hàng triệu phiên bản bạn đã thử nghiệm!
ypercubeᵀᴹ

@ ypercubeᵀᴹ đây là kế hoạch cho cái đó. Nó hơi khác một chút, nhưng có các đặc điểm tương tự như các UNION ALLkế hoạch (CPU 360ms, đọc 11k).
Erik Darling

Này Erik, vừa đi lang thang trong thế giới của sql và xuất hiện để nói "cột được tính toán" chỉ để làm phiền bạn. <3
nồi nấu kim loại

17

Tôi không phải là trò chơi để khôi phục cơ sở dữ liệu 110 GB cho chỉ một bảng vì vậy tôi đã tạo dữ liệu của riêng mình . Phân phối độ tuổi phải khớp với những gì trên Stack Overflow nhưng rõ ràng bản thân bảng sẽ không khớp. Tôi không nghĩ rằng đó là một vấn đề quá lớn bởi vì các truy vấn sẽ đạt chỉ mục nào. Tôi đang thử nghiệm trên máy tính 4 CPU với SQL Server 2016 SP1. Một điều cần lưu ý là đối với các truy vấn kết thúc nhanh chóng, điều quan trọng là không bao gồm kế hoạch thực hiện thực tế. Điều đó có thể làm mọi thứ chậm lại một chút.

Tôi bắt đầu bằng cách xem qua một số giải pháp trong câu trả lời xuất sắc của Erik. Đối với cái 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);

Tôi đã nhận được các kết quả sau từ sys.dm_exec_simes sau hơn 10 thử nghiệm (truy vấn tự nhiên diễn ra song song với tôi):

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3532                 975          60830 
╚══════════╩════════════════════╩═══════════════╝

Truy vấn hoạt động tốt hơn cho Erik thực sự hoạt động kém hơn trên máy của tôi:

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);

Kết quả từ 10 thử nghiệm:

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     5704                1636          60850 
╚══════════╩════════════════════╩═══════════════╝

Tôi không thể giải thích ngay tại sao nó tệ đến vậy, nhưng không rõ tại sao chúng tôi muốn buộc gần như mọi nhà khai thác trong kế hoạch truy vấn phải đi song song. Trong kế hoạch ban đầu, chúng ta có một vùng nối tiếp tìm thấy tất cả các hàng với AGE < 18. Chỉ có vài ngàn hàng. Trên máy của tôi, tôi nhận được 9 lần đọc logic cho phần truy vấn đó và 9 ms thời gian CPU được báo cáo và thời gian trôi qua. Ngoài ra còn có một vùng nối tiếp cho tổng hợp toàn cầu cho các hàng AGE IS NULLnhưng chỉ xử lý một hàng trên mỗi DOP. Trên máy của tôi đây chỉ là bốn hàng.

Takeaway của tôi là điều quan trọng nhất để tối ưu hóa một phần của truy vấn mà tìm thấy hàng với một NULLcho Agevì có hàng triệu những hàng. Tôi không thể tạo một chỉ mục có ít trang bao phủ dữ liệu hơn một trang được nén đơn giản trên cột. Tôi giả định rằng có một kích thước chỉ mục tối thiểu trên mỗi hàng hoặc có thể tránh được nhiều không gian chỉ mục với các thủ thuật mà tôi đã thử. Vì vậy, nếu chúng ta bị mắc kẹt với cùng số lần đọc logic để lấy dữ liệu thì cách duy nhất để làm cho nó nhanh hơn là truy vấn song song hơn, nhưng điều này cần được thực hiện theo cách khác với truy vấn của Erik sử dụng TF 8649. Trong truy vấn trên, chúng tôi có tỷ lệ 3,62 cho thời gian CPU so với thời gian trôi qua là khá tốt. Lý tưởng sẽ là tỷ lệ 4.0 trên máy của tôi.

Một lĩnh vực có thể cải tiến là phân chia công việc đồng đều hơn giữa các luồng. Trong ảnh chụp màn hình bên dưới, chúng ta có thể thấy rằng một trong những CPU của tôi đã quyết định nghỉ một chút:

chủ đề lười biếng

Quét chỉ mục là một trong số ít các toán tử có thể được triển khai song song và chúng ta không thể làm gì về cách các hàng được phân phối cho các luồng. Có một yếu tố cơ hội cho nó là tốt nhưng khá nhất quán tôi đã thấy một chủ đề chưa hoàn thành. Một cách để giải quyết vấn đề này là thực hiện song song một cách khó khăn: trên phần bên trong của một vòng lặp lồng nhau. Bất cứ điều gì ở phần bên trong của một vòng lặp lồng nhau sẽ được thực hiện theo cách nối tiếp nhưng nhiều luồng nối tiếp có thể chạy đồng thời. Miễn là chúng ta có được một phương thức phân phối song song thuận lợi (chẳng hạn như vòng tròn), chúng ta có thể kiểm soát chính xác có bao nhiêu hàng được gửi đến mỗi luồng.

Tôi đang chạy truy vấn với DOP 4 vì vậy tôi cần chia đều các NULLhàng trong bảng thành bốn nhóm. Một cách để làm điều này là tạo ra một loạt các chỉ mục trên các cột được tính toán:

ALTER TABLE dbo.Users
ADD Compute_bucket_0 AS (CASE WHEN Age IS NULL AND Id % 4 = 0 THEN 1 ELSE NULL END),
Compute_bucket_1 AS (CASE WHEN Age IS NULL AND Id % 4 = 1 THEN 1 ELSE NULL END),
Compute_bucket_2 AS (CASE WHEN Age IS NULL AND Id % 4 = 2 THEN 1 ELSE NULL END),
Compute_bucket_3 AS (CASE WHEN Age IS NULL AND Id % 4 = 3 THEN 1 ELSE NULL END);

CREATE INDEX IX_Compute_bucket_0 ON dbo.Users (Compute_bucket_0) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_1 ON dbo.Users (Compute_bucket_1) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_2 ON dbo.Users (Compute_bucket_2) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_3 ON dbo.Users (Compute_bucket_3) WITH (DATA_COMPRESSION = PAGE);

Tôi không chắc tại sao bốn chỉ mục riêng biệt nhanh hơn một chỉ số một chút nhưng đó là một trong những gì tôi tìm thấy trong thử nghiệm của mình.

Để có được một kế hoạch lặp song song lồng nhau, tôi sẽ sử dụng cờ theo dõi không có giấy tờ 8649 . Tôi cũng sẽ viết mã một cách kỳ lạ để khuyến khích trình tối ưu hóa không xử lý nhiều hàng hơn mức cần thiết. Dưới đây là một triển khai có vẻ hoạt động tốt:

SELECT SUM(t.cnt) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18)
FROM 
(VALUES (0), (1), (2), (3)) v(x)
CROSS APPLY 
(
    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_0 = CASE WHEN v.x = 0 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_1 = CASE WHEN v.x = 1 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_2 = CASE WHEN v.x = 2 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_3 = CASE WHEN v.x = 3 THEN 1 ELSE NULL END
) t
OPTION (QUERYTRACEON 8649);

Kết quả từ mười thử nghiệm:

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3093                 803          62008 
╚══════════╩════════════════════╩═══════════════╝

Với truy vấn đó, chúng tôi có CPU với thời gian trôi qua là 3,85! Chúng tôi đã loại bỏ 17 ms khỏi thời gian chạy và chỉ mất 4 cột và chỉ mục được tính toán để làm điều đó! Mỗi luồng xử lý rất gần với cùng một số lượng hàng vì mỗi chỉ mục có rất gần với cùng một số hàng và mỗi luồng chỉ quét một chỉ mục:

công việc được phân chia tốt

Trên một lưu ý cuối cùng, chúng ta cũng có thể nhấn nút dễ dàng và thêm CCI không bao gồm vào Agecột:

CREATE NONCLUSTERED COLUMNSTORE INDEX X_NCCI ON dbo.Users (Age);

Truy vấn sau kết thúc sau 3 ms trên máy của tôi:

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18 OR u.Age IS NULL;

Đó sẽ là khó khăn để đánh bại.


7

Mặc dù tôi không có bản sao cơ sở dữ liệu Stack Overflow cục bộ, tôi đã có thể thử một vài truy vấn. Tôi nghĩ rằng để có được một số lượng người dùng từ chế độ xem danh mục hệ thống (trái ngược với việc trực tiếp nhận số lượng hàng từ bảng bên dưới). Sau đó, nhận được một số hàng làm (hoặc có thể không) phù hợp với tiêu chí của Erik và làm một số phép toán đơn giản.

Tôi đã sử dụng Stack Exchange Data Explorer (Cùng với SET STATISTICS TIME ON;SET STATISTICS IO ON;) để kiểm tra các truy vấn. Đối với một điểm tham chiếu, đây là một số truy vấn và số liệu thống kê CPU / IO:

SỐ 1

--Erik's query From initial question.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

Thời gian thực thi của máy chủ SQL: Thời gian CPU = 0 ms, thời gian trôi qua = 0 ms. (1 hàng được trả lại)

Bảng 'Người dùng'. Quét số 17, đọc logic 201567, đọc vật lý 0, đọc trước 2740, đọc logic 0, đọc vật lý lob 0, đọc trước đọc 0, đọc trước 0.

Thời gian thực thi máy chủ SQL: Thời gian CPU = 1829 ms, thời gian trôi qua = 296 ms.

SỐ 2

--Erik's "OR" query.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;

Thời gian thực thi của máy chủ SQL: Thời gian CPU = 0 ms, thời gian trôi qua = 0 ms. (1 hàng được trả lại)

Bảng 'Người dùng'. Quét số 17, đọc logic 201567, đọc vật lý 0, đọc trước đọc 0, đọc logic 0, đọc vật lý lob 0, đọc trước đọc 0, đọc trước 0.

Thời gian thực thi máy chủ SQL: Thời gian CPU = 2500 ms, thời gian trôi qua = 147 ms.

SỐ 3

--Erik's derived tables/UNION ALL query.
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);

Thời gian thực thi của máy chủ SQL: Thời gian CPU = 0 ms, thời gian trôi qua = 0 ms. (1 hàng được trả lại)

Bảng 'Người dùng'. Quét số 34, đọc logic 403134, đọc vật lý 0, đọc trước 0, đọc logic 0, đọc vật lý lob 0, đọc trước đọc 0, đọc trước 0.

Thời gian thực thi máy chủ SQL: Thời gian CPU = 3156 ms, thời gian trôi qua = 215 ms.

Lần thử đầu tiên

Điều này chậm hơn tất cả các truy vấn của Erik mà tôi đã liệt kê ở đây ... ít nhất là về thời gian đã trôi qua.

SELECT SUM(p.Rows)  -
  (
    SELECT COUNT(*)
    FROM dbo.Users AS u
    WHERE u.Age >= 18
  ) 
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

Thời gian thực thi của máy chủ SQL: Thời gian CPU = 0 ms, thời gian trôi qua = 0 ms. (1 hàng được trả lại)

Bảng 'Bàn làm việc'. Quét số 0, đọc logic 0, đọc vật lý 0, đọc trước đọc 0, đọc logic 0, đọc vật lý 0, đọc đọc trước 0, đọc sysrowets '. Quét số 2, đọc logic 10, đọc vật lý 0, đọc trước đọc 0, đọc logic 0, đọc vật lý 0, đọc đọc trước 0, đọc sysschobjs '. Quét số 1, đọc logic 4, đọc vật lý 0, đọc trước đọc 0, đọc logic 0, đọc vật lý 0, đọc đọc trước 0, đọc trước 0. Bảng 'Người dùng'. Quét số 1, đọc logic 201567, đọc vật lý 0, đọc trước đọc 0, đọc logic 0, đọc vật lý lob 0, đọc trước đọc 0, đọc trước 0.

Thời gian thực thi máy chủ SQL: Thời gian CPU = 593 ms, thời gian trôi qua = 598 ms.

Lần thử thứ 2

Ở đây tôi đã chọn một biến để lưu trữ tổng số người dùng (thay vì truy vấn phụ). Số lần quét tăng từ 1 đến 17 so với lần thử đầu tiên. Đọc logic vẫn như cũ. Tuy nhiên, thời gian trôi qua giảm đáng kể.

DECLARE @Total INT;

SELECT @Total = SUM(p.Rows)
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

SELECT @Total - COUNT(*)
FROM dbo.Users AS u
WHERE u.Age >= 18

Thời gian thực thi của máy chủ SQL: Thời gian CPU = 0 ms, thời gian trôi qua = 0 ms. Bảng 'Bàn làm việc'. Quét số 0, đọc logic 0, đọc vật lý 0, đọc trước đọc 0, đọc logic 0, đọc vật lý 0, đọc đọc trước 0, đọc sysrowets '. Quét số 2, đọc logic 10, đọc vật lý 0, đọc trước đọc 0, đọc logic 0, đọc vật lý 0, đọc đọc trước 0, đọc sysschobjs '. Quét số 1, đọc logic 4, đọc vật lý 0, đọc trước đọc 0, đọc logic 0, đọc vật lý lob 0, đọc trước đọc 0, đọc trước 0.

Thời gian thực thi máy chủ SQL: Thời gian CPU = 0 ms, thời gian trôi qua = 1 ms. (1 hàng được trả lại)

Bảng 'Người dùng'. Quét số 17, đọc logic 201567, đọc vật lý 0, đọc trước đọc 0, đọc logic 0, đọc vật lý lob 0, đọc trước đọc 0, đọc trước 0.

Thời gian thực thi máy chủ SQL: Thời gian CPU = 1471 ms, thời gian trôi qua = 98 ms.

Ghi chú khác: DBCC TRACEON không được phép trên Stack Exchange Data Explorer, như đã lưu ý dưới đây:

Người dùng 'STACKEXCHANGE \ svc_sede' không có quyền chạy DBCC TRACEON.


1
Họ có thể không có cùng chỉ số mà tôi làm, do đó có sự khác biệt. Và ai biết? Có lẽ máy chủ nhà tôi dùng phần cứng tốt hơn;) Câu trả lời tuyệt vời!
Erik Darling

bạn nên sử dụng truy vấn sau đây cho attemp đầu tiên của mình (sẽ nhanh hơn nhiều, vì nó chứa rất nhiều sys.objects-trên đầu): SELECT SUM(p.Rows) - (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age >= 18 ) FROM sys.partitions p WHERE p.index_id < 2 AND p.object_id = OBJECT_ID('dbo.Users')
Thomas Franz

PS: lưu ý rằng các chỉ mục trong bộ nhớ (NONCLUSTERED HASH) không có chỉ số id = 0/1 như một chỉ số heap / clustered chung sẽ có)
Thomas Franz

1

Sử dụng biến?

declare @int1 int = ( select count(*) from table_1 where bb <= 1 )
declare @int2 int = ( select count(*) from table_1 where bb is null )
select @int1 + @int2;

Mỗi bình luận có thể bỏ qua các biến

SELECT (select count(*) from table_1 where bb <= 1) 
     + (select count(*) from table_1 where bb is null);

3
Ngoài ra:SELECT (select count(*) from table_1 where bb <= 1) + (select count(*) from table_1 where bb is null);
ypercubeᵀᴹ

3
Có thể muốn thử điều đó trong khi kiểm tra CPU & IO. Gợi ý: nó giống như một trong những câu trả lời của Erik.
Brent Ozar

0

Sử dụng tốt SET ANSI_NULLS OFF;

SET ANSI_NULLS OFF; 
SET STATISTICS TIME ON;
SET STATISTICS IO ON;

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE age=NULL or age<18

Table 'Users'. Scan count 17, logical reads 201567

 SQL Server Execution Times:
 CPU time = 2344 ms,  elapsed time = 166 ms.

Đây là một cái gì đó xuất hiện trong tâm trí của tôi. Chỉ cần thực hiện điều này trong https://data.stackexchange.com

Nhưng không hiệu quả như @blitz_erik


0

Một giải pháp tầm thường là tính toán đếm (*) - đếm (tuổi> = 18):

SELECT
    (SELECT COUNT(*) FROM Users) -
    (SELECT COUNT(*) FROM Users WHERE Age >= 18);

Hoặc là:

SELECT COUNT(*)
     - COUNT(CASE WHEN Age >= 18)
FROM Users;

Kết quả tại đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.