logo
vbRad Home
Source Code
Book Reviews
Forum
Links
About Us
Contribute

Compare Databases with SQL Effects Clarity
 
 Four ways to page through records

Posted on
11/21/2001
Author:
Robert Gelb
Email:
Not Shown
Applies To OS:
NT, 9x, 2000
Product:
5, 6



Methods (1, 2, 3, 4) are listed in the order of ease, flexibility and common sense of implementation. We provide pros and cons for each approach, followed by commentary and example code. Keep in mind that no approach will work with every situation, but some approaches are better than others. Finally check out the conclusion at the end of the document.

Method 1: Use the magic of ADO to get the records
Pros Quick & clean. It actually works.
Cons Rarely, it just goes ahead and downloads all records. You are limited to Client-side cursors, thus all the processing is done on the client or in the middle-tier.

When I first saw this method, I didn't believe it. I thought that it probably only works with SQL Server. I was wrong -- it actually works with other DBs as well. The example below connects to Sybase 12 using OLEDB provider for ODBC drivers and an old Intersolv ODBC driver for Sybase 11. Yeah, you couldn't get a tougher combo, but ADO stood the test - a testament to the fine work of the ADO team, Sybase and Intersolv. All it takes is the following code:

    Dim iRecs As Long
    Dim goConn as ADODB.Connection
    Const MaxRecordsPerPage = 20
    
    'Create a connection to the nearby Sybase 12 database	
    Set goConn = New ADODB.Connection
    goConn.ConnectionString = "Provider=MSDASQL.1;Password=pwd;" & _ 
		"User ID=uid;Data Source=Dictionary"
    goConn.Open
    
    Dim oRs As ADODB.Recordset

    Set oRs = New ADODB.Recordset
    With oRs
        'make sure CursorLocation is set to adUseClient
        .CursorLocation = adUseClient
        .ActiveConnection = goConn
		
        'insert your own query here		
        .Source = "select * from ObjectParam"
		
        'The next line is the key - it tells ADO to get data in chunks of 5 records
        .CacheSize = 5    
        
        'Here we specify the number of records to show per page - 20 in this case		
        .PageSize = MaxRecordsPerPage
        .Open
        
        'Let's jump to page 40
        .AbsolutePage = 40		
        Do While Not .EOF And iRecs < MaxRecordsPerPage
            Print .Fields(0).Value;
            
            .MoveNext
            iRecs = iRecs + 1
        Loop
        Print
    End With
So, as you see, pretty standard code: you connect to DB, set up the recordset and then open it. Of interest is the iRecs variable. It basically makes sure that we will process MaxRecordsPerPage and not more. Anyway, cool all the way around. Word to the wise, I've seen several instances where ADO ignored its CacheSize property and just went ahead and got all records. Maybe I need a service pack.


Method 2: Get all records then page through them.
Pros Easiest to program, not much processing on the database, data comes back pretty fast. Ideal if your target resultset is fairly small, not more than 1000-2000 records.
Cons Difficult to support many users on the client as multiple records quickly conspire to take a lot of memory, requires a robust & fast network (which you should have anyway).

Before you spit in disguist, at least hear me out. I agree that ferrying 20,000 records through a tired network and then getting the necessary page is a bandwidth killer. But you have to weigh other factors when making a decision on paging technique. What is your network configuration? What is your database setup? What is the weakest link in the whole arrangement? If your network is really strong and your database is overworked, then sending ton of data across the wire may not be a big deal. It takes the processing out of the database, freeing it up to do other things. Once the data is on the client, you could relatively quickly scroll to the proper page and display the appropriate records. In addition, some data sources simply make it horribly difficult to get just the records you want. Some older databases make it downright impossible, even with the newest version of ADO.


Method 3: Use a cursor in a stored procedure to scroll and return the records you need.
Pros The interface is pretty simple. Easy code for generators to generate (if you use those), very frugal with network bandwidth.
Cons Definetely requires good SQL skills and deep understanding of the databases to get good performance. Cursors take a large hit on the database. If you use MySQL or Access, which don't support stored procs or cursors, you are hosed. In addition, moving between databases maybe difficult (maybe a moot issue, since it happens rarely).

This is pretty slick. However, you will need a stored proc for each table you are generating. In addition, if you are collecting data from several tables, your SQL skills will be severely tested. Below I provided a stored procedure that accepts @StartPoint and @PageSize variables, so parameters 560, 20 will return records 560 through 580. It works on a hypothetical table with various types of banking information. The SP is lengthy, if you grok SQL, it should be easy to understand.

create proc PageScroll (@StartPoint int = 1, @PageSize int = 20)
as
declare @SourceID numeric(10,0), @DelinqTyp int, @SubPool char(8), @OidSubPool char(10),
	@ServicerID char(6), @LoanNum char(20), @ProspID char(20)
declare @Counter1 int, @Counter2 int

declare LoanCursor cursor
for select SourceID, DelinqTyp, SubPool, OidSubPool, ServicerID, LoanNum, ProspID
from Loan
order by SourceID
for read only

open LoanCursor

select @Counter1 = 1

select *
into #Loan
from Loan
where 1 = 2

if @StartPoint > 1

begin
   while (@Counter1 < @StartPoint and @@sqlstatus = 0)
      begin
         fetch LoanCursor into @SourceID, @DelinqTyp, 
                       @SubPool, @OidSubPool, @ServicerID, @LoanNum, @ProspID
         select @Counter1 = @Counter1 + 1
      end
end

-- At this point the Cursor should be set to the 
-- record before the starting point  unless @sqlstatus = 2

if (@@sqlstatus = 2)
begin
    select 'The starting Point exceeds the Number of Records in the Set'
    close LoanCursor
    return
end

select @Counter2 = 0

while (@Counter2 < @PageSize and @@sqlstatus = 0)
   begin
      fetch LoanCursor into @SourceID, @DelinqTyp, @SubPool, 
                @OidSubPool, @ServicerID, @LoanNum, @ProspID

      insert into #Loan (SourceID, DelinqTyp, SubPool, OidSubPool, 
                                     ServicerID, LoanNum, ProspID)
      values (@SourceID, @DelinqTyp, @SubPool, @OidSubPool, 
	                       @ServicerID, @LoanNum, @ProspID)

     select @Counter2 = @Counter2 + 1
   end

close LoanCursor

So, as you can see, it is a lot of code, however, because regardless of the table, the code does the same thing, this type of stored procedure is highly generatable.


Method 4: Have the stored proc dump the results of your target query into a temp table with an identity field, then get the needed page based on the identity field.
Pros The interface is simple as well. Network bandwidth and client memory are preserved for other things. Fairly simple in terms of SQL.
Cons May take a large hit on the database if the resultset is large. Requires large tempdb.

Stored procedure like this is fairly simple to crank out, but you'll pay in the long term as the number of users increases. Eventually, your tempdb will have to be huge to accomodate all the temporary tables. Plus, eventually these tables will have to be torn down. Maintanance nightmare.


Conclusion
If at all possible, go with Method 1. It is simple, elegant, easy on the network, not too hard on the database. Your middle tier might suffer a bit if you have very large number of users, but so what, get another box.



Add Your Comment  

Name: Email Address: all fields optional
Notify me via email when someone responds to this message (valid email required).

Enter the word: