Search EggHeadCafe's Job Board
EggHeadCafe Silverlight WPF ASP.NET VB.NET C# Excel SQL Server SharePoint
search
MicrosoftArticlesForumsFAQs
C# .NET
VB.NET
Visual Studio .NET
ADO.NET
Xml / Xslt
VB 6.0
.NET CF
GDI+
LINQ
Deployment
Security
FoxPro
Silverlight / WPF
Entity Framework
RIA Services

WebArticlesForumsFAQs
JavaScript
ASP
ASP.NET
WCF

DatabasesArticlesForumsFAQs
SQL Server
Access
Oracle
MySQL
Other Databases

OfficeArticlesForumsFAQs
Excel
Word
Powerpoint
Outlook
Publisher
Money

Non-MicrosoftArticlesForumsFAQs
NHibernate
Perl
PHP
Ruby
Java
Linux / Unix
Apple
Open Source

Operating SysArticlesForumsFAQs
Windows 7
Windows Server
Windows Vista
Windows XP
Windows Update
MAC
Linux / UNIX

Server PlatformsArticlesForumsFAQs
BizTalk
Site Server
Exhange Server
IIS

Graphic DesignArticlesForumsFAQs
Macromedia Flash
Adobe PhotoShop
Expression Blend
Expression Design
Expression Web

OtherArticlesForumsFAQs
Lounge
Subversion / CVS
Ask Dr. Dotnetsky
Active Directory
Networking
Uninstall Virus
Job Openings
Product Reviews
Search Engines
Resumes

 

SQL Server 2005 Paging Performance Tip


By Robbe Morris
Printer Friendly Version
View My Articles
177 Views
    

This quick tip demonstrates how to get the total rows as part of the paging query as well as how avoid a common coding error with joins that can harm performance. It also provides a faster alternative to the SQL Server's paging syntax using TABLE variables.


I've seen the following technique in several beginner code samples for demonstrating SQL Server 2005's ability to return paged results.

I've added the TotalRows = Count(*) OVER() line to demonstrate how return the total rows returned above and beyond the row count for the paged set.  This removes the need for a second query to get the total rows available for paging techniques in your application.  In your application, just check to make sure your resultset has records, then just grab the first record and retrieve its TotalRows column value.

Notice that in this query, the JOIN between the Orders table and the Users table is being run across all records that are found NOT just the records returned in the paged set.

  declare @StartRow int
  declare @MaxRows int

  select @StartRow = 1
  select @MaxRows = 10

   select *
     from
     (select o.*,u.FirstName,u.LastName,
         TotalRows=Count(*) OVER(),
         ROW_NUMBER() OVER(ORDER BY o.CreateDateTime desc) as RowNum
         from Orders o , Users u
   WHERE o.CreateDateTime > getdate() -30
        AND (o.UserID = u.UserID)
     )
     WHERE RowNum BETWEEN @StartRow AND (@StartRow + @MaxRows) -1

If you adjust your query as follows, you will see a substantial boost in performance.  Notice this query only performs the join on the returned resultset which is much, much smaller.

SELECT MyTable.*,u.FirstName,u.LastName
FROM
   (SELECT o.*,
       TotalRows=Count(*) OVER(),
       ROW_NUMBER() OVER(ORDER BY o.CreateDateTime desc) as RowNum
    FROM Orders o
    WHERE o.CreateDateTime > getdate() -30
   ) as MyTable, Users u
WHERE RowNum BETWEEN @StartRow AND (@StartRow + @MaxRows) -1
  and (MyTable.UserID = u.UserID)

