松本崇博 Blog (SQL Server Tips)

Matsumoto Takahiro Blog, SQL Server Tips & Tricks

  PASSJブログ :: ホーム :: 連絡をする :: RSS  :: ATOM :: Login
  116 投稿数 :: 0 ストーリー :: 213 コメント :: 85 トラックバック

ニュース

過去の記事

カテゴリ

イメージギャラリ

リンク

検索結果を n 件ずつ表示する方法は、いろいろとありますが、
一時テーブル&ストアドを利用するのが一番簡単な方法です。

これを Northwind の Products テーブルを使って説明します。
# コマンドをコピー&ペーストして実行しながら読み進められます

USE Northwind
 
-- 変数宣言、変数へ ch を代入
DECLARE @p1 varchar(40)
SELECT @p1='ch'
 
--商品名に ch が含まれる商品を検索
SELECT ProductID,ProductName
  FROM Products
   WHERE ProductName LIKE '%' + @p1 + '%'

検索結果(14件)
ProductID ProductName
1 Chai
2 Chang
39 Chartreuse verte
4 Chef Anton's Cajun Seasoning
5 Chef Anton's Gumbo Mix
48 Chocolade
56 Gnocchi di nonna Alice
26 Gumbär Gummibärchen
41 Jack's New England Clam Chowder
55 Pâté chinois
12 Queso Manchego La Pastora
34 Sasquatch Ale
27 Schoggi Schokolade
19 Teatime Chocolate Biscuits

SELECT の実行直後に

SELECT @@ROWCOUNT

と実行すると、検索件数(ch 含む商品なら 14 件)を取得できます。

この検索結果を n 件ずつ表示するには、検索結果に連番を振り、
それを一時テーブルに格納するようにします。

-- 一時テーブルの作成(rowID 列が連番のための列)
CREATE TABLE #t
( rowID int IDENTITY(1,1),
  ProductID int,
  ProductName varchar(40) )
 
-- 検索結果(chを含む商品)を一時テーブルへ格納
-- rowID 列には IDENTITY によって連番が自動で割り振られる
DECLARE @p1 varchar(40)
SELECT @p1='ch'
INSERT INTO #t(ProductID,ProductName) 
 SELECT ProductID,ProductName 
  FROM Products
   WHERE ProductName LIKE '%' + @p1 + '%'


あとは、BETWEEN を使って、取り出したい範囲を指定すれば
任意の場所からデータを取得できます。

-- 検索結果のうち 1件目から5件目を取り出す
SELECT * FROM #t
  WHERE rowID BETWEEN 1 AND 5
rowID ProductID ProductName
1 1 Chai
2 2 Chang
3 39 Chartreuse verte
4 4 Chef Anton's Cajun Seasoning
5 5 Chef Anton's Gumbo Mix

-- 6件目から10件目を取り出す
SELECT * FROM #t
  WHERE rowID BETWEEN 6 AND 10
rowIDProductIDProductName
648Chocolade
756Gnocchi di nonna Alice
826Gumbär Gummibärchen
941Jack's New England Clam Chowder
1055Pâté chinois
 
-- 確認が完了したら一時テーブルを削除しておく
DROP TABLE #t


ページングのためのストアドプロシージャ
 以上をまとめて、n 件ずつ取得するためのストアドプロシージャ
  n 件(ページサイズ)部分をパラメータ化したもの

-- ストアドプロシージャ名: paging1
CREATE PROCEDURE paging1
 @p1 varchar(40),     --検索したい文字列
 @rcnt int OUTPUT,    --検索結果の件数を出力するため
 @currentPage int,    --現在のページ番号
 @pageSize int        --ページサイズ
AS
CREATE TABLE #t
( rowID int IDENTITY(1,1),
  ProductID int,
  ProductName varchar(40) )
INSERT INTO #t(ProductID,ProductName) 
 SELECT ProductID,ProductName 
  FROM Products
   WHERE ProductName LIKE '%' + @p1 + '%'
 
