ASP からのレコードをページングする

J.D. Meier
Microsoft Corporation

May 22, 2000

目次
はじめに
問題点
ソリューション
サンプル コード
分析
結論

はじめに

Active Server Pages (ASP) アプリケーションで大きなレコードセットを表示することは、よくある問題の 1 つでしょう。この記事では、この問題と解決方法、特定の状況に合わせて動作するように簡単に変更できるサンプル コードについて考えます。このサンプル コードは使用するブラウザに関係のない、サーバー側のソリューションとなるように設計されています。話題の進行に従って、ソリューションの設計時に考慮する必要のある問題を指摘します。

問題点

クエリから大きなレコードセットが返されましたとしましょう。結果をブラウズするためのシンプルな方法をユーザーに提供し、各ページに結果のサブセットだけを表示する必要があります。これを効率的に実行するには、ActiveX(R) Data Objects (ADO) とデータベースがどのように共に動作するかについて深く理解する必要があります。

ソリューション

1 つの大きな結果ではなく、どのようにレコードセットを分けて "ページ" に表示しますか? ページには基本的に、指定した複数のレコードを共に表示します。たとえば、レコードセットに 100 個のレコードがある場合、1 ページ当たり 10 個のレコードずつ表示させることができます。

ADO には PageSizeAbsolutePage の 2 つのメソッドがあります。これらのメソッドを使用して、1 ページ当たりのレコード数を指定し、ページの先頭にカーソルを表示することができます。

レコードセットを開いた後の基本的な手順は次のとおりです。

  1. レコードセットの PageSize を指定します。これは 1 ページ当たりに表示されるレコードの数を表します。
  2. レコードセットの AbsolutePage を指定します。これにより、連続するページの中から、指定されたページの先頭にレコード ポインタが移動します。
  3. レコードのページを表示します。このためには、PageSize に設定した回数だけ、またはファイルの末尾に到達するまでレコードセットをループします。

サンプル コード

次のサンプル コードは、動作中のページングを表しています。この例を使用して、自分の作成するソリューションのプロトタイプを構築することができます。作成するコードに対して、必ず次の作業を行なってください。

  • エラー ハンドリングの追加。
  • クエリで返されるレコード数に対する制限の追加。
  • 条件を使用したレコードのフィルタリング。たとえば、WHERE 句を構築します。
  • ストアド プロシージャ、またはビューの使用。

サンプル コードの接続文字列と SQL 文を変更して、使用しているデータベースをポイントするように必ず修正してください。また、このコードでは adUserServer などの ADO 定数が使用されているので、Global.asa ファイルの TypeLibrary を参照するか、ASP ページに ADOVBS.INC ファイルをインクルードする必要があります。Microsoft ADO へのプロジェクト参照を設定すると、Visual InterDev(R) では自動的に TypeLibrary 参照が生成されることに注意してください。

前述の例では、ナビゲーション バーを提供するために 2 種類のメソッドが使用されています。

  • ShowNavBar : このメソッドはレコード数と共に特定のページに移動する方法をユーザーに提供します (図 1 参照)。このために、このメソッドでは RecordCount プロパティと PageCount プロパティが使用されます。
  • ShowNavBarFast : このメソッドでは、特定のページに移動する機能もレコード数も提供されませんが、CacheSize プロパティを使用して、フェッチされたレコードの数を制御することができます (図 2 参照)。

PageThroughRs.Asp

