Tôi sẽ đăng câu trả lời để bắt đầu. Suy nghĩ đầu tiên của tôi là có thể tận dụng tính chất bảo toàn trật tự của một vòng lặp lồng nhau cùng với một vài bảng trợ giúp có một hàng cho mỗi chữ cái. Phần khó khăn sẽ được lặp theo cách mà các kết quả được sắp xếp theo chiều dài cũng như tránh trùng lặp. Ví dụ: khi tham gia chéo một CTE bao gồm tất cả 26 chữ in hoa cùng với '', bạn có thể kết thúc việc tạo 'A' + '' + 'A'
và '' + 'A' + 'A'
tất nhiên đó là cùng một chuỗi.
Quyết định đầu tiên là nơi lưu trữ dữ liệu của người trợ giúp. Tôi đã thử sử dụng bảng tạm thời nhưng điều này có tác động tiêu cực đáng ngạc nhiên đến hiệu suất, mặc dù dữ liệu phù hợp với một trang. Bảng tạm thời chứa dữ liệu dưới đây:
SELECT 'A'
UNION ALL SELECT 'B'
...
UNION ALL SELECT 'Y'
UNION ALL SELECT 'Z'
So với việc sử dụng CTE, truy vấn mất nhiều thời gian hơn 3 lần với bảng phân cụm và 4 lần dài hơn với một đống. Tôi không tin rằng vấn đề là dữ liệu trên đĩa. Nó nên được đọc vào bộ nhớ dưới dạng một trang duy nhất và được xử lý trong bộ nhớ cho toàn bộ kế hoạch. Có lẽ SQL Server có thể làm việc với dữ liệu từ toán tử Constant Scan hiệu quả hơn so với dữ liệu được lưu trữ trong các trang của hàng lưu trữ thông thường.
Thật thú vị, SQL Server chọn đặt các kết quả được đặt hàng từ một bảng tempdb trang duy nhất với dữ liệu được sắp xếp vào một bộ đệm bảng:
SQL Server thường đặt kết quả cho bảng bên trong của phép nối chéo vào bộ đệm bảng, ngay cả khi nó có vẻ vô nghĩa khi làm như vậy. Tôi nghĩ rằng trình tối ưu hóa cần một chút công việc trong lĩnh vực này. Tôi chạy truy vấn với NO_PERFORMANCE_SPOOL
để tránh hiệu suất nhấn.
Một vấn đề với việc sử dụng CTE để lưu trữ dữ liệu của người trợ giúp là dữ liệu không được đảm bảo để được đặt hàng. Tôi không thể nghĩ tại sao trình tối ưu hóa lại chọn không đặt hàng và trong tất cả các thử nghiệm của tôi, dữ liệu đã được xử lý theo thứ tự tôi đã viết CTE:
Tuy nhiên, tốt nhất không nên nắm bắt bất kỳ cơ hội nào, đặc biệt là nếu có cách để làm điều đó mà không có chi phí hiệu suất lớn. Có thể sắp xếp dữ liệu trong một bảng dẫn xuất bằng cách thêm một TOP
toán tử thừa . Ví dụ:
(SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR)
Việc thêm vào truy vấn sẽ đảm bảo rằng kết quả sẽ được trả về theo đúng thứ tự. Tôi dự kiến tất cả các loại sẽ có tác động tiêu cực lớn. Trình tối ưu hóa truy vấn dự kiến điều này cũng dựa trên chi phí ước tính:
Rất ngạc nhiên, tôi không thể quan sát thấy bất kỳ sự khác biệt có ý nghĩa thống kê về thời gian hoặc thời gian chạy cpu có hoặc không có thứ tự rõ ràng. Nếu bất cứ điều gì, truy vấn dường như chạy nhanh hơn với ORDER BY
! Tôi không có lời giải thích cho hành vi này.
Phần khó khăn của vấn đề là tìm ra cách chèn các ký tự trống vào đúng chỗ. Như đã đề cập trước một đơn giản CROSS JOIN
sẽ dẫn đến dữ liệu trùng lặp. Chúng tôi biết rằng chuỗi 100000000 sẽ có độ dài sáu ký tự vì:
26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 = 914654 <100000000
nhưng
26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 + 26 ^ 6 = 321272406> 100000000
Vì vậy, chúng tôi chỉ cần tham gia vào thư CTE sáu lần. Giả sử rằng chúng tôi tham gia CTE sáu lần, lấy một chữ cái từ mỗi CTE và ghép chúng lại với nhau. Giả sử chữ cái ngoài cùng bên trái không trống. Nếu bất kỳ chữ cái tiếp theo nào trống có nghĩa là chuỗi dài dưới sáu ký tự thì đó là một bản sao. Do đó, chúng ta có thể ngăn các bản sao bằng cách tìm ký tự không trống đầu tiên và yêu cầu tất cả các ký tự sau nó cũng không trống. Tôi đã chọn theo dõi điều này bằng cách chỉ định một FLAG
cột cho một trong các CTE và bằng cách thêm một kiểm tra vào WHERE
mệnh đề. Điều này sẽ rõ ràng hơn sau khi nhìn vào truy vấn. Truy vấn cuối cùng như sau:
WITH FIRST_CHAR (CHR) AS
(
SELECT 'A'
UNION ALL SELECT 'B'
UNION ALL SELECT 'C'
UNION ALL SELECT 'D'
UNION ALL SELECT 'E'
UNION ALL SELECT 'F'
UNION ALL SELECT 'G'
UNION ALL SELECT 'H'
UNION ALL SELECT 'I'
UNION ALL SELECT 'J'
UNION ALL SELECT 'K'
UNION ALL SELECT 'L'
UNION ALL SELECT 'M'
UNION ALL SELECT 'N'
UNION ALL SELECT 'O'
UNION ALL SELECT 'P'
UNION ALL SELECT 'Q'
UNION ALL SELECT 'R'
UNION ALL SELECT 'S'
UNION ALL SELECT 'T'
UNION ALL SELECT 'U'
UNION ALL SELECT 'V'
UNION ALL SELECT 'W'
UNION ALL SELECT 'X'
UNION ALL SELECT 'Y'
UNION ALL SELECT 'Z'
)
, ALL_CHAR (CHR, FLAG) AS
(
SELECT '', 0 CHR
UNION ALL SELECT 'A', 1
UNION ALL SELECT 'B', 1
UNION ALL SELECT 'C', 1
UNION ALL SELECT 'D', 1
UNION ALL SELECT 'E', 1
UNION ALL SELECT 'F', 1
UNION ALL SELECT 'G', 1
UNION ALL SELECT 'H', 1
UNION ALL SELECT 'I', 1
UNION ALL SELECT 'J', 1
UNION ALL SELECT 'K', 1
UNION ALL SELECT 'L', 1
UNION ALL SELECT 'M', 1
UNION ALL SELECT 'N', 1
UNION ALL SELECT 'O', 1
UNION ALL SELECT 'P', 1
UNION ALL SELECT 'Q', 1
UNION ALL SELECT 'R', 1
UNION ALL SELECT 'S', 1
UNION ALL SELECT 'T', 1
UNION ALL SELECT 'U', 1
UNION ALL SELECT 'V', 1
UNION ALL SELECT 'W', 1
UNION ALL SELECT 'X', 1
UNION ALL SELECT 'Y', 1
UNION ALL SELECT 'Z', 1
)
SELECT TOP (100000000)
d6.CHR + d5.CHR + d4.CHR + d3.CHR + d2.CHR + d1.CHR
FROM (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d6
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d5
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d4
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d3
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d2
CROSS JOIN (SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR) d1
WHERE (d2.FLAG + d3.FLAG + d4.FLAG + d5.FLAG + d6.FLAG) =
CASE
WHEN d6.FLAG = 1 THEN 5
WHEN d5.FLAG = 1 THEN 4
WHEN d4.FLAG = 1 THEN 3
WHEN d3.FLAG = 1 THEN 2
WHEN d2.FLAG = 1 THEN 1
ELSE 0 END
OPTION (MAXDOP 1, FORCE ORDER, LOOP JOIN, NO_PERFORMANCE_SPOOL);
Các CTE được mô tả ở trên. ALL_CHAR
được nối đến năm lần vì nó bao gồm một hàng cho một ký tự trống. Ký tự cuối cùng trong chuỗi không bao giờ được để trống để CTE riêng được xác định cho nó , FIRST_CHAR
. Cột cờ bổ sung trong ALL_CHAR
được sử dụng để ngăn ngừa trùng lặp như mô tả ở trên. Có thể có một cách hiệu quả hơn để thực hiện kiểm tra này nhưng chắc chắn có nhiều cách không hiệu quả hơn để thực hiện. Một nỗ lực của tôi với LEN()
vàPOWER()
làm cho truy vấn chạy chậm hơn sáu lần so với phiên bản hiện tại.
Các gợi ý MAXDOP 1
và FORCE ORDER
gợi ý là rất cần thiết để đảm bảo rằng thứ tự được bảo toàn trong truy vấn. Một kế hoạch ước tính có chú thích có thể hữu ích để xem tại sao các liên kết theo thứ tự hiện tại của chúng:
Các gói truy vấn thường được đọc từ phải sang trái nhưng các yêu cầu hàng xảy ra từ trái sang phải. Lý tưởng nhất là SQL Server sẽ yêu cầu chính xác 100 triệu hàng từ d1
toán tử quét không đổi. Khi bạn di chuyển từ trái sang phải, tôi hy vọng sẽ có ít hàng được yêu cầu hơn từ mỗi toán tử. Chúng ta có thể thấy điều này trong kế hoạch thực hiện thực tế . Ngoài ra, bên dưới là ảnh chụp màn hình từ SQL Sentry Plan Explorer:
Chúng tôi có chính xác 100 triệu hàng từ d1, đó là một điều tốt. Lưu ý rằng tỷ lệ của các hàng giữa d2 và d3 gần như chính xác là 27: 1 (165336 * 27 = 4464072), điều này có ý nghĩa nếu bạn nghĩ về cách liên kết chéo sẽ hoạt động. Tỷ lệ của các hàng giữa d1 và d2 là 22,4 đại diện cho một số công việc lãng phí. Tôi tin rằng các hàng thừa là từ các bản sao (do các ký tự trống ở giữa các chuỗi) không vượt qua được toán tử nối vòng lặp lồng nhau thực hiện quá trình lọc.
Các LOOP JOIN
gợi ý là không cần thiết về mặt kỹ thuật vì một CROSS JOIN
chỉ có thể được thực hiện như một vòng tham gia trong SQL Server. Các NO_PERFORMANCE_SPOOL
là để ngăn chặn các spooling bảng không cần thiết. Việc bỏ qua gợi ý bộ đệm làm cho truy vấn mất 3 lần lâu hơn trên máy của tôi.
Truy vấn cuối cùng có thời gian cpu khoảng 17 giây và tổng thời gian trôi qua là 18 giây. Đó là khi chạy truy vấn thông qua SSMS và loại bỏ tập kết quả. Tôi rất thích thú khi thấy các phương pháp tạo dữ liệu khác.