私はおおよそ次のようなスキーマを持っています(これは実際のスキーマを単純化したものです):
CREATE TABLE foo (
key1 NUMERIC(6) NOT NULL,
key2 VARCHAR(32) NOT NULL,
val VARCHAR(255) NULL,
CONSTRAINT foo_pk PRIMARY KEY (key1, key2) --PK on key1, key2
)
GO
CREATE TABLE bar (
key1 NUMERIC(6) NOT NULL,
key2 VARCHAR(32) NOT NULL,
val VARCHAR(255) NULL,
CONSTRAINT bar_pk PRIMARY KEY (key1, key2) --PK on key1, key2
)
GO
CREATE TABLE aliases (
id VARCHAR(32) NOT NULL PRIMARY KEY,
text VARCHAR(255) NOT NULL,
CONSTRAINT aliases_uk UNIQUE (text) --PK on id, unique constraint on text
)
GO
INSERT INTO aliases (id, text)
SELECT REPLACE(NEWID(), '-', ''), 'Text1' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text2' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text3' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text4' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text5' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text6' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text7' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text8' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text9' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text10' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text11' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text12' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text13' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text14' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text15' UNION ALL
SELECT REPLACE(NEWID(), '-', ''), 'Text16';
GO
BEGIN
DECLARE @i INT = 1;
WHILE (@i <= 234)
BEGIN
INSERT INTO foo (key1, key2, val) SELECT @i, id, id FROM aliases;
SET @i = @i + 1;
END;
INSERT INTO bar (key1, key2, val) SELECT key1 + 234, key2, val FROM foo;
END;
GO
すべてのkey1
とkey2
のペアのリストを取得したい(key2
のわかりやすい名前を使用し、val
sのわかりやすいエイリアスを連結したもの。私の最初の試みは次のようになります:
WITH
foos_and_bars (key1, key2, val) AS (
SELECT key1, key2, val FROM foo UNION ALL
SELECT key1, key2, val FROM bar),
texts (key1, key2_name, val_text, rnum) AS (
SELECT key1, a1.text, a2.text, ROW_NUMBER() OVER (PARTITION BY key1, key2 ORDER BY val)
FROM foos_and_bars
JOIN aliases a1 ON a1.id = foos_and_bars.key2
LEFT JOIN aliases a2 ON a2.id = foos_and_bars.val),
partitioned (key1, key2_name, val_text, rnum, maxnum) AS (
SELECT key1, key2_name, val_text, rnum, MAX(rnum) OVER (PARTITION BY key1, key2_name)
FROM texts),
recurse (key1, key2_name, val_texts, rnum, maxnum) AS (
SELECT key1, key2_name, val_text, rnum, maxnum
FROM partitioned
WHERE rnum = 1
UNION ALL
SELECT r.key1,
r.key2_name,
CAST(r.val_texts + CHAR(13) + CHAR(10) + p.val_text AS VARCHAR(255)),
p.rnum,
r.maxnum
FROM recurse r
JOIN partitioned p
ON p.key1 = r.key1
AND p.key2_name = r.key2_name
AND p.rnum = r.rnum + 1)
SELECT * FROM recurse WHERE rnum = maxnum;
それは機能し、1秒未満で7480行を取得しますが、SSMSは2分40秒以上実行され、最後の8行を混乱させます。これらの最後の行は最初の行よりも複雑ではなく(実際、現在システムにあるデータでは、rnum
とmaxnum
は1より大きくなることはありません)、これらのテーブルはこのSEの質問をあざけって、ロックはないはずです。
上から下にpartitioned
を選択すると、1秒未満で7488行が生成されるため、問題は再帰的なCTEに限定されているようです。 CTEは何に行き詰まっているのでしょうか?
実行計画(簡略化されたスキーマに一致する設定を使用):
出力は、レコードの検索に使用されるSybaseウィンドウに戻るビューに送られます。そのため、プログラムはデフォルトで一度に1000行をプルしますが、すべての行を取得できる必要があります。
再帰的SQLクエリのアンカー部分は、7488行すべてを生成します。私のマシンでは、クエリのその部分は100ミリ秒未満で終了します。 SSMSは、グリッド結果の7488行すべてをすぐには表示しません。私にも7480しか表示されません。結果はパケットで送信され、残りの8行ではパケットを満たすのに十分ではないため、これが発生したと思います。
クエリの後半は、前半に比べて非常にコストがかかります。実行プランを見ると、ネストされたループの内側に6つのクラスター化インデックススキャンが見られます。これらのスキャンは、アンカーパーツのすべての行に対して少なくとも1回実行されます。 SQL Serverは、数分にわたって合計44928スキャンを実行し、最終的にその部分から0行を生成します。実行のその部分が完了すると、SQL Serverは残りの行のパケットをクライアントに送信し、完了したクエリの結果グリッドに7488行すべてが表示されます。最後の8つの8行を見つけるのに3分かかるとは限りません。ほぼ瞬時に見つかります。送信にはわずか3分かかります。
実際のプランを確認すると、クエリの下半分で実行された大量の作業を確認できます。矢印の太さとスキャンオペレーターの実行回数に注意してください。
クエリが遅いのはそのためです。このクエリで何を達成しようとしているのか理解できないので、速くするためのアドバイスはありません。