Paging data with SQL Server 2005

The project I am working on required sorting, filtering and paging of large data sets. This isn't an uncommon requirement; yet the solutions are often either fiddly or not very performant. But fortunately, using SQL Server 2005's common table expressions and the new ROW_NUMBER() function, I managed to create a stored procedure that provides sorting, filtering and paging of large volumes of data for any table - and surprisingly it's really fast too! The code is at the end of the post. Feel free to try it out.

The most obvious advantage of the common table expression approach is that it results in much simpler code. You don't end up working with temporary tables or table variables like you had to in SQL Server 2000. As Nigel Rivett explains ,

"A CTE is a "temporary result set" that exists only within the scope of a single SQL statement. It allows access to functionality within that single SQL statement that was previously only available through use of functions, temp tables, cursors, and so on."

In addition to the terseness of CTEs, I imagine that SQL Server is probably super-optimised to work with them. I haven't done any benchmark tests to prove that a CTE approach is more performant than a temp table approach - but I'd be willing to wager that it is. I'll leave the comparison as an excercise for the reader (or for me if I find the time).

Anyway, the proc returns two result sets: the first contains the page of data; the second contains a single value, which is the total number of records that would have been returned without paging. By dividing the total number of records by the page size, you get the total number of pages, which allows you to display things like "Page 3 of 3904".

I thought that the dynamic SQL would make it much slower, but the impact was imperceptible even with large data sets. SQL Server 2005 continues to impress me.

I hope someone finds this stored proc useful.

SQL:
  1. /****** Object:  StoredProcedure [dbo].[GetDataPage]    Script Date: 08/12/2006 23:44:34 ******/
  2. IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GetDataPage]') AND type IN (N'P', N'PC'))
  3. DROP PROCEDURE [dbo].[GetDataPage]
  4. SET ANSI_NULLS ON
  5. SET QUOTED_IDENTIFIER ON
  6. go
  7.  
  8.  
  9. -- =============================================
  10. -- Author:    Stuart Campbell
  11. -- Create date: 25 July 2006
  12. -- Description: Returns a page of data from a table. The data can optionally be filtered and sorted.
  13. -- =============================================
  14. CREATE PROCEDURE [dbo].[GetDataPage]
  15.     @tableName sysname,
  16.     @primaryKey nvarchar(100),
  17.     @startRowIndex int = NULL,
  18.     @maximumRows int = NULL,
  19.     @SortColumn nvarchar(100) = NULL,
  20.     @SortDirection char(4) = 'ASC', -- ASC or DESC
  21.     @Filter nvarchar(1000) = NULL
  22. AS
  23. BEGIN
  24.     SET NOCOUNT ON;
  25.  
  26.     DECLARE @endRowIndex int
  27.     SELECT @endRowIndex = @startRowIndex+@maximumRows-1
  28.  
  29.  
  30.     DECLARE @FROM varchar(1000)
  31.     DECLARE @orderBy varchar(100)
  32.    
  33.     SELECT @FROM = ' FROM ' + @tableName;
  34.     IF (@filter IS NOT NULL) AND (@filter <> '')
  35.         SELECT @FROM = @FROM + ' WHERE ' + @filter
  36.  
  37.     IF (@sortColumn IS NOT NULL) AND (@sortColumn <> '')
  38.         SET @orderBy = ' ORDER BY ' + @sortColumn + ' ' + COALESCE(@sortDirection, '')
  39.     ELSE
  40.         SET @orderBy = ' ORDER BY '+ @primaryKey;
  41.  
  42.     EXEC('WITH cte AS (SELECT ROW_NUMBER() OVER (' + @orderBy + ') AS RowNumber, * ' + @FROM')
  43.     SELECT *
  44.     FROM cte
  45.     WHERE RowNumber between '+ @startRowIndex + ' AND ' + @endRowIndex);
  46.  
  47.     EXEC('SELECT COUNT(1) as TotalRows ' + @FROM);
  48.  
  49.     SET NOCOUNT ON
  50. END

Leave a Reply