-- 処理件数(=検索結果の件数)を出力パラメータへ代入
SELECT @rcnt=@@ROWCOUNT
 
-- 一時テーブルから現在のページ番号に応じて n 件分取り出す
DECLARE @first int,@last int
SELECT @first=((@currentPage-1)*@pageSize)+1
SELECT @last=(@currentPage*@pageSize)
SELECT ProductID,ProductName
 FROM #t
  WHERE rowID BETWEEN @first AND @last


ストアドプロシージャの動作確認

-- ch を含む商品の検索、1ページ目、5件ずつ表示
DECLARE @rcnt int
EXEC paging1 'ch',@rcnt output,1,5
SELECT @rcnt
-- 2ページ目
DECLARE @rcnt int
EXEC paging1 'ch',@rcnt output,2,5
SELECT @rcnt


<その他>
・一時テーブルに PRIMARY KEY 制約をつけるか否か
  PRIMARY KEY 制約をつけると、検索結果を INSERT する
  ときのパフォーマンスが悪くなりますが、BETWEEN での検索が
  速くなります。前者は制約チェックのオーバーヘッド、後者は
  Index Seek の恩恵です。
  どっちが良いかは難しいところですが...
  ページ移動のたびにストアドが呼び出されて、検索が再実行
 (一時テーブルへの INSERT し直し)されることを考えると、
  PRIMARY KEY 制約をつけない方が良いような気がします。

・テーブルサイズが大きい場合
  LIKE '% によるテーブルスキャンがパフォーマンス低下の原因と
  なる可能性が高いので、 LIKE '% で検索対象となる列を別テーブ
  ルへ分割したり、LIKE '% を使わなくても済むような作り込みが必
  要です。

 案1. 「キーワード登録用のキーワード テーブルを別途作る」、
 案2  「頻繁に検索されるキーワードの検索結果を永続テーブルと
    して定期的にバッチで作成する+ユーザーが入力した検索キー
    ワードをログへ残し、頻繁に使用されるものを把握する」、
 案3. 「検索結果の上限を設けてしまう(上位150件のみ表示など)」

 などかな?

・ADO.NET の DataGrid でカスタムページングを行ないたい場合
  参考資料(dotnetjunkies の 2つ)のように、ページ移動ボタンを
  LinkButton で自作します。ページサイズは DataGrid の PageSize
  プロパティから取得できますが、PageIndexChanged イベントハン
  ドラは利用せず、LinkButton の Click イベントハンドラで処理します。

・ADO.NET の DataGrid のカスタムページングで VirtualItemCount
  プロパティと PageIndexChanged イベントハンドラを使いたい場合
  VirtualItemCount と PageIndexChanged を使うと、ページ移動ボタン
  を自作することなく簡単にカスタムページングが実現できるのです
  が、VirtualItemCount プロパティは検索結果の件数を事前に設定
  しなければなりません。このため、OUTPUT パラメータで出力し
  た @@ROWCOUNT は利用できません。
  事前に件数を取得するには、通常の検索を行なう前に同じ検索
  条件で COUNT(*) を実行するようにします。
  この方法は、大澤さんの PASSJ 連載「ステップアップ!ADO.NET」
  の第4回が参考になります。
  「データベースからの読み込み ~その3:ページ操作、並べ替え、そして検索~」
 http://www.sqlpassj.org/bunkakai/web/series/ado/04.aspx

  ただし、この方法では検索を 2 回実行することになるので、
  検索対象となるテーブルがデータバッファキャッシュに入りきら
  ないぐらい大きい場合には注意が必要です。


<参考資料>
・dotnetjunkies: ASP.NET DataGrid Paging Part 2 - Custom Paging
 http://www.dotnetjunkies.com/Article/EA868776-D71E-448A-BC23-B64B871F967F.dcik

