The "Numbers" or "Tally" Table: What it is and how it replaces a loop

  • Yes. Apologies, it's a colloquial expression meaning 'Spot on'. And the word Rooster is apparently an Americanism for the animal featured on my Avatar.

    1772, from roost (earlier roost cock, 1606), in sense of "the roosting bird," favored in the U.S. as a puritan alternative to cock

    Hey, I'm from Derbyshire, in the UK. We are very rural...

    🙂

    Dave J


    http://glossopian.co.uk/
    "I don't know what I don't know."

  • Great stuff. This really helps in cases where we tend to think the "loop" is the solution to all problems in SQL..

    Awesome

    N

  • Thanks, Navket... I appreciate the feedback.

    If you really want to see something that replaces the loop or a cursor, take a look at the following...

    http://www.sqlservercentral.com/articles/Advanced+Querying/61716/

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

  • The tally table is wonderful -- thought I'd post another use I'm getting from it: finding a minimum numeric record key within a potentially gapping set.

    So my TableWithManualKey table might have the values 1, 2, 5, 7, 12 stored in the ManualKey due to record deletions. Here's how I quickly figure out where to fill the gap (grabbing a 3).

    declare @NewKeyValue int

    begin tran

    select @NewKeyValue = min(N)

    from someDb.dbo.Tally T

    where not exists (select 'x' from someDb.dbo.TableWithManualKey MK where T.N = MK.ManualKey)

    insert into someDb.dbo.TableWithManualKey (ManualKey, someOtherFields)

    values (@NewKeyValue, @otherValues)

    commit tran

    Obviously you have to fill the tally table up as high as your keys might go.

    Thanks!

  • Good idea, but be careful... the Tally table is not a panacea. For example, there is code that will beat the pants off the Tally table for finding missing sequences... I've got an example somewhere... lemme see if I can find it...

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

  • thomas.lamp (8/6/2008)


    The tally table is wonderful -- thought I'd post another use I'm getting from it: finding a minimum numeric record key within a potentially gapping set.

    So my TableWithManualKey table might have the values 1, 2, 5, 7, 12 stored in the ManualKey due to record deletions. Here's how I quickly figure out where to fill the gap (grabbing a 3).

    declare @NewKeyValue int

    begin tran

    select @NewKeyValue = min(N)

    from someDb.dbo.Tally T

    where not exists (select 'x' from someDb.dbo.TableWithManualKey MK where T.N = MK.ManualKey)

    insert into someDb.dbo.TableWithManualKey (ManualKey, someOtherFields)

    values (@NewKeyValue, @otherValues)

    commit tran

    Obviously you have to fill the tally table up as high as your keys might go.

    Thanks!

    Hi Thomas,

    As Jeff already said, this might not be the best way to find your first gap. Here are two alternatives that, provided the table has an index on the ManualKey column, will probably be at least as fast as the numbers table:

    INSERT INTO dbo.YourTable

    (KeyColumn, OtherColumn)

    SELECT MIN(t.KeyColumn) + 1, @OtherColumn

    FROM dbo.YourTable AS t

    WHERE NOT EXISTS

    (SELECT *

    FROM dbo.YourTable AS t2

    WHERE t2.KeyColumn = t.KeyColumn + 1);

    INSERT INTO dbo.YourTable

    (KeyColumn, OtherColumn)

    SELECT TOP (1) t.KeyColumn + 1, @OtherColumn

    FROM dbo.YourTable AS t

    WHERE NOT EXISTS

    (SELECT *

    FROM dbo.YourTable AS t2

    WHERE t2.KeyColumn = t.KeyColumn + 1)

    ORDER BY t.KeyColumn;

    You can also try using an anti-semi join instead of the NOT EXISTS, though I doubt it will make a difference - it's probably a variation that the optimizer will consider by itself anyway.

    INSERT INTO dbo.YourTable

    (KeyColumn, OtherColumn)

    SELECT MIN(t.KeyColumn) + 1, @OtherColumn

    FROM dbo.YourTable AS t

    LEFT JOIN dbo.YourTable AS t2

    ON t2.KeyColumn = t.KeyColumn + 1

    WHERE t2.KeyColumn IS NULL;

    INSERT INTO dbo.YourTable

    (KeyColumn, OtherColumn)

    SELECT TOP(1) t.KeyColumn + 1, @OtherColumn

    FROM dbo.YourTable AS t

    LEFT JOIN dbo.YourTable AS t2

    ON t2.KeyColumn = t.KeyColumn + 1

    WHERE t2.KeyColumn IS NULL

    ORDER BY t.KeyColumn;


    Hugo Kornelis, SQL Server/Data Platform MVP (2006-2016)
    Visit my SQL Server blog: https://sqlserverfast.com/blog/
    SQL Server Execution Plan Reference: https://sqlserverfast.com/epr/

  • I must say that it is very interesting and informative article. I use the strategy very frequently and have suggested many others to use this.

    Great Article...

    Atif Sheikh

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------
    Sometimes, winning is not an issue but trying.
    You can check my BLOG
    [font="Arial Black"]here[/font][/url][/right]

  • Thanks for the feedback, Atif!

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

  • Hugo Kornelis (8/7/2008)


    As Jeff already said, this might not be the best way to find your first gap. Here are two alternatives that...

    Here's another take on the same problem... test data is included...

    --===== Setup for speed and to prevent blocking

    SET NOCOUNT ON

    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

    --=============================================================================

    -- Create an experimental table to simulate the table being examined

    -- Again... 6 million rows...

    --=============================================================================

    --===== Create the experimental temp table and populate with Serial #'s on the fly

    -- This works because SysColumns always has at least 4000 entries

    -- even in a new database and 4000*4000 = 16,000,000

    SELECT TOP 6000000 SerialNumber = IDENTITY(INT, 1, 1)

    INTO #JbmTest

    FROM Master.dbo.SysColumns sc1,

    Master.dbo.SysColumns sc2

    -- --===== Like any good table, our experimental table needs a Primary Key

    ALTER TABLE #JbmTest

    ADD PRIMARY KEY CLUSTERED (SerialNumber)

    -- This deletes a "monster" range just to see how it's handled.

    DELETE #JbmTest

    WHERE SerialNumber BETWEEN 5000000 AND 5500000

    -- This deletes every third row in the first 1000 rows

    DELETE #JbmTest

    WHERE SerialNumber %3 = 0

    AND SerialNumber <= 1000

    PRINT REPLICATE('=',100)

    --=============================================================================

    -- Test the code

    --=============================================================================

    SET STATISTICS IO ON

    SET STATISTICS TIME ON

    --===== Calculated Gaps

    SELECT GapStart = (SELECT ISNULL(MAX(b.SerialNumber),0)+1

    FROM #JbmTest b

    WHERE b.SerialNumber < a.SerialNumber),

    GapEnd = SerialNumber - 1

    FROM #JbmTest a

    WHERE a.SerialNumber - 1 NOT IN (SELECT SerialNumber FROM #JbmTest)

    AND a.SerialNumber - 1 > 0

    SET STATISTICS IO OFF

    SET STATISTICS TIME OFF

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

  • I thought this to be an excellent article with super examples - I was even able to update the freight example to use the AdventureWorks database without help:P

    The question I have is you mentioned that you regularly create the tally table, do you create it in the tempdb database and just use it from there in all your queries that need it or do you put a copy in each database?

    Tim "the newbie DBA"

  • Thanks for the awesome compliment, Timothy.

    It depends on what the client is comfortable with... some won't allow it as a permanent table so I have to code it in where ever it's needed. It's very fast to build so it's not much of a problem to build it as a temp table and just go from there.

    Others insist that it be in every database (Model, too) but most want it in a Utility DB... some want it as a "permanent" table in Temp DB on boot and some want it in the Master DB.

    I've even had customers ask for it to be in a Utility DB with a pass through view in every DB.

    I guess my preferences would be to have it in every DB just for performance sake and ease of code or to have it in a Utility DB to reduce backup size a bit. The only time I backup a Utility DB is after a change or addition.

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

  • Tnx for a very enlightning article, including all comments in this thread. I have wondered many times about using SET-based techniques instead of LOOP-like techniques. This article has opened my eyes in a way that other articles could not.

    Many tnx.

    Greetz,
    Hans Brouwer

  • Thanks for the awesome feedback, Hans. You made my day. Just remember, the Tally table is not a panacea... it cannot be used for all things that loop. For example, lot's of folks have asked, but I don't know of a way to use it in SQL Server 2000 to number rows sequentially as in Ranking. There are other methods for that as in the article below...

    http://www.sqlservercentral.com/articles/Advanced+Querying/61716/

    On the other hand, the Tally table does solve things that a lot of people would actually write a loop for.

    --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, for the excellent example of how to loop through a CSV column using a Tally Table.

    I knew how to use a Tally Table for creating a series of dates, etc. but really couldn't picture how to use it instead of loops. This article explained it very clearly. I can't wait to clean up some code!

  • Thanks for the great feedback, Carla. If you want a bit more on splitting things, take a peek at the followup article below...

    http://www.sqlservercentral.com/articles/T-SQL/63003/

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

Viewing 15 posts - 166 through 180 (of 511 total)

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