<%@ Language=VBScript %>
<% Option Explicit %>
<SCRIPT LANGUAGE=VBScript RUNAT=SERVER>
   '必ず ADO Typelib を参照するか、ADOVBS.Inc を使用します。
   Dim iPageNum, iRowsPerPage

   Main
      Sub Main()
   Dim rst
   Dim sSQL, sConnString

      If Request.QueryString("iPageNum") = "" Then
         iPageNum = 1
      Else
         iPageNum = Request.QueryString("iPageNum")
         iPageNum = CInt(iPageNum)
      End If

      iRowsPerPage = 10


      sConnString = "Provider=SQLOLEDB.1;password=Xyz123;user id=WebUser;" & _ 
         "Initial Catalog=NorthWind;Data Source=MySQLServer;" & _ 
         "network=dbmssocn;" 

      '次の SQL 文により、SQL ビューからすべての列が取得されます。
      'パフォーマンスを最適化するには、
      '- SELECT でストアド プロシージャ、ビュー、または特定の列を使用します。
      '- WHERE 句など、条件を使用して返されるレコードを制限します。
      sSQL = "SELECT CategoryName, ProductName, QuantityPerUnit,"
      sSQL = sSQL & "UnitsInStock, Discontinued"
      sSQL = sSQL & " FROM [Products By Category]"

      Set rst = GetRecords(sConnString, sSQL)

      WriteTableHeader rst
      WriteTableBody rst, iRowsPerPage, iPageNum
      ShowNavBar rst

      'ShowFastNavBar メソッドでは、RecordCount や PageCount が
      '使用されないので、レコードセット ShowFastNavBar rst の 
      'CacheSize で指定された数のレコードだけが取得されます。

      CleanUp rst
   End Sub

   Function GetRecords(sConnString, sSQL)
   Dim cnn
   Dim rst

      set cnn = Server.CreateObject("ADODB.CONNECTION")
      cnn.ConnectionString = sConnString
      cnn.Open

      Set rst = Server.CreateObject("ADODB.RECORDSET")

      Set rst.ActiveConnection = cnn

      'Recordset が開かれると、adUseClient の CursorLocation により、
      'すべてのレコードが取得されます。
      'adUseServer を使用して、CacheSize を大きくすることができます。
      rst.CursorLocation = adUseServer

      'CacheSize により、サーバー側カーソルを使用する場合に 
      'フェッチされる行を制限します。ここでは、表示されている
      'レコードの数 (iRowsPerPage) 使用します。
      rst.CacheSize = iRowsPerPage

      rst.Open sSQL,,adOpenStatic, adLockReadOnly
      
      Set GetRecords = rst
   end Function

   Sub WriteTableHeader(rst)
   Dim fld

      Response.Write "<TABLE WIDTH=80% BORDER=1>"
      Response.Write "<TR>"

      '表の列見出しを作成します。
      For Each fld in rst.Fields
          Response.Write "<TD><B>" & fld.Name & "</B></TD>"
      Next
      Response.Write "</TR>"
   End Sub

   Sub WriteTableBody(rst, iRowsPerPage, iPageNum)
   Dim iLoop
   Dim fld

      iLoop = 1

      rst.PageSize = iRowsPerPage
      rst.AbsolutePage = iPageNum

      '現在のレコード ページを書き出します。
      Do While (Not rst.EOF) and (iLoop <= iRowsPerPage)
         Response.Write "<TR>"
         For Each fld in rst.Fields
             Response.Write "<TD>" & fld.value & "</TD>"
         Next
       iLoop = iLoop + 1
         rst.MoveNext
         Response.Write "</TR>"
       Loop
       Response.Write "</TABLE>"
   End Sub

   Sub ShowNavBar(rst)
   Dim iPageCount
   Dim iLoop
   Dim sScriptName

      'このバージョンではユーザー ナビゲーションが改善されていますが、
      'RecordCount と PageCount に依存しています。このため、サーバー側
      'カーソル用に CacheSize を指定したことは無駄になります。

      Response.Write "<BR><BR>"
      sScriptName = Request.ServerVariables("SCRIPT_NAME")

      If iPageNum > 1 Then
         Response.Write " <a href=" & sScriptName & "?iPageNum="
         Response.Write (iPageNum -1) & "><< Previous</a>"
      End If

      iPageCount = rst.PageCount
      Do Until iLoop > iPageCount
         If iLoop = iPageNum Then
             Response.Write " <B>" & CStr(iLoop) & "</B>"
         Else
             Response.Write " <a href=" & sScriptName & "?iPageNum=" & _
             Cstr(iLoop) & ">" & iLoop & "</a>"
         End If
         iLoop = iLoop + 1
      Loop

      If Not rst.EOF Then
         Response.Write " <a href=" & sScriptName & "?iPageNum="
         Response.Write (iPageNum +1) & "> Next >></a><BR>"
      Else
         Response.Write "<BR>"
      End If

      Response.Write "Page " & iPageNum & " of " & iPageCount & "<BR>"
      Response.Write rst.RecordCount & " Records"  
      End Sub

   Sub ShowFastNavBar(rst)
   Dim iPageCount
   Dim iLoop
   Dim sScriptName

      'このメソッドでは、RecordCount と PageCount が使用されないので、
      'CacheSize を指定し、サーバー側カーソルを使用した場合にとても
      '効果的ですが、ユーザー側の操作における快適さが犠牲になります。

      Response.Write "<BR><BR>"
      sScriptName = Request.ServerVariables("SCRIPT_NAME")

      If iPageNum > 1 Then
         Response.Write " <a href=" & sScriptName & "?iPageNum="
         Response.Write (iPageNum -1) & "><< Previous</a>"
      End If

      If Not rst.EOF Then
         Response.Write " <a href=" & sScriptName & "?iPageNum="
         Response.Write (iPageNum +1) & "> Next >></a><BR>"
      Else
         Response.Write "<BR>"
      End If

      Response.Write "Page " & iPageNum

   End Sub

   Sub CleanUp(rst)
      If Not rst Is Nothing then
         If rst.state = adStateOpen then rst.close
            set rst = nothing
      End If
   End Sub

</SCRIPT>

分析

ページング ソリューションを設計するときに考慮しなければならない問題は次のとおりです。

  • カーソルの位置は重要です。クライアント側のカーソルを使用する場合、レコードセットを開くたびに、レコードがすべて読み込まれます。すべてのレコードが読み込まれるので、RecordCount プロパティや PageCount プロパティへの以降のアクセスが速くなります。サーバー側のカーソルを使用すると、必要に応じてレコードが取得されます。CacheSize プロパティを使用して、1 度に読み込まれるレコードの数を指定し、パフォーマンスを向上させることができます。ただし、サーバー側カーソルで RecordCount プロパティや PageCount プロパティを使用すると、すべてのレコードが読み取られるので、パフォーマンスの向上が帳消しになってしまいます。より多くの情報が表示されたユーザー インターフェイスと、すべてのレコードを取得することによるパフォーマンスへの影響のバランスを考慮する必要があります。
  • サーバー側カーソルを使用する場合にページングを使用するには、CursorType プロパティを adOpenStatic または adOpenKeyset に設定する必要があります。
  • ページングが適切ではない場合もあります。ページングはユーザーが検索エンジンの結果を見たり、製品カタログをブラウズする場合に適しています。
  • 最初の数ページに関連するレコードがより多く表示されるように、SQL の ORDER BY 句などを使用して、レコードをソートするようにします。ユーザーは必要なだけのデータを表示することができます。
  • 表示する必要のある列だけを取得します。たとえば、SELECT * を使用するのは避けます。
  • 表示する必要のあるレコードだけを取得します。たとえホ、WHERE 句を使用して、条件をフィルタしてください。

このほかにも、覚えておくと便利なヒントがあります。

  • メソッドのロジックをカプセル化します。メソッドを使用すると、プレゼンテーション ロジックとデータ アクセス ロジックを分離することができます。これにより、Windows® スクリプト コンポーネント、Visual Basic® Scripting Edition (VBScript) のクラス、またはコンポーネントへのコードの移行が簡単になります。機能の変更が簡単になり、コードの管理性能も向上します。さらに、メソッドの呼び出しにコメントを付けたり、削除することができるので、テストやデバックも改善されます。
  • ADOVBS.INC をインクルードするよりも、ADO の TypeLibrary を参照するほうが優れたソリューションです。これは、ASP ではインクルードを処理するときに、必要な部分だけではなくファイル全体が読み込まれるからです。

結論

ページングは、大量のレコードをブラウズする優れた方法を提供するために、多くの Web アプリケーションで一般的に使用されている技術です。ページング ソリューションを設計する場合、レコードの取得方法や、ユーザーに提供する必要のあるナビゲーションの種類など、考慮しなければならない点がたくさんあります。最善のソリューションはアプリケーションによって異なりますが、この記事で説明する技術を使用することにより、設計に関して、より適切な判断ができるようになります。

J.D. Meier は米国東海岸で生まれて育ちました。Horace Greeley の助言に従って、MTS および ASP 技術を含むサーバー側コンポーネントと Windows DNA アプリケーションを専門とするデベロッパ サポート エンジニアとして働いています。