How to get prev and next record from a selection

  • IF OBJECT_ID('tempdb..#Data') IS NOT NULL DROP TABLE #Data;

    create table #Data([ID] [Int],[Sub][Int], [Data][Int])

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (1,1,102)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (2,1,65)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (3,1,85)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (4,1,73)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (5,2,12)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (6,2,234)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (7,2,25)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (8,2,2476)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (9,3,45)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (10,3,5)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (11,3,15)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (12,3,54)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (13,4,512)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (14,4,24)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (15,4,24)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (16,4,2)

    CREATE CLUSTERED INDEX ip_tID ON #Data(ID);

    SELECT * FROM #Data

    IF OBJECT_ID('tempdb..#Data') IS NOT NULL DROP TABLE #Data;

    Say my selection is WHERE ID = 3

    Then my prev ID would be 2 and my next ID would be 4

    and

    Say my selection is WHERE ID = 1

    Then my prev ID would be <nothing> and my next ID would be 2

    and

    Say my selection is WHERE ID = 5

    Then my prev ID would be 4 and my next ID would be 6

    How do I get two recordsets of the NEXT and PREV either in two separate select statments.

    Any ideas.:-)

  • I'm not sure I follow what you are trying to achieve, but this might be what you are looking for, however it will likely be expensive to run:

    select id, sub, data,

    (select max(id) from #data where ID < D.ID) as PrevID,

    (select min(id) from #data where ID > D.ID) as NextID

    from #Data D

    --modified per Jim's comment on the typo---Thanks Jim!

    --------------------------------------------------------------------------
    When you realize you've dug yourself into a hole....Step 1...stop digging.

  • Chazman - nice work. The min select should be aliased as NextID instead. Minor typo.

    I agree with Chazman - although this will work, it is not a nice, cheap way of getting records.

    Are you trying to loop from within SQL server (cursor, while loop)? If so, is that really necessary. Think of your data in SETs rather than rows. Jeff Moden calls this RBAR (Row By Agonizing Row). The power of SQL server over older technologies is that SQL works with data as Sets. It is slowest if we disregard that and loop.

    If you are looping outside of SQL (C#, VB.NET) then the code is quite different of course.

    Jim

    Jim Murphy
    http://www.sqlwatchmen.com
    @SQLMurph

  • [Jim].[dba].[Murphy] (3/10/2011)


    Chazman - nice work. The min select should be aliased as NextID instead. Minor typo.

    I agree with Chazman - although this will work, it is not a nice, cheap way of getting records.

    Are you trying to loop from within SQL server (cursor, while loop)? If so, is that really necessary. Think of your data in SETs rather than rows. Jeff Moden calls this RBAR (Row By Agonizing Row). The power of SQL server over older technologies is that SQL works with data as Sets. It is slowest if we disregard that and loop.

    If you are looping outside of SQL (C#, VB.NET) then the code is quite different of course.

    Jim

    Now-a-days, the MIN/MAX method will work quite well here depending, of course, on effective indexing. Correlated subqueries are usually well absorbed into the execution plan and aren't the RBAR that a lot of folks think they are provided that a "Triangular Join" isn't formed (like some folks do to form a running total, for example). It's like having a formula in a CROSS APPLY. It's treated as if it were a "view".

    Make no doubt about, though... correlated subqueries USED to be a form of RBAR but MS has fixed that little problem over time.

    --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)
    Intro to Tally Tables and Functions

  • That's good to know Jeff. Thanks!

    Jim

    Jim Murphy
    http://www.sqlwatchmen.com
    @SQLMurph

  • Here's another method to accomplish it, utilizing self-joins:

    SELECT t1.*,

    PrevId = t2.ID,

    PrevSub = t2.Sub,

    PrevData = t2.DATA,

    NextId = t3.Id,

    NextSub = t3.Sub,

    NextData = t3.DATA

    FROM #Data t1

    LEFT JOIN #Data t2 ON t1.ID = t2.ID+1

    LEFT JOIN #Data t3 ON t1.ID = t3.ID-1

    Wayne
    Microsoft Certified Master: SQL Server 2008
    Author - SQL Server T-SQL Recipes


    If you can't explain to another person how the code that you're copying from the internet works, then DON'T USE IT on a production system! After all, you will be the one supporting it!
    Links:
    For better assistance in answering your questions
    Performance Problems
    Common date/time routines
    Understanding and Using APPLY Part 1 & Part 2

  • IF OBJECT_ID('tempdb..#Data') IS NOT NULL DROP TABLE #Data;

    create table #Data([ID] [Int],[Sub][Int], [Data][Int])

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (1,1,102)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (2,1,65)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (3,1,85)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (6,1,73)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (7,2,12)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (8,2,234)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (10,2,25)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (11,2,2476)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (12,3,45)

    INSERT INTO #Data ([ID], [Sub],[Data]) VALUES (13,3,5)

    CREATE CLUSTERED INDEX ip_tID ON #Data(ID);

    SELECT * FROM #Data

    IF OBJECT_ID('tempdb..#Data') IS NOT NULL DROP TABLE #Data;

    Thanks for help, have to add one more complication.

    What happens when index numbers are missing like at 3 to 6 and 8 to 10??:-)

    The reason why I ask, is that its for my blog next and prev page. So when page 2 selected, I can populate a menu with next and prev.

  • my query would just show you the next closest value, so you should be all set.

    --------------------------------------------------------------------------
    When you realize you've dug yourself into a hole....Step 1...stop digging.

  • Yes the MAX(ID) does do that, thanks.

    This code does not...

    SELECT t1.*,

    PrevId = t2.ID,

    PrevSub = t2.Sub,

    PrevData = t2.DATA,

    NextId = t3.Id,

    NextSub = t3.Sub,

    NextData = t3.DATA

    FROM #Data t1

    LEFT JOIN #Data t2 ON t1.ID = t2.ID+1

    LEFT JOIN #Data t3 ON t1.ID = t3.ID-1

    OK I am good, thanks all !!:-D

  • Digs (3/10/2011)


    Yes the MAX(ID) does do that, thanks.

    This code does not...

    SELECT t1.*,

    PrevId = t2.ID,

    PrevSub = t2.Sub,

    PrevData = t2.DATA,

    NextId = t3.Id,

    NextSub = t3.Sub,

    NextData = t3.DATA

    FROM #Data t1

    LEFT JOIN #Data t2 ON t1.ID = t2.ID+1

    LEFT JOIN #Data t3 ON t1.ID = t3.ID-1

    OK I am good, thanks all !!:-D

    Easily fixed... and probably more efficient than correlated sub-queries.

    ;

    WITH CTE AS

    (

    SELECT ID, Sub, Data,

    RN = ROW_NUMBER() OVER (ORDER BY ID)

    FROM #Data

    )

    SELECT t1.ID, t1.Sub, t1.Data,

    PrevId = t2.ID,

    PrevSub = t2.Sub,

    PrevData = t2.DATA,

    NextId = t3.Id,

    NextSub = t3.Sub,

    NextData = t3.DATA

    FROM CTE t1

    LEFT JOIN CTE t2 ON t1.RN = t2.RN+1

    LEFT JOIN CTE t3 ON t1.RN = t3.RN-1;

    Wayne
    Microsoft Certified Master: SQL Server 2008
    Author - SQL Server T-SQL Recipes


    If you can't explain to another person how the code that you're copying from the internet works, then DON'T USE IT on a production system! After all, you will be the one supporting it!
    Links:
    For better assistance in answering your questions
    Performance Problems
    Common date/time routines
    Understanding and Using APPLY Part 1 & Part 2

  • Thank you 🙂

  • Welcome!:-D

    Wayne
    Microsoft Certified Master: SQL Server 2008
    Author - SQL Server T-SQL Recipes


    If you can't explain to another person how the code that you're copying from the internet works, then DON'T USE IT on a production system! After all, you will be the one supporting it!
    Links:
    For better assistance in answering your questions
    Performance Problems
    Common date/time routines
    Understanding and Using APPLY Part 1 & Part 2

  • WayneS (3/10/2011)


    and probably more efficient than correlated sub-queries.

    There's only one way to know, huh? 😉

    --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)
    Intro to Tally Tables and Functions

  • Jeff Moden (3/10/2011)


    WayneS (3/10/2011)


    and probably more efficient than correlated sub-queries.

    There's only one way to know, huh? 😉

    As usual: It depends...

    ...and it depends a LOT on the indexing. (The proof is coming, I promise. Trying to nail down the moving parts is like herding cats.)


    - Craig Farrell

    Never stop learning, even if it hurts. Ego bruises are practically mandatory as you learn unless you've never risked enough to make a mistake.

    For better assistance in answering your questions[/url] | Forum Netiquette
    For index/tuning help, follow these directions.[/url] |Tally Tables[/url]

    Twitter: @AnyWayDBA

  • Jeff Moden (3/10/2011)


    WayneS (3/10/2011)


    and probably more efficient than correlated sub-queries.

    There's only one way to know, huh? 😉

    I would have expected Wayne to be right, but in 2K8 - I'm getting the exact opposite. The CSQ is using the index when you have itin place - but the CTE just doesn't seem to gets its act in gear. on a 200K table the CSQ solution returns in 2.4 secs; I cancelled the CTE after 4 minutes.

    create table randomSel (id int identity(1,1) primary key clustered, altkey int)

    --go

    insert randomSel(altkey)

    select top (200000) rand(checksum(newid()))*10000 from sys.columns sc1 cross join sys.columns sc2

    create index funidx on randomsel(altkey)

    set statistics time on

    declare @fun int

    select @fun=altkey,

    @fun=(select max(altkey) from randomSel where altkey < D.altkey),

    @fun=(select min(altkey) from randomSel where altkey > D.altkey)

    from randomSel d

    declare @fun int

    ;

    WITH CTE AS

    (

    SELECT altkey,

    RN = ROW_NUMBER() OVER (ORDER BY altkey)

    FROM randomSel

    )

    SELECT @fun=t1.altkey,

    @fun= t2.altkey,

    @fun= t3.altkey

    FROM CTE t1

    LEFT JOIN CTE t2 ON t1.RN = t2.RN+1

    LEFT JOIN CTE t3 ON t1.RN = t3.RN-1;

    ----------------------------------------------------------------------------------
    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?

Viewing 15 posts - 1 through 15 (of 15 total)

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