• Idea Deadbeat (12/6/2010)


    Not directly related to article - but - an alternative to spt_values:

    create table #Number (number int);

    with N4000 as (select 0 as Number union all select Number+1 from N4000 where Number <4000

    )insert into #number select * from N4000 option (MAXRECURSION 4000);

    create index ix_N on #Number (Number);

    I know you're only building it once but I'd never use the hidden RBAR of a recursive CTE to build an on-the-fly Tally Table for no other reason than someone may copy the code to do something larger and really end up with a performance problem.

    I'd also use a clustered index on the table with a FILLFACTOR = 100 just in case someone wants to use the code on a server where the default FILLFACTOR has been changed to something other than 0 (100).

    Here's what I mean... the following code includes several different methods including the original recursive CTE. Yeah... I even threw in a WHILE loop because it's a big surprise for a lot of folks when compared to a recursive CTE: (Didn't include index builds because they're all the same)

    --===== Drop Tables ===========================================================================

    DROP TABLE #Number, #Number1, #Number2, #Tally, #Tally1;

    SET NOCOUNT ON;

    DBCC FREEPROCCACHE;

    WAITFOR DELAY '00:00:05'; --Let the system "settle" for several seconds

    GO

    --===== Recursive CTE w/Predefined Table (original)

    create table #Number (number int);

    with N4000 as (select 1 as Number union all select Number+1 from N4000 where Number <4000

    )insert into #number select * from N4000 option (MAXRECURSION 4000);

    GO

    --===== Recursive CTE w/SELECT/INTO

    with N4000 as (select ISNULL(1,0) as Number union all select Number+1 from N4000 where Number <4000

    )select * INTO #Number1 from N4000 option (MAXRECURSION 4000);

    GO

    --===== WHILE Loop

    CREATE TABLE #Number2 (Number INT NOT NULL);

    DECLARE @Counter INT;

    SELECT @Counter = 1;

    BEGIN TRANSACTION;

    WHILE @Counter <= 4000

    BEGIN

    INSERT INTO #Number2

    (Number)

    SELECT @Counter;

    set @Counter = @Counter + 1;

    END;

    COMMIT;

    GO

    --===== Cross Join

    SELECT TOP 4000

    N = IDENTITY(INT,1,1)

    INTO #Tally

    FROM sys.all_columns ac1

    CROSS JOIN sys.all_columns ac2

    ;

    GO

    --===== Modified Itzek Method

    WITH

    E1(N) AS ( --=== Create Ten 1's

    SELECT 1 UNION ALL SELECT 1 UNION ALL

    SELECT 1 UNION ALL SELECT 1 UNION ALL

    SELECT 1 UNION ALL SELECT 1 UNION ALL

    SELECT 1 UNION ALL SELECT 1 UNION ALL

    SELECT 1 UNION ALL SELECT 1 --10

    ),

    E2(N) AS (SELECT 1 FROM E1 a, E1 b), --100

    E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10,000

    cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT N)) FROM E4)

    SELECT N = ISNULL(N,0)

    INTO #Tally1

    FROM cteTally

    WHERE N <= 4000

    ;

    And, here are the results from SQL Profiler. Even the WHILE loop beat the recursive CTE! 😛

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