回答を投稿して開始します。私が最初に考えたのは、ネストされたループ結合の順序を維持する性質と、文字ごとに1行のいくつかのヘルパーテーブルを利用できることです。トリッキーな部分は、結果が長さで並べられ、重複を避けるような方法でループすることでした。「」と一緒に、すべての26件の大文字が含まれてCTEを接合する際、クロスたとえば、あなたが生成を終わることができ'A' + '' + 'A'
そして'' + 'A' + 'A'
もちろん、同じ文字列です。
最初の決定は、ヘルパーデータを保存する場所でした。一時テーブルを使用してみましたが、データが単一のページに収まる場合でも、これはパフォーマンスに驚くほど悪い影響を与えました。一時テーブルには以下のデータが含まれていました。
SELECT 'A'
UNION ALL SELECT 'B'
...
UNION ALL SELECT 'Y'
UNION ALL SELECT 'Z'
CTEを使用した場合と比較すると、クエリはクラスター化されたテーブルでは3倍、ヒープでは4倍長くかかりました。問題は、データがディスク上にあることだとは思わない。単一ページとしてメモリに読み込まれ、プラン全体でメモリで処理される必要があります。おそらく、SQL Serverは、通常の行ストアページに格納されているデータよりも、Constant Scanオペレーターからのデータをより効率的に処理できます。
興味深いことに、SQL Serverは、順序付けられたデータを持つ単一ページのtempdbテーブルからの順序付けされた結果をテーブルスプールに入れることを選択します。
SQL Serverは、クロス結合の内部テーブルの結果をテーブルスプールに入れることがよくあります。オプティマイザーはこの領域で少し作業が必要だと思います。NO_PERFORMANCE_SPOOL
パフォーマンスヒットを回避するために、クエリを実行しました。
CTEを使用してヘルパーデータを保存する場合の問題の1つは、データの順序が保証されていないことです。オプティマイザーがそれを順序付けしないことを選択する理由は考えられません。すべてのテストで、データはCTEを記述した順序で処理されました。
ただし、特にパフォーマンスの大きなオーバーヘッドなしでそれを行う方法がある場合は、チャンスをとらないことをお勧めします。余分なTOP
演算子を追加することで、派生テーブルのデータを並べ替えることができます。例えば:
(SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR)
クエリへの追加により、結果が正しい順序で返されることが保証されます。すべての種類がパフォーマンスに大きな悪影響を与えると予想しました。クエリオプティマイザーは、推定コストに基づいてこれも予想していました。
非常に驚くべきことに、明示的な順序付けの有無にかかわらず、CPU時間または実行時間の統計的に有意な差を観察できませんでした。どちらかといえば、クエリはORDER BY
!この動作の説明はありません。
問題の厄介な部分は、適切な場所に空白文字を挿入する方法を見つけ出すことでした。前に述べたように、単純なCROSS JOIN
結果は重複データになります。100000000番目の文字列の長さは6文字であることがわかっています。
26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 = 914654 <100000000
だが
26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 + 26 ^ 6 = 321272406> 100000000
したがって、CTEに6回参加するだけで済みます。CTEに6回参加し、各CTEから1つの文字を取得し、それらをすべて連結するとします。左端の文字が空白ではないと仮定します。後続の文字のいずれかが空白の場合、文字列の長さが6文字未満であるため、重複していることを意味します。したがって、最初の非空白文字を見つけ、それ以降のすべての文字も空白ではないことを要求することにより、重複を防ぐことができます。FLAG
列をCTEの1つに割り当て、WHERE
句にチェックを追加することで、これを追跡することにしました。これは、クエリを見るとより明確になるはずです。最終的なクエリは次のとおりです。
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);
CTEは上記のとおりです。ALL_CHAR
空白文字の行が含まれているため、5回結合されます。文字列の最後の文字は空白にしないでください。そのため、別個のCTEが定義されていFIRST_CHAR
ます。余分なフラグ列は、ALL_CHAR
上記の重複を防ぐために使用されます。このチェックを行うためのより効率的な方法があるかもしれませんが、それを行うための間違いなくより非効率的な方法があります。私が一つの試みLEN()
とPOWER()
現在のバージョンよりも6倍も遅いクエリの実行を作りました。
MAXDOP 1
そしてFORCE ORDER
ヒントは、順番がクエリに保存されていることを確認するために不可欠です。注釈付きの推定プランは、結合が現在の順序になっている理由を確認するのに役立つ場合があります。
クエリプランはしばしば右から左に読み取られますが、行リクエストは左から右に発生します。理想的には、SQL Serverはd1
常時スキャン演算子から1億行を正確に要求します。左から右に移動すると、各演算子から要求される行が少なくなると予想されます。これは実際の実行計画で見ることができます。さらに、以下はSQL Sentry Plan Explorerのスクリーンショットです。
d1から正確に1億行を得たのは良いことです。d2とd3の間の行の比率は、ほぼ正確に27:1(165336 * 27 = 4464072)であることに注意してください。これは、クロス結合がどのように機能するかを考えると意味があります。d1とd2の間の行の比率は22.4で、これは無駄な作業を表しています。余分な行は(文字列の中央にある空白文字のため)重複からのものであり、フィルタリングを行うネストされたループ結合演算子を通過しないと考えています。
aはSQL Serverのループ結合としてのみ実装できるLOOP JOIN
ため、このヒントは技術的には不要CROSS JOIN
です。これNO_PERFORMANCE_SPOOL
は、不要なテーブルスプールを防ぐためです。スプールヒントを省略すると、クエリが私のマシンで3倍長くかかります。
最終クエリのCPU時間は約17秒で、合計経過時間は18秒です。それは、SSMSを介してクエリを実行し、結果セットを破棄するときでした。私はデータを生成する他の方法を見ることに非常に興味があります。