Important alert: (current site time 7/15/2013 7:01:32 PM EDT)
 

VB icon

SQL Server Paging Grand Tourismo!

Email
Submitted on: 7/9/2003 7:09:37 PM
By: Andrew Carrasco 
Level: Beginner
User Rating: By 15 Users
Compatibility: SQL Server 2000
Views: 19162
(About the author)
 
     This is a stored procedure that will allow you to page through a result set that has been generated with arbitrary SQL. This code is generic and will allow paging of ANY SQL statement except for execute statements. So, basically, ALL selects will work. It doesnt matter, the procedure uses server metadata to generate a temp table containing just the page you want, which it then returns. It seems reasonable fast, I have a table with 200,000 rows, and I can retrieve any page of data in less than 2 seconds.
 
Can't Copy and Paste this?
Click here for a copy-and-paste friendly version of this code!
--**************************************
-- for :SQL Server Paging Grand Tourismo!
--**************************************
Loosely based on a code sample from an unknown author found in a news group somewhere. His was tightly bound to the table structures, whereas mine is not.
code:
Can't Copy and Paste this?
Click here for a copy-and-paste friendly version of this code!
 
Terms of Agreement:   
By using this code, you agree to the following terms...   
  1. You may use this code in your own programs (and may compile it into a program and distribute it in compiled format for languages that allow it) freely and with no charge.
  2. You MAY NOT redistribute this code (for example to a web site) without written permission from the original author. Failure to do so is a violation of copyright laws.   
  3. You may link to this code from another website, but ONLY if it is not wrapped in a frame. 
  4. You will abide by any additional copyright restrictions which the author may have placed in the code or code's description.
				
--**************************************
-- Name: SQL Server Paging Grand Tourismo!
-- Description:This is a stored procedure that will allow you to page through a result set that has been generated with arbitrary SQL. This code is generic and will allow paging of ANY SQL statement except for execute statements. So, basically, ALL selects will work. It doesnt matter, the procedure uses server metadata to generate a temp table containing just the page you want, which it then returns. It seems reasonable fast, I have a table with 200,000 rows, and I can retrieve any page of data in less than 2 seconds.
-- By: Andrew Carrasco
--
-- Inputs:@page_num --> The page you want to retrieve, @rows_per_page --> The number of rows in a page,
@total_rows (OUTPUT) --> The domain of the query,
@sql_string --> The SQL you want to execute
--
-- Returns:A resultset containing just the rows you selected.
--
-- Assumes:This doesnt execute 'execute' statements, and doesnt work against other stored procs. It ONLY works with arbitrary SQL SELECT statements. Still, it is rather useful. Call it from your other stored procs. It doesnt check to see if you are at the end of the pages. If you request a page that is off the end of the result set you will just get an empty result set. Run a count() on your select statements and divide it by the number of rows per page to get the Page count.
--
--This code is copyrighted and has-- limited warranties.Please see http://www.Planet-Source-Code.com/vb/scripts/ShowCode.asp?txtCodeId=718&lngWId=5--for details.--**************************************

SET QUOTED_IDENTIFIER ON 
GO
SET ANSI_NULLS ON 
GO
ALTER procedure sp_Page_Data 
	@page_num int = 1,
	@rows_per_page int = 25,
	@total_rows int output,
	@sql_string varchar(4096)
as
set nocount on
begin
DECLARE @column_name VARCHAR(100)
DECLARE @column_num INT
DECLARE @column_count INT
DECLARE @declaration_String VARCHAR(1000)
DECLARE @cursor_variables VARCHAR(1000)
DECLARE @html_cursor_variables VARCHAR(1000)
DECLARE @column_size int
DECLARE @column_type_name varchar(100)
DECLARE @max_page int
DECLARE @rows_on_last_page int
 SELECT @declaration_String = ' '
 SELECT @cursor_variables = ' '
 SELECT @html_cursor_variables = ''
