河端善博 ブログ / SQL Server / PASSJ

SQL Server, .NET, PASSJ

  PASSJブログ :: ホーム :: 連絡をする :: RSS  :: ATOM :: Login
  952 投稿数 :: 112 ストーリー :: 1482 コメント :: 597 トラックバック

ニュース

Windows Live Alerts
My Yahoo!に追加
Add to Google dotnetnuke.jp
NPOPLUS.JP
MVP
PASSJ

記事のカテゴリ

過去の記事

カテゴリ

イメージギャラリ

.TEXT

INETA コミュニティ

MOM2005

MSMVP

PASSJ

SQL コミュニティ

セキュリティ TIPS

セキュリティのキホン

テスト

ブログ翻訳

多言語対応

.TEXT

INETA コミュニティ

MOM2005

MSMVP

PASSJ

SQL コミュニティ

セキュリティ TIPS

セキュリティのキホン

テスト

ブログ翻訳

多言語対応

項目別の TOP 3 を求めるクエリー(一時テーブルとカーソル版) 」の改良版を作成しました。
みなさんのアドバイスを下に、次の点を変更しています。

■更新履歴

  • カーソルを SELECT DISTINCT Name FROM T とするかわりに、Name 用一時テーブル #N を事前に作成した (2004/5/17)
  • 結果をテーブル変数 @t に保存する代わりに、一時テーブル #T にした(2004/5/17)
  • テーブル #R に ID のみを入れるようにし、出力時に JOIN するようにした (2004/5/19)
  • テーブル #R の ID を NOT NULL にした (2004/5/19)
  • ループを BEGIN TRANSACTION/COMMIT TRANSACTION でまとめた (2004/5/19)
  • カーソルを STATIC にした (2004/5/19)
  • 時間の計測開始を一番最初にした (2004/5/19)

■結果

約 2 倍早くなりました。特に、カーソル用に #N を用意したことが効いています。
SELECT DISCTINCT Name としている場合、FETCH NEXT のたびに、テーブルスキャンが発生します。
Name 用一時テーブルにした場合、テーブルシークになり、Name のレコード数が多くなると、特に影響します。
なお、テストデータは増やす必要があります。Name 100 x Value 100 だと、計測誤差のほうが大きい。
これに相当するセットベースの改良案を求む !

■クエリー

SET NOCOUNT ON
 
-- 時間計測開始
DECLARE @start datetime
SET @start = GETDATE()
 
-- 一時テーブル作成
CREATE TABLE #R (ID int)
CREATE TABLE #N (Name nvarchar(10) PRIMARY KEY)
INSERT INTO #N
    SELECT Name FROM T GROUP BY Name
-- 変数宣言
DECLARE @t TABLE
    (ID int, Name nvarchar(10), Value int)
DECLARE Names CURSOR
    LOCAL STATIC
    FOR
        SELECT Name FROM #N
DECLARE @Name nvarchar(10)
 
-- カーソルオープン
OPEN Names
FETCH NEXT FROM Names INTO @Name
-- ループ
BEGIN TRANSACTION
WHILE @@FETCH_STATUS = 0
BEGIN
    -- Name 単位に TOP3 を @t へインサート
    INSERT INTO #R
    SELECT TOP 3 ID
    FROM T
    WHERE Name = @Name
    ORDER BY Value
    
    FETCH NEXT FROM Names    INTO @Name
END
COMMIT TRANSACTION
-- 終了
CLOSE Names
DEALLOCATE Names
-- 結果表示
SELECT T.Name, T.Value, T.ID FROM #R
INNER JOIN T
ON #R.ID = T.ID
ORDER BY T.Name, T.Value
DROP TABLE #N
DROP TABLE #R
 
-- 時間表示
SELECT 'TIME(ms)' = DATEDIFF(ms, @start, GETDATE())
投稿日時 : 2004年5月17日 16:25

コメントを追加

# re: 項目別の TOP 3 を求めるクエリー(カーソル版 一時テーブル積極利用) 2004/05/17 18:39 渋谷 泰宏
これに対抗するには、WHERE 句すべてベタ書きにするか
動的にクエリを作成して UNION で記述するぐらいしか無いのでわ。
もはやセットベースとは言えませんが。。。
(FOREACH UNION とかって書けると最高な気が。。)

