Để hoàn thiện, một cách khác để tiếp cận vấn đề này là sử dụng OUTER ỨNG DỤNG . Chúng ta có thể thêm một OUTER APPLY
toán tử cho mỗi giá trị riêng biệt mà chúng ta cần tìm. Khái niệm này tương tự như cách tiếp cận đệ quy của ypercube, nhưng thực sự có đệ quy được viết bằng tay. Một lợi thế là chúng ta có thể sử dụng TOP
trong các bảng dẫn xuất thay vì ROW_NUMBER()
cách giải quyết. Một nhược điểm lớn là văn bản truy vấn sẽ dài hơn khi N
tăng.
Đây là một triển khai cho truy vấn chống lại heap:
SELECT VAL
FROM (
SELECT t1.VAL VAL1, t2.VAL VAL2, t3.VAL VAL3, t4.VAL VAL4, t5.VAL VAL5, t6.VAL VAL6, t7.VAL VAL7, t8.VAL VAL8, t9.VAL VAL9, t10.VAL VAL10
FROM
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP
) t1
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t2 WHERE t2.VAL NOT IN (t1.VAL)
) t2
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t3 WHERE t3.VAL NOT IN (t1.VAL, t2.VAL)
) t3
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t4 WHERE t4.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL)
) t4
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t5 WHERE t5.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL)
) t5
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t6 WHERE t6.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL)
) t6
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t7 WHERE t7.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL)
) t7
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t8 WHERE t8.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL)
) t8
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t9 WHERE t9.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL)
) t9
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t10 WHERE t10.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL, t9.VAL)
) t10
) t
UNPIVOT
(
VAL FOR VALS IN (VAL1, VAL2, VAL3, VAL4, VAL5, VAL6, VAL7, VAL8, VAL9, VAL10)
) AS upvt;
Đây là kế hoạch truy vấn thực tế cho truy vấn trên. Trên máy của tôi, truy vấn này hoàn thành trong 713 ms với 625 ms thời gian CPU và 12605 lần đọc logic. Chúng tôi nhận được một giá trị khác biệt mới cho mỗi 100k hàng, vì vậy tôi mong muốn truy vấn này sẽ quét khoảng 900000 * 10 * 0,5 = 4500000 hàng. Về lý thuyết, truy vấn này sẽ thực hiện năm lần đọc logic của truy vấn này từ câu trả lời khác:
DECLARE @j INT = 10;
SELECT DISTINCT TOP (@j) VAL
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1, OPTIMIZE FOR (@j = 1));
Truy vấn đó đã đọc 2537 logic. 2537 * 5 = 12685, khá gần với 12605.
Đối với bảng có chỉ số cụm chúng ta có thể làm tốt hơn. Điều này là do chúng ta có thể chuyển giá trị khóa được nhóm cuối cùng vào bảng dẫn xuất để tránh quét cùng một hàng hai lần. Một cách thực hiện:
SELECT VAL
FROM (
SELECT t1.VAL VAL1, t2.VAL VAL2, t3.VAL VAL3, t4.VAL VAL4, t5.VAL VAL5, t6.VAL VAL6, t7.VAL VAL7, t8.VAL VAL8, t9.VAL VAL9, t10.VAL VAL10
FROM
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI
) t1
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t2 WHERE PK > t1.PK AND t2.VAL NOT IN (t1.VAL)
) t2
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t3 WHERE PK > t2.PK AND t3.VAL NOT IN (t1.VAL, t2.VAL)
) t3
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t4 WHERE PK > t3.PK AND t4.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL)
) t4
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t5 WHERE PK > t4.PK AND t5.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL)
) t5
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t6 WHERE PK > t5.PK AND t6.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL)
) t6
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t7 WHERE PK > t6.PK AND t7.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL)
) t7
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t8 WHERE PK > t7.PK AND t8.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL)
) t8
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t9 WHERE PK > t8.PK AND t9.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL)
) t9
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t10 WHERE PK > t9.PK AND t10.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL, t9.VAL)
) t10
) t
UNPIVOT
(
VAL FOR VALS IN (VAL1, VAL2, VAL3, VAL4, VAL5, VAL6, VAL7, VAL8, VAL9, VAL10)
) AS upvt;
Đây là kế hoạch truy vấn thực tế cho truy vấn trên. Trên máy của tôi, truy vấn này hoàn thành trong 154 ms với 140 ms thời gian CPU và 3203 lần đọc logic. Điều này dường như chạy nhanh hơn một chút so với OPTIMIZE FOR
truy vấn đối với bảng chỉ mục được nhóm. Tôi không mong đợi điều đó nên tôi đã cố gắng đo hiệu suất cẩn thận hơn. Phương pháp của tôi là chạy mỗi truy vấn mười lần mà không có tập kết quả và xem xét các số tổng hợp từ sys.dm_exec_sessions
và sys.dm_exec_session_wait_stats
. Phiên 56 là APPLY
truy vấn và phiên 63 là OPTIMIZE FOR
truy vấn.
Đầu ra của sys.dm_exec_sessions
:
╔════════════╦══════════╦════════════════════╦═══════════════╗
║ session_id ║ cpu_time ║ total_elapsed_time ║ logical_reads ║
╠════════════╬══════════╬════════════════════╬═══════════════╣
║ 56 ║ 1360 ║ 1373 ║ 32030 ║
║ 63 ║ 2094 ║ 2091 ║ 30400 ║
╚════════════╩══════════╩════════════════════╩═══════════════╝
Dường như có một lợi thế rõ ràng trong cpu_time và elapsed_time cho APPLY
truy vấn.
Đầu ra của sys.dm_exec_session_wait_stats
:
╔════════════╦════════════════════════════════╦═════════════════════╦══════════════╦══════════════════╦═════════════════════╗
║ session_id ║ wait_type ║ waiting_tasks_count ║ wait_time_ms ║ max_wait_time_ms ║ signal_wait_time_ms ║
╠════════════╬════════════════════════════════╬═════════════════════╬══════════════╬══════════════════╬═════════════════════╣
║ 56 ║ SOS_SCHEDULER_YIELD ║ 340 ║ 0 ║ 0 ║ 0 ║
║ 56 ║ MEMORY_ALLOCATION_EXT ║ 38 ║ 0 ║ 0 ║ 0 ║
║ 63 ║ SOS_SCHEDULER_YIELD ║ 518 ║ 0 ║ 0 ║ 0 ║
║ 63 ║ MEMORY_ALLOCATION_EXT ║ 98 ║ 0 ║ 0 ║ 0 ║
║ 63 ║ RESERVED_MEMORY_ALLOCATION_EXT ║ 400 ║ 0 ║ 0 ║ 0 ║
╚════════════╩════════════════════════════════╩═════════════════════╩══════════════╩══════════════════╩═════════════════════╝
Các OPTIMIZE FOR
truy vấn có một loại chờ đợi thêm, RESERVED_MEMORY_ALLOCATION_EXT . Tôi không biết chính xác điều này có nghĩa là gì. Nó có thể chỉ là một phép đo chi phí trong toán tử băm khớp (dòng phân biệt). Trong mọi trường hợp, có lẽ không đáng lo ngại về sự khác biệt 70 ms về thời gian CPU.