Having recently reviewed ways to speed up the paging grids at EggHeadCafe (they were never slow but I'm always looking for ways to optimize things), I decided to put SQL Server's new paging mechanism to a test between itself and using standard queries in conjunction with TABLE variables.  Across 5 different very large tables at EggHeadCafe,  the TABLE variable option performed twice as fast as the suggested paging mechanism above.  Why I didn't of think of why much sooner is beyond me...

When you look at the inner query in the paging sample above, you notice that it is pulling back the entire Orders record for every single record in the Orders table.  Then, the outer query queries the inner table's results.

The
TABLE variable sample stored procedure syntax doesn't perform the same JOINS as above.  It is included
here just to give you an idea of how you might write your own.  Of course, depending on how often your data changes, you could
even implement a cache of the primary keys to speed this up even more.

CREATE PROCEDURE [dbo].[GetRecordsPaged]
(
  @StartRow int,
  @MaxRows int
)
AS

declare @TotalRows bigint

declare @Pager table
(
  RowNumber int IDENTITY (1, 1) Primary key NOT NULL ,
  RecordID bigint
)

-- Notice that this INSERT INTO query can get 100% of its results from the clustered primary key index.

INSERT INTO @Pager (RecordID)
SELECT RecordID
FROM dbo.Record
ORDER BY RecordID desc --
You can ORDER BY datetime columns if your primary key is not a number oriented column.

SELECT top 1 @TotalRows = COUNT(*) from @Pager -- Did this because it is a little faster than TotalRows=Count(*) OVER()

-- You would append your JOINS etc. to the result query below

SELECT Record.*,
              @TotalRows as TotalRows
FROM dbo.Record
WHERE RecordID in (SELECT RecordID
                                     FROM @Pager i
                                   WHERE i.RowNumber BETWEEN @StartRow AND (@StartRow + @MaxRows) - 1)
ORDER BY RecordID desc


Biography - Robbe Morris
Robbe has been a Microsoft MVP in C# since 2004. He is also the co-founder of EggHeadCafe.com which provides .NET articles, book reviews, software reviews, and software download and purchase advice.

button
Article Discussion: SQL Server 2005 Paging Performance Tip
Robbe Morris posted at Friday, May 16, 2008 11:22 AM
Original Article
 

Insert into , syntax error
Benedict Silla replied to Robbe Morris at Saturday, October 17, 2009 7:10 AM
Private Sub Command2_Click()
    If Trim(gStatus) = "ADD" Then
        Data1.Database.Execute "Insert into BIODATAS (Reg_No,Name_Of_Child,Father,Mother,Born_in,Date_Confirmed,BPlace,Age,God_Parent,Religion,Parent_Contact_No,Diocese,Priest,Witness,Sponsor,dbirth) values ('" & Text1.Text & "','" & Text2.Text & "','" & Text3.Text & "','" & Text4.Text & "','" & text5.Text & "','" & Text6.Text & "','" & Text8.Text & "','" & Text9.Text & "','" & Text10.Text & "','" & Text11.Text & "','" & Text12.Text & "','" & Text13.Text & "','" & Text15.Text & "','" & Text16.Text & "','" & Text17.Text & "','" & Text7.Text & "')"
        Data1.Refresh
        Data1.Recordset.MoveLast
        '--------------
        Command3.Enabled = True
        Command2.Enabled = False
        Command1.Enabled = True
    ElseIf Trim(gStatus) = "EDIT" Then
        Data1.Database.Execute "Update BIODATAS set Reg_No='" & Text1.Text & "',Name_Of_Child='" & Text2.Text & "',Father='" & Text3.Text & "',Mother='" & Text4.Text & "',Born_in='" & text5.Text & "',Date_Confirmed='" & Text6.Text & "',BPlace='" & Text7.Text & "',Age='" & Text8.Text & "',God_Parent='" & Text9.Text & "',Religion='" & Text10.Text & "',Parent_Contact_No='" & Text11.Text & "',Diocese='" & Text12.Text & "',Priest='" & Text13.Text & "' ,Witness='" & Text15.Text & "' ,Sponsor='" & Text16.Text & "',dbirth='" & Text17.Text & "'where Reg_No='" & Text1.Text & "'"
        Data1.Refresh
        '---------------
        Command3.Enabled = True
        Command2.Enabled = False
        Command1.Enabled = True
    End If
    gStatus = ""
 

This code has SQL Injection attack vulnerabilities
Robbe Morris replied to Benedict Silla at Saturday, October 17, 2009 9:21 AM

I'd address that long before I worried about syntax errors.  Search google for SQL Injection and how to prevent it.