
View Demo | Download Source Code (667kb)
One of the great weaknesses of SQL server 2000 was its inability to handle data pagination well. The solution to the problem was always to select all of your results and programmatically hide the results you did not want to show. As the user clicked next or previous you would again select all of the rows and only display what the user asked for. Though the desired interface is achieved through this method, it is terribly inefficient. Why couldnt we select only the data that we wanted? If you have ever had to write a search that used "like" against thousands of records you know how terribly slow SQL server 2000 could perform.
During my SQL Server 2000 days I would search endlessly for a solution to this problem. I tried implementations of using a select top where my last row was greater then a parameter. This works only in some cases, like when ordering by a PK or by date. Otherwise this failed because of the existence of duplicate data. I also tried building sprocs that used crazy for loops to try and accomplish this. In every instance I would always hit a brick wall. The client would request a feature that I could not support with my method and I would always default back to the poor performance of selecting all of the rows(or many of them) and handling the paging scheme programmatically.
Throughout this process I often theorized of a SQL server function that could add a sequential row number to my result set and allow me to use a where clause against that row the only select what rows I needed. After a bit of research I found out that this function did in fact exist. The only problem was it existed only in Oracle! I was enraged, how could something so useful be simply left out of SQL Server 2000?
A few years pass by and microsoft releases .net which offers a partial solution to the problem. Asp.net offers you the ability to output cache the results of your web control. So essentially you can select all of the rows once and as you page through results pull each subsequent set from the cached results. This seems to partially solve the performance problem though you are still faced with making the initial selection. But what if you want to view live changing data? As you decrease your cache time your performance gets worse, as you increase it you data gets old. Eventually you fall back on your tired old method again.
With the release of SQL Server 2005 Microsoft introduces the long overdue ROW_NUMBER() function to solve this problem. In this article we will walk through a C# implementation of pagination using the ROW_NUMBER() method.
The first step is writing your stored procedure. The SQL code for using ROW_NUMBER() is not as intuitive as you might think. When I originally attempted to do this, I tried to simply use the ROW_NUMBER() function like I would newid(). I quickly found out that was not going to work. After some research I came up with the stored procedure below. Though I would have rather seen a more intuitive syntax then what is below when you think about it, it does make sense. I suppose they did not want to hide logic from the programmer and ask him to accept that something magical simply happens. In the following project I will use a database of all zip codes in the United States.
CREATE PROCEDURE [dbo].[sp_getzipcodes]
-- Add the parameters for the stored procedure here
@start int = 0
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
Set NOCOUNT ON
SELECT TOP 20 * FROM
(
SELECT zip,city,state,latitude,longitude,timezone,dst,
ROW_NUMBER() OVER (ORDER BY zip) AS num
FROM dbo.zipcode
) AS a
WHERE num > @start
END
Now that you have you stored procedure you will need to display the results on a web site. In our example we use a grid view control, but essentially this will work with any control because we set the parameter in our SQL data source like so.
<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:personalConnectionString %>"
SelectCommand="sp_getzipcodes" SelectCommandType="StoredProcedure">
<SelectParameters>
<asp:QueryStringParameter Name="start" QueryStringField="start" DefaultValue="0" />
</SelectParameters>
</asp:SqlDataSource>
Finally you need to build your pagination controls. For this project we accomplish this by setting an literal in our code behind page.
if ((Request.QueryString["start"] == null) | (Request.QueryString["start"] == "0"))
{
paging.Text = "<< prev | <a href = \"?start=20\">next >></a>";
}
else
{
int start = Convert.ToInt32(Request.QueryString["start"]) + 1;
int next = Convert.ToInt32(Request.QueryString["start"]) + results;
int prev = Convert.ToInt32(Request.QueryString["start"]) - results;
if (next > max)
{
paging.Text = @"<a href = ""?start=" + prev + @"""><< prev</a> | next >></a>";
}
else
{
paging.Text = @"<a href = ""?start=" + prev + @"""><< prev</a> | <a href = ""?start=" + next + @""">next >></a>";
}
}
That is about it. Download the source code to get the full project. Included in the download is the Visual Studio 2005 project, sql server stored procedures and the zip code database in csv format.
View Demo | Download Source Code (667kb)