# re: 項目別の TOP 3 を求めるクエリー(カーソル版 一時テーブル積極利用) 2004/05/18 0:44 たなか
本来なら、自分で試してからコメントするべきなのでしょうが、まだまだ
すらすらSQLを書けるまでには行きませんので・・・ アイディアだけ
のコメントです。
・Name+Valueでクラスタインデックスをつける。
・select * from Tでfarst forwardのカーソルを作成する。
・カウンタを初期化する。
・名前変数を初期化する。
・fetchし、名前が名前変数と違えば、名前を名前変数に代入し、カウンタを初期化
・カウンタが3より小さければ、fetchした内容を一時テーブルに書き出し
・カウンタのインクリメント
・fetchをループする
・一時テーブルを出力する
というのはどうなんでしょう。
CPUパワーか多少余計に食うかもしれないのですが、I/Oスピードを
考えると、クラスタオーダーでのテーブルスキャン一回で済むので、
もしも最初のuniq名前の作成段階でテーブルスキャンが必要になる
のであれば、後のアクセスが不要な分、速くなりそうにも思うのですが。

いずれにしろ、カーソルアクセスというのは、たとえ速くても、解としては
面白みに欠けてしまいますね。

# re: 項目別の TOP 3 を求めるクエリー(カーソル版 一時テーブル積極利用) 2004/05/18 1:00 たなか
すみません。selectにorder by Name, Valueを付け忘れてましたね。
ascかdescかは、あわせることが必要でしょうか。

# re: 項目別の TOP 3 を求めるクエリー(カーソル版 一時テーブル積極利用) 2004/05/18 1:57 河端善博
たなかさん、アイデアありがとうございます。
カーソルでも、カリカリにチューニングしてみたいですね。
今回のテストでは、ASC でいきたいと思います。


# re: 項目別の TOP 3 を求めるクエリー(カーソル版 一時テーブル積極利用) 2004/05/18 2:04 河端善博
わずかな改善案がありました。
ループの前後を BEGIN TRANS/COMMIT TRANS します。
気持ち早くなります。


# re: 項目別の TOP 3 を求めるクエリー(カーソル版 一時テーブル積極利用) 2004/05/18 2:15 河端善博
もうひとつの改善案
カーソルを FAST_FORWARD から STATIC にします。
さらに、気持ち早くなるようです。

# re: 項目別の TOP 3 を求めるクエリー(カーソル版 一時テーブル積極利用) 2004/05/18 2:19 河端善博
訂正があります
ループの中で、INSERT INTO 用 SELECT に ORDER BY Value を追加する必要があります

# re: 項目別の TOP 3 を求めるクエリー(カーソル版 一時テーブル積極利用) 2004/05/18 2:23 河端善博
出力結果を一時テーブル #R へ出力する代わりに、テーブル変数 @t へ出力すると、遅くなるようです。
どうして、テーブル変数のほうが遅いのだろうか。

# re: 項目別の TOP 3 を求めるクエリー(カーソル版 一時テーブル積極利用) 2004/05/18 3:35 河端善博
UNION ALL を並べたストアドプロシージャを作成してみました。
さすがに早い !

CREATE PROCEDURE p_Top3UnionAll
AS
SELECT TOP 3 ID, [Name], Value, ID FROM T WHERE [Name] = 'A001'
UNION ALL SELECT TOP 3 ID, [Name], Value, ID FROM T WHERE [Name] = 'A002'
UNION ALL SELECT TOP 3 ID, [Name], Value, ID FROM T WHERE [Name] = 'A003'
以下略


# re: 項目別の TOP 3 を求めるクエリー(カーソル版 一時テーブル積極利用) 2004/05/18 14:17 渋谷 泰宏
時間計測を一時テーブル作成の前にするのが
セットベースと比較する際に公平かと思います。

# re: 項目別の TOP 3 を求めるクエリー(カーソル版 一時テーブル積極利用) 2004/05/18 15:02 河端善博
計測開始ポイント、たしかにそうですね。
変更しましょう。

# re: 項目別の TOP 3 を求めるクエリー(カーソル版 一時テーブル積極利用) 2004/05/18 21:09 青柳 臣一
差があるのかどうか、どっちがいいのかはわかりませんが...

CREATE TABLE #R (ID int PRIMARY KEY)

として、最後に結果を出すときに T と INNER JOIN すると
いうのはどうでしょう?
そのほうが #R への書き込みが減りますし、結果を出すときも
(もしあれば) T の Name、Value のインデックスを参照でき
ますし。


# re: 項目別の TOP 3 を求めるクエリー(カーソル版 一時テーブル積極利用) 2004/05/19 11:55 河端善博
みなさんの意見を元に、クエリーを更新しました。
テストデータ作成クエリーも更新していますので、あわせて評価してみてくださいね

コメント

タイトル:
名前:
Url:
コメント: