クエリ結果のページング
クエリ結果のページングとは、クエリ結果をデータの小さなサブセット、つまりページに分けて返すプロセスです。クエリ結果のページングは、結果を管理しやすい小さな単位でユーザーに表示するために行われる一般的な処理です。
DataAdapter には、Fill メソッドのオーバーロードを通じて 1 ページ分のデータだけを返す機能が用意されています。しかしこれは大きなクエリ結果のページングには適していません。DataAdapter が目的の DataTable または DataSet に、要求されたレコードだけを格納する一方で、クエリ全体を返すためのリソースが使用されるためです。クエリ全体を返す必要があるリソースを使用せずにデータ ソースから 1 ページ分のデータを返すには、必要な行だけ返すように限定する抽出条件をクエリに追加します。
Fill メソッドを使用して 1 ページ分のデータを返すには、データ ページの先頭レコードを指定する startRecord およびデータ ページのレコード数を指定する maxRecords を指定します。
Fill メソッドを使用してクエリ結果の最初のページ (ページ サイズ : 5 つのレコード) を返す方法を次のコード例に示します。
Dim currentIndex As Integer = 0
Dim pageSize As Integer = 5
Dim orderSQL As String = "SELECT * FROM Orders ORDER BY OrderID"
Dim myDA As SqlDataAdapter = New SqlDataAdapter(orderSQL, nwindConn)
Dim myDS As DataSet = New DataSet()
myDA.Fill(myDS, currentIndex, pageSize, "Orders")
[C#]
int currentIndex = 0;
int pageSize = 5;
string orderSQL = "SELECT * FROM Orders ORDER BY OrderID";
SqlDataAdapter myDA = new SqlDataAdapter(orderSQL, nwindConn);
DataSet myDS = new DataSet();
myDA.Fill(myDS, currentIndex, pageSize, "Orders");
上記の例では、DataSet に 5 つのレコードだけが格納されますが、Orders テーブル全体が返されます。DataSet にこれと同じ 5 つのレコードを格納し、5 つのレコードだけを返すには、次のコード例に示すように SQL ステートメントで TOP 句と WHERE 句を使用します。
Dim pageSize As Integer = 5
Dim orderSQL As String = "SELECT TOP " & pageSize & " * FROM Orders ORDER BY OrderID"
Dim myDA As SqlDataAdapter = New SqlDataAdapter(orderSQL, nwindConn)
Dim myDS As DataSet = New DataSet()
myDA.Fill(myDS, "Orders")
[C#]
int pageSize = 5;
string orderSQL = "SELECT TOP " + pageSize + " * FROM Orders ORDER BY OrderID";
SqlDataAdapter myDA = new SqlDataAdapter(orderSQL, nwindConn);
DataSet myDS = new DataSet();
myDA.Fill(myDS, "Orders");
この方法でクエリ結果をページングするときは、次のレコード ページを返すコマンドに一意の ID を渡すために、行を順序付けする固有の識別子を保存する必要があります。次のコード例で示します。
Dim lastRecord As String = myDS.Tables("Orders").Rows(pageSize - 1)("OrderID").ToString()
[C#]
string lastRecord = myDS.Tables["Orders"].Rows[pageSize - 1]["OrderID"].ToString();
startRecord パラメータおよび maxRecords パラメータを受け取る Fill メソッドのオーバーロードを使用して次のレコード ページを返すには、現在のレコード インデックスをページ サイズの分だけインクリメントし、テーブルにレコード ページを格納します。DataSet に 1 ページ分のレコードだけを追加する場合でも、データベース サーバーはクエリ結果全体を返すことに注意してください。次のデータ ページを格納する前にテーブル行をクリアするコード例を次に示します。データベース サーバーとのやり取りを減らすために、ローカルのキャッシュに、返された一定量の行を保存することもできます。
currentIndex = currentIndex + pageSize
myDS.Tables("Orders").Rows.Clear()
myDA.Fill(myDS, currentIndex, pageSize, "Orders")
[C#]
currentIndex += pageSize;
myDS.Tables["Orders"].Rows.Clear();
myDA.Fill(myDS, currentIndex, pageSize, "Orders");
データベース サーバーによってクエリ全体を返さずに次のレコード ページを返すには、SQL SELECT ステートメントに限定的な抽出条件を指定します。上の例では最後に返されたレコードが保存されますが、次のコード例に示すようにそのレコードを WHERE 句で使用するとクエリの開始点を指定できます。
orderSQL = "SELECT TOP " & pageSize & " * FROM Orders WHERE OrderID > " & lastRecord & " ORDER BY OrderID"
myDA.SelectCommand.CommandText = orderSQL
myDS.Tables("Orders").Rows.Clear()
myDA.Fill(myDS, "Orders")
[C#]
orderSQL = "SELECT TOP " + pageSize + " * FROM Orders WHERE OrderID > " + lastRecord + " ORDER BY OrderID";
myDA.SelectCommand.CommandText = orderSQL;
myDS.Tables["Orders"].Rows.Clear();
myDA.Fill(myDS, "Orders");
次の例では、データベースから 1 回に 1 ページだけを返す抽出条件を SQL ステートメントに指定してクエリ結果をページングしています。
Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Drawing
Imports System.Windows.Forms
Public Class PagingSample
Inherits Form
' Form controls.
Dim prevBtn As Button = New Button()
Dim nextBtn As Button = New Button()
Shared myGrid As DataGrid = New DataGrid()
Shared pageLbl As Label = New Label()
' Paging variables.
Shared pageSize As Integer = 10 ' Size of viewed page.
Shared totalPages As Integer = 0 ' Total pages.
Shared currentPage As Integer = 0 ' Current page.
Shared firstVisibleCustomer As String = "" ' First customer on page to determine location for move previous.
Shared lastVisibleCustomer As String = "" ' Last customer on page to determine location for move next.
' DataSet to bind to DataGrid.
Shared custTable As DataTable
' Initialize connection to database and DataAdapter.
Shared nwindConn As SqlConnection = New SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind")
Shared custDA As SqlDataAdapter = New SqlDataAdapter("", nwindConn)
Shared selCmd As SqlCommand = custDA.SelectCommand()
Public Shared Sub GetData(direction As String)
' Create SQL statement to return a page of records.
selCmd.Parameters.Clear()
Select Case direction
Case "Next"
selCmd.CommandText = "SELECT TOP " & pageSize & " CustomerID, CompanyName FROM Customers " & _
"WHERE CustomerID > @CustomerId ORDER BY CustomerID"
selCmd.Parameters.Add("@CustomerId", SqlDbType.VarChar, 5).Value = lastVisibleCustomer
Case "Previous"
selCmd.CommandText = "SELECT TOP " & pageSize & " CustomerID, CompanyName FROM Customers " & _
"WHERE CustomerID < @CustomerId ORDER BY CustomerID DESC"
selCmd.Parameters.Add("@CustomerId", SqlDbType.VarChar, 5).Value = firstVisibleCustomer
Case Else
selCmd.CommandText = "SELECT TOP " & pageSize & " CustomerID, CompanyName FROM Customers ORDER BY CustomerID"
' Determine total pages.
Dim totCMD As SqlCommand = New SqlCommand("SELECT Count(*) FROM Customers", nwindConn)
nwindConn.Open()
Dim totalRecords As Integer = CInt(totCMD.ExecuteScalar())
nwindConn.Close()
totalPages = CInt(Math.Ceiling(CDbl(totalRecords) / pageSize))
End Select
' Fill a temporary table with query results.
Dim tmpTable As DataTable = New DataTable("Customers")
Dim recordsAffected As Integer = custDA.Fill(tmpTable)
' If table does not exist, create it.
If custTable Is Nothing Then custTable = tmpTable.Clone()
' Refresh table if at least one record returned.
If recordsAffected > 0 Then
Select Case direction
Case "Next"
currentPage += 1
Case "Previous"
currentPage += -1
Case Else
currentPage = 1
End Select
pageLbl.Text = "Page " & currentPage & " of " & totalPages
' Clear rows and add New results.
custTable.Rows.Clear()
Dim myRow As DataRow
For Each myRow In tmpTable.Rows
custTable.ImportRow(myRow)
Next
' Preserve first and last primary key values.
Dim ordRows() As DataRow = custTable.Select("", "CustomerID ASC")
firstVisibleCustomer = ordRows(0)(0).ToString()
lastVisibleCustomer = ordRows(custTable.Rows.Count - 1)(0).ToString()
End If
End Sub
Public Sub New()
MyBase.New
' Initialize controls and add to form.
Me.ClientSize = New Size(360, 274)
Me.Text = "NorthWind Data"
myGrid.Location = New Point(10,10)
myGrid.Size = New Size(340, 220)
myGrid.AllowSorting = true
myGrid.CaptionText = "NorthWind Customers"
myGrid.ReadOnly = true
myGrid.AllowNavigation = false
myGrid.PreferredColumnWidth = 150
prevBtn.Text = "<<"
prevBtn.Size = New Size(48, 24)
prevBtn.Location = New Point(92, 240)
AddHandler prevBtn.Click, New EventHandler(AddressOf Prev_OnClick)
nextBtn.Text = ">>"
nextBtn.Size = New Size(48, 24)
nextBtn.Location = New Point(160, 240)
pageLbl.Text = "No Records Returned."
pageLbl.Size = New Size(130, 16)
pageLbl.Location = New Point(218, 244)
Me.Controls.Add(myGrid)
Me.Controls.Add(prevBtn)
Me.Controls.Add(nextBtn)
Me.Controls.Add(pageLbl)
AddHandler nextBtn.Click, New EventHandler(AddressOf Next_OnClick)
' Populate DataSet with first page of records and bind to grid.
GetData("Default")
Dim custDV As DataView = New DataView(custTable, "", "CustomerID", DataViewRowState.CurrentRows)
myGrid.SetDataBinding(custDV, "")
End Sub
Public Shared Sub Prev_OnClick(sender As Object, args As EventArgs)
GetData("Previous")
End Sub
Public Shared Sub Next_OnClick(sender As Object, args As EventArgs)
GetData("Next")
End Sub
End Class
Public Class Sample
Shared Sub Main()
Application.Run(New PagingSample())
End Sub
End Class
[C#]
using System;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Windows.Forms;
public class PagingSample: Form
{
// Form controls.
Button prevBtn = new Button();
Button nextBtn = new Button();
static DataGrid myGrid = new DataGrid();
static Label pageLbl = new Label();
// Paging variables.
static int pageSize = 10; // Size of viewed page.
static int totalPages = 0; // Total pages.
static int currentPage = 0; // Current page.
static string firstVisibleCustomer = ""; // First customer on page to determine location for move previous.
static string lastVisibleCustomer = ""; // Last customer on page to determine location for move next.
// DataSet to bind to DataGrid.
static DataTable custTable;
// Initialize connection to database and DataAdapter.
static SqlConnection nwindConn = new SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind");
static SqlDataAdapter custDA = new SqlDataAdapter("", nwindConn);
static SqlCommand selCmd = custDA.SelectCommand;
public static void GetData(string direction)
{
// Create SQL statement to return a page of records.
selCmd.Parameters.Clear();
switch (direction)
{
case "Next":
selCmd.CommandText = "SELECT TOP " + pageSize + " CustomerID, CompanyName FROM Customers " +
"WHERE CustomerID > @CustomerId ORDER BY CustomerID";
selCmd.Parameters.Add("@CustomerId", SqlDbType.VarChar, 5).Value = lastVisibleCustomer;
break;
case "Previous":
selCmd.CommandText = "SELECT TOP " + pageSize + " CustomerID, CompanyName FROM Customers " +
"WHERE CustomerID < @CustomerId ORDER BY CustomerID DESC";
selCmd.Parameters.Add("@CustomerId", SqlDbType.VarChar, 5).Value = firstVisibleCustomer;
break;
default:
selCmd.CommandText = "SELECT TOP " + pageSize + " CustomerID, CompanyName FROM Customers ORDER BY CustomerID";
// Determine total pages.
SqlCommand totCMD = new SqlCommand("SELECT Count(*) FROM Customers", nwindConn);
nwindConn.Open();
int totalRecords = (int)totCMD.ExecuteScalar();
nwindConn.Close();
totalPages = (int)Math.Ceiling((double)totalRecords / pageSize);
break;
}
// Fill a temporary table with query results.
DataTable tmpTable = new DataTable("Customers");
int recordsAffected = custDA.Fill(tmpTable);
// If table does not exist, create it.
if (custTable == null)
custTable = tmpTable.Clone();
// Refresh table if at least one record returned.
if (recordsAffected > 0)
{
switch (direction)
{
case "Next":
currentPage++;
break;
case "Previous":
currentPage--;
break;
default:
currentPage = 1;
break;
}
pageLbl.Text = "Page " + currentPage + " of " + totalPages;
// Clear rows and add new results.
custTable.Rows.Clear();
foreach (DataRow myRow in tmpTable.Rows)
custTable.ImportRow(myRow);
// Preserve first and last primary key values.
DataRow[] ordRows = custTable.Select("", "CustomerID ASC");
firstVisibleCustomer = ordRows[0][0].ToString();
lastVisibleCustomer = ordRows[custTable.Rows.Count - 1][0].ToString();
}
}
public PagingSample()
{
// Initialize controls and add to form.
this.ClientSize = new Size(360, 274);
this.Text = "NorthWind Data";
myGrid.Location = new Point(10,10);
myGrid.Size = new Size(340, 220);
myGrid.AllowSorting = true;
myGrid.CaptionText = "NorthWind Customers";
myGrid.ReadOnly = true;
myGrid.AllowNavigation = false;
myGrid.PreferredColumnWidth = 150;
prevBtn.Text = "<<";
prevBtn.Size = new Size(48, 24);
prevBtn.Location = new Point(92, 240);
prevBtn.Click += new EventHandler(Prev_OnClick);
nextBtn.Text = ">>";
nextBtn.Size = new Size(48, 24);
nextBtn.Location = new Point(160, 240);
pageLbl.Text = "No Records Returned.";
pageLbl.Size = new Size(130, 16);
pageLbl.Location = new Point(218, 244);
this.Controls.Add(myGrid);
this.Controls.Add(prevBtn);
this.Controls.Add(nextBtn);
this.Controls.Add(pageLbl);
nextBtn.Click += new EventHandler(Next_OnClick);
// Populate DataSet with first page of records and bind to grid.
GetData("Default");
DataView custDV = new DataView(custTable, "", "CustomerID", DataViewRowState.CurrentRows);
myGrid.SetDataBinding(custDV, "");
}
public static void Prev_OnClick(object sender, EventArgs args)
{
GetData("Previous");
}
public static void Next_OnClick(object sender, EventArgs args)
{
GetData("Next");
}
}
public class Sample
{
static void Main()
{
Application.Run(new PagingSample());
}
}
参照
サンプル ADO.NET シナリオ | ADO.NET を使用したデータのアクセス | .NET Framework データ プロバイダによるデータのアクセス | DataSet の作成および使用 | DataTable の作成と使用