・dotnetjunkies: ASP.NET DataGrid Paging - Custom Paging w/ Caching & Numeric Links
 http://www.dotnetjunkies.com/Tutorial/07FB766A-35F2-4691-923A-D5F1BA3A12DD.dcik

・DBマガジン 2004年1月号 「ADO.NET 基礎と実践 TIPS」

投稿日時 : 2004年5月18日 10:00

コメントを追加

# re: ページングのためのストアドプロシージャ(検索結果を n件ずつ表示するために) 2004/05/18 11:12 河端善博
よろしければ、徹底的にこのテーマをやりませんか ?
テスト用データを定義して、あとは、徹底的にチューニング。
いかがでしょうか ?

# re: ページングのためのストアドプロシージャ(検索結果を n件ずつ表示するために) 2004/05/18 11:25 松本崇博
>よろしければ、徹底的にこのテーマをやりませんか ?
>
やりたいですね!
今はちょっと時間がないので、時間に余裕があるときに
テスト用データを作成してみます

# re: ページングのためのストアドプロシージャ(検索結果を n件ずつ表示するために) 2004/05/19 8:58 松本崇博
[pml-begin,04379] での 木村さんのアイデアより、
http://www.sqlpassj.org/bbs/ml_disp.aspx?forum_id=1&current_page=1&disp_mode=0&detail_mode=1&message_id=17451

キーのみを一時テーブルに格納した方がパフォーマンスが良い!

-- キー(ProductID)のみを一時テーブルに定義
CREATE TABLE #t
( rowID int IDENTITY(1,1),
ProductID int )

-- キーのみを一時テーブルへ格納
USE Northwind
INSERT INTO #t(ProductID)
SELECT ProductID
FROM Products
WHERE ProductName LIKE '%ch%'

-- キーからその他の情報を取得(6件目から10件目)
SELECT ProductID,ProductName --,UnitPrice やらなにやら
FROM Products
WHERE ProductID IN
( SELECT ProductID FROM #t
WHERE rowID BETWEEN 6 AND 10 )

DROP TABLE #t

# ML でのクエリ版も含めて、今度じっくりと速度調査してみますね。
# ああコピーロボットがほしい。。。

# re: ページングのためのストアドプロシージャ(検索結果を n件ずつ表示するために) 2004/05/19 14:35 渋谷 泰宏
フルテキスト インデックスを作成している場合だけみたいですが、
CONTAINSTABLE という行セット関数を使う方法もあるようですね。
KB 検索みたいな長文検索には向いているみたいです。
私はまだ使ったことないので、時間があれば使ってみたいと思います。

# re: ページングのためのストアドプロシージャ(検索結果を n件ずつ表示するために) 2004/05/20 20:29 松本崇博
渋谷さん、コメントありがとうございます!
フルテキスト インデックス、現場ではどれくらい利用されているのでしょう??
とっても興味がありますね!

# re: ページングのためのストアドプロシージャ(検索結果を n件ずつ表示するために) 2004/05/31 12:04 松本崇博
自分のためにメモ(今度じっくりみてみよう)

aspfaq: How do I page through a recordset?
http://www.aspfaq.com/show.asp?id=2120

# re: ページングのためのストアドプロシージャ(検索結果を n件ずつ表示するために) 2004/06/05 23:33 松本崇博
自分のためにメモ

Paging of Large Resultsets in ASP.NET
http://www.codeproject.com/aspnet/PagingLarge.asp

# PASSJ ウェブログ参照数 トップ 20 (2004/6/15 - 2004/6/29) 2004/06/29 17:53 河端善博の .TEXT でウェブログ
PASSJ ウェブログ参照数 トップ 20 (2004/6/15 - 2004/6/29)

# ページングのためのストアドプロシージャ 2005/06/16 10:15 藤城 さつきの スタートアップ SQL Server
ページングのためのストアドプロシージャ

コメント

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