declare @starting_record int
set @starting_record = ((@page_num * @rows_per_page) - @rows_per_page) + 1
exec('declare page_cursor scroll cursor for ' + @sql_string)
open page_cursor
set @total_rows = @@CURSOR_ROWS
CREATE TABLE #cols
(ColNum INT IDENTITY (1, 1) NOT NULL ,
ColName VARCHAR(100),
ColCreateString VARCHAR(100)
)
DECLARE colCur CURSOR FOR SELECT A.column_name, A.ordinal_position, B.column_count, 
	A.column_size, C.[name] As Type_Name
	FROM master.dbo.syscursorcolumns A, master.dbo.syscursors B, master.dbo.systypes C
	WHERE A.cursor_handle = B.cursor_handle
	AND A.data_type_sql = C.xtype
	AND B.cursor_name = 'page_cursor'
	ORDER BY ordinal_position
OPEN colCur 
FETCH NEXT FROM colCur INTO @column_name, @column_num, @column_count, @column_size, @column_type_name
INSERT INTO #cols( ColName, ColCreateString) VALUES ( @column_name, @column_type_name + '(' + CONVERT(VARCHAR, @column_size) + ')')
DECLARE @temp_table_string VARCHAR(4096)
DECLARE @temp_col_string VARCHAR(2000)
DECLARE @temp_declare_string varchar(4096)
SET @temp_declare_string = ' '
SET @temp_table_string = 'CREATE TABLE #converge ('
WHILE (@@FETCH_STATUS = 0)
BEGIN
	---INSERT COLUMN NAMES INTO THE cols TABLE
	INSERT INTO #cols( ColName, ColCreateString) VALUES ( @column_name, @column_type_name + '(' + CONVERT(VARCHAR, @column_size) + ')')
	IF ( @column_num +1) < @column_count 
	BEGIN
		 SELECT @cursor_variables = @cursor_variables + ' @' + @column_name + ', '
		 SELECT @temp_col_string =
			CASE @column_type_name
				WHEN 'varchar' THEN @column_name + ' ' + @column_type_name + '(' + CONVERT(VARCHAR, @column_size) + '), ' + CHAR(13)
			ELSE @column_name + ' ' + @column_type_name +', ' + CHAR(13)
			END
		SET @temp_declare_string = @temp_declare_string + 'DECLARE @' + left(@temp_col_string, len(@temp_col_string) - 3) + CHAR(13)
		SET @temp_table_string = @temp_table_string + @temp_col_string
	END
	ELSE
	BEGIN
		SELECT @cursor_variables = @cursor_variables + ' @' + @column_name	
		SELECT @temp_col_string =
			CASE @column_type_name
				WHEN 'varchar' THEN @column_name + ' ' + @column_type_name + '(' + CONVERT(VARCHAR, @column_size) + ')) ' + CHAR(13)
			ELSE @column_name + ' ' + @column_type_name +') ' + CHAR(13)
			END
		SET @temp_declare_string = @temp_declare_string + 'DECLARE @' + left(@temp_col_string, len(@temp_col_string) - 3) + CHAR(13)
		SET @temp_table_string = @temp_table_string + @temp_col_string
	END
	FETCH NEXT FROM colCur INTO @column_name, @column_num, @column_count, @column_size, @column_type_name
END
IF (@total_rows % (@page_num * @rows_per_page)) > 0
	SET @max_page = (@total_rows / @rows_per_page) + 1
ELSE
	SET @max_page = (@total_rows / @rows_per_page)
DECLARE @total_rows_temp INT
IF (@page_num = @max_page)
	SET @total_rows_temp = (@total_rows - ((@max_page - 1) * @rows_per_page))
ELSE
	SET @total_rows_temp = @total_rows
