Stored Procedure - which is faster/better?

  • The query that I posted above has been doing pretty well. I'm testing it on a 3-million row table with substantial column width (sysobjects plus about 200 more bytes) against a non-indexed lookup column and it consistently returns 20 rows in about 5-6 seconds. Even when I go two million rows in it still only takes 8 seconds. And that is on my laptop which has the distinct disadvantge of having the MDF, LDF and TempDB all on the same disk.

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

  • If you were in charge of a project, would you tolerate a 5-6 second lag time even on millions of rows? Rhetorical question, for sure because I know you wouldn't.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Thanks Jeff - would be good to see that thread..

  • I found it... it's not as robust as I thought it was, but it does have several pieces of test code along with some reasons why some works better than others. Found out why the dynamic SQL works better (forces recompile) and how to make TOP (XX) code work faster (force a recompile), etc...

    http://www.sqlservercentral.com/Forums/Topic478168-338-1.aspx#bm482005

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • That example can't really be used here Jeff because it assumes the existence of a useable RowNum column and that just doesn't exist in the general Web Page/Rows case. Most of the work in my query is actually maikng a useable row number column. And most of the rest of the time goes to dealing with the wide-rows which is also typical for the general Web-Page# problem. (If I drop these two constraints, my approach runs 100 to 1000 times faster).

    Do you have a version that works in these more general cases?

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

  • You don't actually need a numbered column using the TOP (XX) methods... just need a good set of indexes and the correct "sort".

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • None of the TOP(XX) methods that I have been testing are able to keep up with the Row_Number() technique that I posted.

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

  • I love testing... got any test code you'd like to share? Paging is always one of those PITA's that people (not you) have a hard time getting their arms around and some good quantitative test code just might help folks out.

    If not, I'll make some. Lemme know.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • OK, well I am going away for the weekend and I don't have time to clean it up, so you'll have to take it as is...

    Here is the Test-Rig (everything is SQL2005):

    Select Identity(int) as RowID --PK/Cluster index column

    --This is a unique integer column, that is NOT the PK, good for non-index tests:

    , cast(-1 as bigint)*((select count_big(*) from master..sysobjects)*o1.id + o2.id) as NegPkID

    --this is a unique string column, that only partly conforms to the PK

    , o1.xtype+o2.name + o2.xtype+o1.name + cast(o1.uid as varchar(9)) + cast(o2.uid as varchar(9)) as StrPK

    , cast(o1.id as bigint)-o2.id as DiffID --non-unique integer, for grouping

    , cast(o1.id as bigint)+o2.id as AddID --non-unique integer, for different grouping

    , o2.* --all of the object fields, but skewed from the PK

    Into MegaTest3

    From master..sysobjects o1

    Join master..sysobjects o2 ON 1=1

    ALTER TABLE MegaTest3 ADD PRIMARY KEY CLUSTERED (RowID)

    On my laptop this makes a table with over three million rows with non-trivial rows, approx. 200-300 bytes wide, with lots of good grouping and sorting columns.

    Here are the Row_Number() and TOP(XX) test queries:

    Set statistics time Off

    Declare @startingRow int, @rows int

    Select @startingRow = 1500000, @rows = 20

    print '**'

    print 'row_number() methods:'

    Set statistics time ON

    --Non-clustered, wide-result row test:

    Select * From MegaTest3 m

    Where m.NegPkId IN( Select NegPkID from

    (select NegPkID, row_number() over (order by NegPkID) RN

    from MegaTest3) r

    Where rn between @startingRow and @startingRow+@rows-1

    )

    --Non-clustered index narrow-result test:

    Select * From (select RowID, NegPkID, row_number() over (order by NegPkID) RN

    from MegaTest3) r

    Where rn between @startingRow and @startingRow+@rows

    --Clustered index wide-result test:

    Select * From (select *, row_number() over (order by RowID) RN

    from MegaTest3) r

    Where rn between @startingRow and @startingRow+@rows

    --Clustered index narrow-result test:

    Select * From (select RowID, NegPkID, row_number() over (order by RowID) RN

    from MegaTest3) r

    Where rn between @startingRow and @startingRow+@rows

    --======TOP(XX) routines

    Set statistics time Off

    print '**'

    print 'TOP(XX) routines:'

    Set statistics time ON

    --Non-clustered, wide-result row test:

    Select * From MegaTest3 m

    Where m.NegPkId IN( Select TOP (@rows) NegPkID from

    (select TOP(@startingRow+@rows) NegPkID

    from MegaTest3

    Order by NegPkID) r

    Order By NegPkID Desc)

    --Non-clustered index narrow-result test:

    Select TOP(@rows) * From (select TOP(@startingRow+@rows) RowID, NegPkID

    from MegaTest3

    Order by NegPkID) r

    Order by NegPkID Desc

    --Clustered index wide-result test:

    Select TOP(@rows) * From (select TOP(@startingRow+@rows) *

    from MegaTest3

    Order by RowID) r

    Order by RowID Desc

    --Clustered index narrow-result test:

    Select TOP(@rows) * From (select TOP(@startingRow+@rows) RowID, NegPkId

    from MegaTest3

    Order by RowID) r

    Order by RowID Desc

    A note about these tests: one of the things that you will notice is that because of the non-trival nature of the test table and the queries, caching is very significant. It usually takes 3 or 4 runs before the numbers stabilize. Also, the intermediate storage can be quite large, so physical seperation of TempDB and the MDF is likely to have a significant impact (I have not been able to test this on a real server yet).

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

  • Well, you guys are way more advanced than me!! It's impressive. I'm trying to code an ASP.NET datagrid page with custom paging, table only will have 10,000 records or so. From another SQL Server Central thread I copied this from Matt Miller:

    ----------------------------------

    if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Paging_Cursor]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)

    drop procedure [dbo].[Paging_Cursor]

    GO

    CREATE PROCEDURE Paging_Cursor (

    @Tables varchar(1000),

    @pk varchar(100),

    @sort varchar(200) = NULL,

    @PageNumber int = 1,

    @PageSize int = 10,

    @Fields varchar(1000) = '*',

    @Filter varchar(1000) = NULL,

    @Group varchar(1000) = NULL)

    AS

    /*Find the @pk type*/

    DECLARE @PKTable varchar(100)

    DECLARE @PKName varchar(100)

    DECLARE @type varchar(100)

    DECLARE @prec int

    IF CHARINDEX('.', @pk) > 0

    BEGIN

    SET @PKTable = SUBSTRING(@PK, 0, CHARINDEX('.',@PK))

    SET @PKName = SUBSTRING(@PK, CHARINDEX('.',@PK) + 1, LEN(@PK))

    END

    ELSE

    BEGIN

    SET @PKTable = @Tables

    SET @PKName = @pk

    END

    SELECT @type=t.name, @prec=c.prec

    FROM sysobjects o

    JOIN syscolumns c on o.id=c.id

    JOIN systypes t on c.xusertype=t.xusertype

    WHERE o.name = @PKTable AND c.name = @PKName

    IF CHARINDEX('char', @type) > 0

    SET @type = @type + '(' + CAST(@prec AS varchar) + ')'

    DECLARE @strPageSize varchar(50)

    DECLARE @strStartRow varchar(50)

    DECLARE @strFilter varchar(1000)

    DECLARE @strGroup varchar(1000)

    SET STATISTICS time ON

    --===== Declare the local variables

    DECLARE @PageSize INT --How many rows to appear per page

    DECLARE @PageNum INT --What page number to appear

    DECLARE @Skip INT --Working variable for where to start for page

    DECLARE @sql VARCHAR(8000)

    --===== Set the local variables for pagesize and page

    -- PageSize and PageNum would be parameters in a stored proc

    SET @PageSize = 100

    SET @PageNum = 9000

    SET @Skip = 900000

    SELECT t.RowNum

    FROM dbo.Test t,

    (

    SELECT TOP (@PageSize) test.RowNum

    FROM dbo.Test WITH (NOLOCK)

    LEFT OUTER merge JOIN (SELECT TOP(@skip) RowNum

    FROM dbo.Test WITH (NOLOCK)

    ORDER BY RowNum) r

    ON test.rownum=r.rownum

    WHERE r.rownum IS NULL

    ORDER BY test.RowNum

    ) d

    WHERE t.RowNum = d.RowNum

    SET STATISTICS time OFF

    PRINT REPLICATE('=',50)

    SET STATISTICS time ON

    SET @sql = '

    SELECT t.RowNum

    FROM dbo.Test t,

    (

    SELECT TOP '+STR(@PageSize)+' RowNum

    FROM dbo.Test WITH (NOLOCK)

    WHERE RowNum NOT IN (SELECT TOP '+STR(@Skip)+' RowNum

    FROM dbo.Test

    ORDER BY RowNum)

    ORDER BY RowNum

    ) d

    WHERE t.RowNum = d.RowNum'

    EXEC (@SQL)

    SET STATISTICS time OFF

    --------------------------------------------

    I will try to use the above, but my table doesn't have a RowNum column..! I''m rather confused by now. Can someone post some code that I can plop into my db and use it (with asp.net)?? Thanks a lot..!!

    I also saw this analysis of paging, but am so confused by now:

    http://www.codeproject.com/KB/aspnet/PagingLarge.aspx

    tx,

    matt

    matt

  • Matt -

    In your case - "Rownum" would be whatever you're using to sort your dataset (preferrably indexed if you want any kind of performance , ideally the clustered index). I think you're ordering on employeeID - so replace rownum with that.

    ----------------------------------------------------------------------------------
    Your lack of planning does not constitute an emergency on my part...unless you're my manager...or a director and above...or a really loud-spoken end-user..All right - what was my emergency again?

  • Thank you very much for your help Matt!

    p.s. is there any part of the above sql that i don't need? e.g. /*Find the @pk type*/ -

    matt

  • hmm...kind of looks to me that there's a combination of several things of code. One of which is the beginning to dynamically build the statement (based on whatever happens to be the PK), one of which is the paging process itself.

    The find the PK type doesn't look to be used at all, so unless you want to work that into your Dynamic SQL somehow - no, I don't think you need any of that (all of the way up to --==Declare the local variables)

    ----------------------------------------------------------------------------------
    Your lack of planning does not constitute an emergency on my part...unless you're my manager...or a director and above...or a really loud-spoken end-user..All right - what was my emergency again?

  • Thanks for the info Matt: I've whittled it down to this:

    CREATE PROCEDURE Paging_Cursor (

    @PageNumber int = 1,

    @PageSize int = 10)

    AS

    --===== Declare the local variables

    DECLARE @PageNum INT --What page number to appear

    DECLARE @Skip INT --Working variable for where to start for page

    DECLARE @sql VARCHAR(8000)

    --===== Set the local variables for pagesize and page

    SET @Skip = 900000

    SELECT t.RowNum

    FROM dbo.Test t,

    (

    SELECT TOP (@PageSize) test.RowNum

    FROM dbo.Test WITH (NOLOCK)

    LEFT OUTER merge JOIN (SELECT TOP(@skip) RowNum

    FROM dbo.Test WITH (NOLOCK)

    ORDER BY RowNum) r

    ON test.rownum=r.rownum

    WHERE r.rownum IS NULL

    ORDER BY test.RowNum

    ) d

    WHERE Test.RowNum = d.RowNum

    SET @sql = '

    SELECT t.RowNum

    FROM dbo.Test t,

    (

    SELECT TOP '+STR(@PageSize)+' RowNum

    FROM dbo.Test WITH (NOLOCK)

    WHERE RowNum NOT IN (SELECT TOP '+STR(@Skip)+' RowNum

    FROM dbo.Test

    ORDER BY RowNum)

    ORDER BY RowNum

    ) d

    WHERE t.RowNum = d.RowNum'

    EXEC (@SQL)

    ------------------

    However, I don't understand what @Skip = 90000 is. Do I need this - can you help?

    tx, Matt

    p.s. this will work on a table without an "identity" column, right?

  • matt6749 (5/2/2008)


    However, I don't understand what @Skip = 90000 is. Do I need this - can you help?

    It's the number of rows to skip over and should normally be set to:

    Set @Skip = @PageSize * (@PageNumber - 1)

    It appears that it is set to 90000 as a test value.

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

Viewing 15 posts - 16 through 30 (of 44 total)

You must be logged in to reply to this topic. Login to reply