declare @curStr VARCHAR(4096)
--USE DYNAMIC SQL TO DECLARE VARIABLES AND SCROLL THROUGH CURSOR
SELECT @curStr = @declaration_String + CHAR(13) +
' DECLARE @row_counter int' + CHAR(13) + 
' DECLARE @starting_record int' + CHAR(13) + 
' DECLARE @rows_per_page int' + CHAR(13) + 
' DECLARE @total_rows int' + CHAR(13) +
' DECLARE @rows_on_last_page int' + CHAR(13) +
' SET @total_rows = ' + CONVERT(VARCHAR, @total_rows_temp) + CHAR(13) +
@temp_declare_string + CHAR(13) +
' SET @row_counter = 0' + CHAR(13) + 
' SET @rows_per_page = ' + CONVERT(VARCHAR, @rows_per_page) + CHAR(13) + 
' SET @starting_record = ' + CONVERT(VARCHAR, @starting_record) + CHAR(13) + 
' ' + @temp_table_string + CHAR(13) +
' IF (@total_rows > 0)' + CHAR(13) +
' BEGIN' + CHAR(13) +
' 	FETCH ABSOLUTE @starting_record FROM page_cursor INTO '+ @cursor_variables + CHAR(13) +
' 	INSERT INTO #converge VALUES (' + @cursor_variables + ')' + CHAR(13) +
--'	FETCH NEXT FROM page_cursor INTO '+ @cursor_variables + CHAR(13) +
' 	WHILE (@@FETCH_STATUS= 0) AND (@row_counter < @rows_per_page - 1) AND (@total_rows > 1) AND (@row_counter < @total_rows - 1)'+ CHAR(13) + 
	'BEGIN ' +CHAR(13) +
		--BEGIN ROW
	'	FETCH NEXT FROM page_cursor INTO '+ @cursor_variables + CHAR(13) +
	'	INSERT INTO #converge VALUES (' + @cursor_variables + ')' + CHAR(13) +
	'	SET @row_counter = @row_counter + 1 '+ CHAR(13) +
 	'END' + CHAR(13) +
' END' + CHAR(13) +
'SELECT * FROM #converge' + CHAR(13)
EXEC ( @curStr)
close page_cursor
close colCur
deallocate colCur
deallocate page_cursor
drop table #cols
end
GO
SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS ON 
GO


Report Bad Submission
Use this form to tell us if this entry should be deleted (i.e contains no code, is a virus, etc.).
This submission should be removed because:

Your Vote

What do you think of this code (in the Beginner category)?
(The code with your highest vote will win this month's coding contest!)
Excellent  Good  Average  Below Average  Poor (See voting log ...)
 

Other User Comments

7/21/2003 9:03:50 AM

Whoah!! So big yet so cool!! can i borrow it for my project. i was supposed to give you an excellent rate but it seems that PSCs confirmation page has been messed up. theres no confirmation code in it :) tnx.
(If this comment was disrespectful, please report it.)

 
7/21/2003 10:04:55 AMAndrew Carrasco

Of course you can use it in your project! It took me ages to figure out some way of doing this without tightly coupling the paging code to the table or view structure. This was much easier in Oracle, but I know SQL Server people need it, so I posted it for free. Its all yours! :-)

Thanks,
Andrew Carrasco
(If this comment was disrespectful, please report it.)

 
7/23/2003 11:58:09 AMAndrew Carrasco

I cant figure out how to edit this code listing, but I've added a couple of bug fixes to this stored proc.

1) The code was acting funny when the SELECT statement passed to it returned 1 or no rows. I've fixed that.

If you would like a copy of the new version of the code, containing the fixes, please email me and I will be happy to send it to you. I hope to figure out how to edit this thing soon.
(If this comment was disrespectful, please report it.)

 
7/24/2003 3:48:55 AM

Excellent work! Give you 5 globes. By the way, do you have new update of this code ? Could you email to me ? Thanks
(If this comment was disrespectful, please report it.)

 
7/24/2003 10:15:26 AMAndrew Carrasco

Just send me an email with your address and I'll forward it to you, no problem. I cant seem to see your e-mail address in planetsourcecode.com anywhere.
(If this comment was disrespectful, please report it.)

 
7/30/2003 5:16:16 PMAndrew Carrasco

Ok, after the proc was in production for a while I found a couple of bugs so here it is totally bulletproof (I hope). Some strange math in there for determining page lengths and such because for some reason @@FETCH_STATUS was return != 0 late. *shrug*

It works tho, so have fun!
(If this comment was disrespectful, please report it.)

 
7/30/2003 6:57:01 PM

awesome!!!!!
(If this comment was disrespectful, please report it.)

 
8/4/2003 2:54:28 PM

Is the currently posted verion the corrected version?
(If this comment was disrespectful, please report it.)

 
8/4/2003 3:25:31 PMAndrew Carrasco

Yes, the currently posted version is the correct and complete version.
(If this comment was disrespectful, please report it.)

 
10/22/2003 2:59:06 AM

Hey, this is really awesome...I'd been trying to get something like this working for a while with no luck. Thanks Andrew...you da man!
(If this comment was disrespectful, please report it.)

 
11/2/2003 12:49:01 AM

Respect ! - I thought I had figured SQL out but this is way beyond me - hehe

I messed with paging tho I used TOP and reversed sortorder to get it right. It worked but it was very hard to keep track of what was going on. Then I tried to move it into SQL-Server 2k but I ended up crashing it instead - hehe.

Correct me if I'm wrong - it looks like you are using a temp-DB - what happens if the SP doesnt complete ? Will there be leftovers from that temp-DB or does it clean out somehow ? (5 golden globes for you Andrew with thanks)

Futte (.)dk :)
(If this comment was disrespectful, please report it.)

 
11/2/2003 3:26:57 PMJohn Overton

Very nice code. Love the use of dynamic SQL.
(If this comment was disrespectful, please report it.)

 
11/3/2003 10:08:29 AMAndrew Carrasco

Futte,

YES, the temp tables will be automatically cleaned up when their containing context is released. This means, that when the proc ends, by success or by error, the temp tables are released and cleaned up.

Thanks so much for the kind comments. This proc was a true mind twister for me, and even now when I look through the code i have a hard time believing that it works. hehe

Andrew


(If this comment was disrespectful, please report it.)

 
11/3/2003 10:09:26 AMAndrew Carrasco

Thanks John, I appreciate the comments. Makes it all worth while. :-)
(If this comment was disrespectful, please report it.)

 
11/3/2003 10:20:50 AM

I have 30 million records in a single table. Will this work for me?????????????

(If this comment was disrespectful, please report it.)

 
11/3/2003 10:45:46 AMAndrew Carrasco

Man, 30 million records??? hehe

Well, in theory, the only limiting factor in this stored proc's speed is in how long it takes to open the cursor on the original table. Therefore, in THEORY, it should execute about the same amount of time no matter what the table size is.

However, I've never had a table that big to test it on. I would love it if you could give it a try and post back to here what your results were. Try running 'select *' from that table and try grabbing different pages and time how long it takes to recieved the results.

If you can do that and post your results here, that would be amazingly cool and helpful!

Thanks!!!

Andrew
(If this comment was disrespectful, please report it.)

 
2/5/2004 9:21:34 AM

It's not
(If this comment was disrespectful, please report it.)

 
2/5/2004 9:25:17 AM

Does it work with all data types?

(If this comment was disrespectful, please report it.)

 
1/6/2005 4:45:21 PM

Andrew,

I have adapted a number of these sql paging methods from various samples over the years, but this is the most complete version ever.

I need to do the same thing to one oracle table. You mention that this is even easier with oracle. I can create simple oracle sp, but have been struggling for days to translate the temp table stuff. Could you send me a link or an example of the oracel sp?

Thank you very much,
Bart
(If this comment was disrespectful, please report it.)

 
11/16/2005 12:11:14 AM_CodeJack_

In other DB's, I'm sure it would just be best to use the LIMIT clause. That will page for you. I know MySQl and Oracle have it. Make sure you use the 2 number limit, LIMIT 5,2...
(If this comment was disrespectful, please report it.)

 
3/29/2006 1:02:52 PMsql user

Hi,
We have a situation where we need to display around 15000 records from sql db 2000 with 50 records per page.

It takes over 25 seconds to display the result.
How do I exactly use this code to fix this problem ?. Please explain in detail.

Thanks for your help !

(If this comment was disrespectful, please report it.)

 
12/11/2008 3:13:15 AMRoel S. de los Santos

hi..does it works in sql server 2005..tnx.
(If this comment was disrespectful, please report it.)

 

Add Your Feedback
Your feedback will be posted below and an email sent to the author. Please remember that the author was kind enough to share this with you, so any criticisms must be stated politely, or they will be deleted. (For feedback not related to this particular code, please click here instead.)
 

To post feedback, first please login.