代码之家  ›  专栏  ›  技术社区  ›  KM.

创建和填充数字表的最佳方法是什么?

  •  56
  • KM.  · 技术社区  · 15 年前

    我见过许多创建和填充数字表的不同方法。但是,创建和填充一个的最佳方法是什么?“最佳”的定义从最重要到最不重要:

    • 使用最佳索引创建的表
    • 最快生成的行数
    • 用于创建和填充的简单代码

    如果您不知道数字表是什么,请看这里: Why should I consider using an auxiliary numbers table?

    11 回复  |  直到 6 年前
        1
  •  119
  •   Community pid    6 年前

    下面是一些代码示例,摘自Web和这个问题的答案。

    对于每个方法,我都修改了原始代码,因此每个方法都使用相同的表和列:numberstest和number,其中有10000行或尽可能接近这一行。此外,我还提供了到原产地的链接。

    方法1 这里有一个非常缓慢的循环方法 here
    平均13.01秒
    最多删除3次,这里是以秒为单位的次数:12.42,13.60

    DROP TABLE NumbersTest
    DECLARE @RunDate datetime
    SET @RunDate=GETDATE()
    CREATE TABLE NumbersTest(Number INT IDENTITY(1,1)) 
    SET NOCOUNT ON
    WHILE COALESCE(SCOPE_IDENTITY(), 0) < 100000
    BEGIN 
        INSERT dbo.NumbersTest DEFAULT VALUES 
    END
    SET NOCOUNT OFF
    -- Add a primary key/clustered index to the numbers table
    ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
    PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE())/1000.0)+' seconds'
    SELECT COUNT(*) FROM NumbersTest
    

    方法2 这是一个更快的循环从 here
    平均1.1658秒
    跑11次最高,这里是以秒为单位的次数:1.117,1.140,1.203,1.170,1.173,1.156,1.203,1.153,1.173,1.170

    DROP TABLE NumbersTest
    DECLARE @RunDate datetime
    SET @RunDate=GETDATE()
    CREATE TABLE NumbersTest (Number INT NOT NULL);
    DECLARE @i INT;
    SELECT @i = 1;
    SET NOCOUNT ON
    WHILE @i <= 10000
    BEGIN
        INSERT INTO dbo.NumbersTest(Number) VALUES (@i);
        SELECT @i = @i + 1;
    END;
    SET NOCOUNT OFF
    ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
    PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE())/1000.0)+' seconds'
    SELECT COUNT(*) FROM NumbersTest
    

    方法3 这是一个基于代码的插入 here
    平均488.6毫秒
    运行11次删除最多,这里是以毫秒为单位的次数:686、673、623、686343343376360343453

    DROP TABLE NumbersTest
    DECLARE @RunDate datetime
    SET @RunDate=GETDATE()
    CREATE TABLE NumbersTest (Number  int  not null)  
    ;WITH Nums(Number) AS
    (SELECT 1 AS Number
     UNION ALL
     SELECT Number+1 FROM Nums where Number<10000
    )
    insert into NumbersTest(Number)
        select Number from Nums option(maxrecursion 10000)
    ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
    PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE()))+' milliseconds'
    SELECT COUNT(*) FROM NumbersTest
    

    方法4 这里有一个“半循环”方法 here 平均348.3毫秒(很难获得好的时间,因为代码中间有“go”,任何建议都会受到赞赏)
    删除次数最多11次,这里是以毫秒为单位的次数:356、360、283、346、360、376、326、373、330、373

    DROP TABLE NumbersTest
    DROP TABLE #RunDate
    CREATE TABLE #RunDate (RunDate datetime)
    INSERT INTO #RunDate VALUES(GETDATE())
    CREATE TABLE NumbersTest (Number int NOT NULL);
    INSERT NumbersTest values (1);
    GO --required
    INSERT NumbersTest SELECT Number + (SELECT COUNT(*) FROM NumbersTest) FROM NumbersTest
    GO 14 --will create 16384 total rows
    ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
    SELECT CONVERT(varchar(20),datediff(ms,RunDate,GETDATE()))+' milliseconds' FROM #RunDate
    SELECT COUNT(*) FROM NumbersTest
    

    方法5 这里有一个插页 Philip Kelley's answer
    平均92.7毫秒
    以毫秒为单位,取11次,取最大值:80、96、96、93、110、110、80、76、93、93。

    DROP TABLE NumbersTest
    DECLARE @RunDate datetime
    SET @RunDate=GETDATE()
    CREATE TABLE NumbersTest (Number  int  not null)  
    ;WITH
      Pass0 as (select 1 as C union all select 1), --2 rows
      Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
      Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
      Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
      Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
      --I removed Pass5, since I'm only populating the Numbers table to 10,000
      Tally as (select row_number() over(order by C) as Number from Pass4)
    INSERT NumbersTest
            (Number)
        SELECT Number
            FROM Tally
            WHERE Number <= 10000
    ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
    PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE()))+' milliseconds'
    SELECT COUNT(*) FROM NumbersTest
    

    方法6 这里有一个插页 Mladen Prajdic answer
    平均82.3毫秒
    以毫秒为单位,取11次的次数最多:80、80、93、76、93、63、93、76、93、76。

    DROP TABLE NumbersTest
    DECLARE @RunDate datetime
    SET @RunDate=GETDATE()
    CREATE TABLE NumbersTest (Number  int  not null)  
    INSERT INTO NumbersTest(Number)
    SELECT TOP 10000 row_number() over(order by t1.number) as N
    FROM master..spt_values t1 
        CROSS JOIN master..spt_values t2
    ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number);
    PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE()))+' milliseconds'
    SELECT COUNT(*) FROM NumbersTest
    

    方法7 这是一个基于代码的插入 在这里
    平均56.3毫秒
    以毫秒为单位,取11次的次数最多:63、50、63、46、60、63、63、46、63、46。

    DROP TABLE NumbersTest
    DECLARE @RunDate datetime
    SET @RunDate=GETDATE()
    SELECT TOP 10000 IDENTITY(int,1,1) AS Number
        INTO NumbersTest
        FROM sys.objects s1       --use sys.columns if you don't get enough rows returned to generate all the numbers you need
        CROSS JOIN sys.objects s2 --use sys.columns if you don't get enough rows returned to generate all the numbers you need
    ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
    PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE()))+' milliseconds'
    SELECT COUNT(*) FROM NumbersTest
    

    在研究了所有这些方法之后,我真的很喜欢方法7,它是最快的,代码也相当简单。

        2
  •  49
  •   Mladen Prajdic    15 年前

    我用的这个速度和地狱一样快:

    insert into Numbers(N)
    select top 1000000 row_number() over(order by t1.number) as N
    from   master..spt_values t1 
           cross join master..spt_values t2
    
        3
  •  17
  •   Bacon Bits    7 年前

    如果只是在SQL Server Management Studio或sqlcmd中执行此操作,则可以使用批处理分隔符来重复批处理:

    CREATE TABLE Number (N INT IDENTITY(1,1) PRIMARY KEY NOT NULL);
    GO
    
    INSERT INTO Number DEFAULT VALUES;
    GO 100000
    

    这将向 Numbers 表。

    很慢。它与@km'答案中的方法1进行了比较,后者是示例中最慢的。然而,它是关于作为代码光,因为它得到。通过在插入批处理之后添加主键约束,可以稍微加快速度。

        4
  •  10
  •   Philip Kelley    15 年前

    我从下面的模板开始,它是从itzik ben gan的例程的大量打印中派生出来的:

    ;WITH
      Pass0 as (select 1 as C union all select 1), --2 rows
      Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
      Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
      Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
      Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
      Pass5 as (select 1 as C from Pass4 as A, Pass4 as B),--4,294,967,296 rows
      Tally as (select row_number() over(order by C) as Number from Pass5)
     select Number from Tally where Number <= 1000000
    

    “where n<=1000000”条款将输出限制为100万到100万,并且可以轻松地调整到所需的范围。

    因为这是一个WITH子句,所以可以将其转换为一个insert…选择…像这样:

    --  Sample use: create one million rows
    CREATE TABLE dbo.Example (ExampleId  int  not null)  
    
    DECLARE @RowsToCreate int
    SET @RowsToCreate = 1000000
    
    --  "Table of numbers" data generator, as per Itzik Ben-Gan (from multiple sources)
    ;WITH
      Pass0 as (select 1 as C union all select 1), --2 rows
      Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
      Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
      Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
      Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
      Pass5 as (select 1 as C from Pass4 as A, Pass4 as B),--4,294,967,296 rows
      Tally as (select row_number() over(order by C) as Number from Pass5)
    INSERT Example (ExampleId)
     select Number
      from Tally
      where Number <= @RowsToCreate
    

    在表构建之后索引它将是索引它的最快方法。

    哦,我会把它称为“计数”表。我认为这是一个常见的术语,你可以通过谷歌搜索找到大量的技巧和例子。

        5
  •  4
  •   Denis Pitcher    10 年前

    对于任何寻找Azure解决方案的人

    SET NOCOUNT ON    
    CREATE TABLE Numbers (n bigint PRIMARY KEY)    
    GO    
    DECLARE @numbers table(number int);  
    WITH numbers(number) as  (   
    SELECT 1 AS number   
    UNION all   
    SELECT number+1 FROM numbers WHERE number<10000  
    )  
    INSERT INTO @numbers(number)  
    SELECT number FROM numbers OPTION(maxrecursion 10000)
    INSERT INTO Numbers(n)  SELECT number FROM @numbers
    

    源于SQL Azure团队博客 http://azure.microsoft.com/blog/2010/09/16/create-a-numbers-table-in-sql-azure/

        6
  •  2
  •   paxdiablo    15 年前

    我主要使用数字表来模拟BIRT中的报告,而不必乱弄记录集的动态创建。

    我对日期也做了同样的事情,有一个从过去10年到未来10年的表格(以及一天中的几个小时来进行更详细的报告)。这是一个很好的技巧来获得价值 全部的 日期,即使您的“真实”数据表没有数据。

    我有一个脚本,我用它来创建这些,比如(这是从内存中得到的):

    drop table numbers; commit;
    create table numbers (n integer primary key); commit;
    insert into numbers values (0); commit;
    insert into numbers select n+1 from numbers; commit;
    insert into numbers select n+2 from numbers; commit;
    insert into numbers select n+4 from numbers; commit;
    insert into numbers select n+8 from numbers; commit;
    insert into numbers select n+16 from numbers; commit;
    insert into numbers select n+32 from numbers; commit;
    insert into numbers select n+64 from numbers; commit;
    

    每行的行数加倍,因此生成真正巨大的表不需要太多。

    我不确定我是否同意你的观点,快速创造是很重要的,因为你只创造了一次。它的成本分摊到所有访问它的路径上,这使得时间相当微不足道。

        7
  •  2
  •   Alex    9 年前

    以下是一些额外的方法:
    方法1

    IF OBJECT_ID('dbo.Numbers', 'U') IS NOT NULL
        DROP TABLE dbo.Numbers
    GO
    
    CREATE TABLE Numbers (Number int NOT NULL PRIMARY KEY);
    GO
    
    DECLARE @i int = 1;
    INSERT INTO dbo.Numbers (Number) 
    VALUES (1),(2);
    
    WHILE 2*@i < 1048576
    BEGIN
        INSERT INTO dbo.Numbers (Number) 
        SELECT Number + 2*@i
        FROM dbo.Numbers;
        SET @i = @@ROWCOUNT;
    END
    GO
    
    SELECT COUNT(*) FROM Numbers AS RowCownt --1048576 rows
    

    方法2

    IF OBJECT_ID('dbo.Numbers', 'U') IS NOT NULL
        DROP TABLE dbo.Numbers
    GO
    
    CREATE TABLE dbo.Numbers (Number int NOT NULL PRIMARY KEY);
    GO
    
    DECLARE @i INT = 0; 
    INSERT INTO dbo.Numbers (Number) 
    VALUES (1);
    
    WHILE @i <= 9
    BEGIN
        INSERT INTO dbo.Numbers (Number)
        SELECT N.Number + POWER(4, @i) * D.Digit 
        FROM dbo.Numbers AS N
            CROSS JOIN (VALUES(1),(2),(3)) AS D(Digit)
        ORDER BY D.Digit, N.Number
        SET @i = @i + 1;
    END
    GO
    
    SELECT COUNT(*) FROM dbo.Numbers AS RowCownt --1048576 rows
    

    方法3

    IF OBJECT_ID('dbo.Numbers', 'U') IS NOT NULL
        DROP TABLE dbo.Numbers
    GO
    
    CREATE TABLE Numbers (Number int identity NOT NULL PRIMARY KEY, T bit NULL);
    
    WITH
        T1(T) AS (SELECT T FROM (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) AS T(T)) --10 rows
       ,T2(T) AS (SELECT A.T FROM T1 AS A CROSS JOIN T1 AS B CROSS JOIN T1 AS C) --1,000 rows
       ,T3(T) AS (SELECT A.T FROM T2 AS A CROSS JOIN T2 AS B CROSS JOIN T2 AS C) --1,000,000,000 rows
    
    INSERT INTO dbo.Numbers(T)
    SELECT TOP (1048576) NULL
    FROM T3;
    
    ALTER TABLE Numbers
        DROP COLUMN T; 
    GO
    
    SELECT COUNT(*) FROM dbo.Numbers AS RowCownt --1048576 rows
    

    方法4 ,取自 Defensive Database Programming 亚历克斯·库兹涅佐夫的书

    IF OBJECT_ID('dbo.Numbers', 'U') IS NOT NULL
        DROP TABLE dbo.Numbers
    GO
    
    CREATE TABLE Numbers (Number int NOT NULL PRIMARY KEY);
    GO
    
    DECLARE @i INT = 1 ; 
    INSERT INTO dbo.Numbers (Number) 
    VALUES (1);
    
    WHILE @i < 524289 --1048576
    BEGIN; 
        INSERT INTO dbo.Numbers (Number) 
        SELECT Number + @i 
        FROM dbo.Numbers; 
        SET @i = @i * 2 ; 
    END
    GO
    
    SELECT COUNT(*) FROM dbo.Numbers AS RowCownt --1048576 rows
    

    方法5 ,取自 Arrays and Lists in SQL Server 2005 and Beyond 文章作者:Erland Sommarskog

    IF OBJECT_ID('dbo.Numbers', 'U') IS NOT NULL
        DROP TABLE dbo.Numbers
    GO
    
    CREATE TABLE Numbers (Number int NOT NULL PRIMARY KEY);
    GO
    
    WITH digits (d) AS (
       SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL
       SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL
       SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL
       SELECT 0)
    INSERT INTO Numbers (Number)
       SELECT Number
       FROM   (SELECT i.d + ii.d * 10 + iii.d * 100 + iv.d * 1000 +
                      v.d * 10000 + vi.d * 100000 AS Number
               FROM   digits i
               CROSS  JOIN digits ii
               CROSS  JOIN digits iii
               CROSS  JOIN digits iv
               CROSS  JOIN digits v
               CROSS  JOIN digits vi) AS Numbers
       WHERE  Number > 0
    GO
    
    SELECT COUNT(*) FROM dbo.Numbers AS RowCownt --999999 rows
    

    总结:
    在这5种方法中,方法3似乎是最快的。

        8
  •  2
  •   Luke Kubat    7 年前

    我知道这个线程已经过时了,可以回答,但是有一种方法可以从方法7中挤出一点额外的性能:

    而不是这样(基本上是方法7,但有一些易于使用的抛光剂):

    DECLARE @BIT AS BIT = 0
    IF OBJECT_ID('tempdb..#TALLY') IS NOT NULL
      DROP TABLE #TALLY
    DECLARE @RunDate datetime
    SET @RunDate=GETDATE()
    SELECT TOP 10000 IDENTITY(int,1,1) AS Number
        INTO #TALLY
        FROM sys.objects s1       --use sys.columns if you don't get enough rows returned to generate all the numbers you need
        CROSS JOIN sys.objects s2 --use sys.co
    ALTER TABLE #TALLY ADD PRIMARY KEY(Number)
    PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE()))+' milliseconds'
    

    试试这个:

    DECLARE @BIT AS BIT = 0
    IF OBJECT_ID('tempdb..#TALLY') IS NOT NULL
      DROP TABLE #TALLY
    DECLARE @RunDate datetime
    SET @RunDate=GETDATE()
    SELECT TOP 10000 IDENTITY(int,1,1) AS Number
        INTO #TALLY
        FROM        (SELECT @BIT [X] UNION ALL SELECT @BIT) [T2]
        CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T4]
        CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T8]
        CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T16]
        CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T32]
        CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T64]
        CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T128]
        CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T256]
        CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T512]
        CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T1024]
        CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T2048]
        CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T4096]
        CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T8192]
        CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T16384]
    ALTER TABLE #TALLY ADD PRIMARY KEY(Number)
    PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE()))+' milliseconds'
    

    在我的服务器上,与从sys.objects中选择时的~16-20 ms相比,这需要~10 ms。它还具有不依赖于sys.objects中有多少个对象的额外好处。虽然它相当安全,但从技术上讲,它是一种依赖关系,而另一种依赖关系则会更快。我认为,如果你改变速度,速度提升会下降到使用位:

    DECLARE @BIT AS BIT = 0
    

    到:

    DECLARE @BIT AS BIGINT = 0
    

    它会给服务器上的总时间增加~8-10毫秒。也就是说,当您扩展到1000000条记录时,bigint和bigint对我的查询不再有明显的影响,但它仍然会从sys.objects运行大约680ms对730ms。

        9
  •  1
  •   Stephan    12 年前

    一些建议的方法基于系统对象(例如“sys.objects”)。他们假设这些系统对象包含足够的记录来生成我们的数字。

    我不会以任何不属于我的申请和我没有完全控制权的东西为基础。例如:这些sys表的内容可能会更改,这些表在新版本的SQL等中可能不再有效。

    作为解决方案,我们可以用记录创建自己的表。然后我们使用这个对象来代替这些与系统相关的对象(如果我们事先知道范围的话,那么所有数字的表都应该是可以的,否则我们可以使用这个对象来进行交叉连接)。

    基于CTE的解决方案工作正常,但它有与嵌套循环相关的限制。

        10
  •  1
  •   John Smith jjcaicedo    8 年前

    这里有一个简短而快速的内存解决方案,我想出了利用 Table Valued Constructors 在SQL Server 2008中介绍:

    --1,000,000 rows.  Either add/remove CROSS JOINs, or use TOP clause to modify this
    
    ;WITH v AS (SELECT * FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) v(z))
    
    SELECT N FROM (SELECT ROW_NUMBER() OVER (ORDER BY v1.z)-1 N FROM v v1 
        CROSS JOIN v v2 CROSS JOIN v v3 CROSS JOIN v v4 CROSS JOIN v v5 CROSS JOIN v v6) Nums
    

    请注意,这可以快速计算,或者(甚至更好)存储在永久表中(只需添加一个 INTO 条款之后 SELECT N 段)上有一个主键 N 提高效率的领域。

        11
  •  0
  •   Mike S    6 年前

    这是一个接受答案的重新包装-但以一种让你自己比较它们的方式-前三种算法被比较(和注释解释为什么其他方法被排除),你可以运行在你自己的设置,看看它们如何与你想要的序列大小执行。

    SET NOCOUNT ON;
    
    --
    -- Set the count of numbers that you want in your sequence ...
    --
    DECLARE @NumberOfNumbers int = 10000000;
    --
    --  Some notes on choosing a useful length for your sequence ...
    --      For a sequence of  100 numbers -- winner depends on preference of min/max/avg runtime ... (I prefer PhilKelley algo here - edit the algo so RowSet2 is max RowSet CTE)
    --      For a sequence of   1k numbers -- winner depends on preference of min/max/avg runtime ... (Sadly PhilKelley algo is generally lowest ranked in this bucket, but could be tweaked to perform better)
    --      For a sequence of  10k numbers -- a clear winner emerges for this bucket
    --      For a sequence of 100k numbers -- do not test any looping methods at this size or above ...
    --                                        the previous winner fails, a different method is need to guarantee the full sequence desired
    --      For a sequence of  1MM numbers -- the statistics aren't changing much between the algorithms - choose one based on your own goals or tweaks
    --      For a sequence of 10MM numbers -- only one of the methods yields the desired sequence, and the numbers are much closer than for smaller sequences
    
    DECLARE @TestIteration int = 0;
    DECLARE @MaxIterations int = 10;
    DECLARE @MethodName varchar(128);
    
    -- SQL SERVER 2017 Syntax/Support needed
    DROP TABLE IF EXISTS #TimingTest
    CREATE TABLE #TimingTest (MethodName varchar(128), TestIteration int, StartDate DateTime2, EndDate DateTime2, ElapsedTime decimal(38,0), ItemCount decimal(38,0), MaxNumber decimal(38,0), MinNumber decimal(38,0))
    
    --
    --  Conduct the test ...
    --
    WHILE @TestIteration < @MaxIterations
    BEGIN
        -- Be sure that the test moves forward
        SET @TestIteration += 1;
    
    /*  -- This method has been removed, as it is BY FAR, the slowest method
        -- This test shows that, looping should be avoided, likely at all costs, if one places a value / premium on speed of execution ...
    
        --
        -- METHOD - Fast looping
        --
    
        -- Prep for the test
        DROP TABLE IF EXISTS [Numbers].[Test];
        CREATE TABLE [Numbers].[Test] (Number INT NOT NULL);
    
        -- Method information
        SET @MethodName = 'FastLoop';
    
        -- Record the start of the test
        INSERT INTO #TimingTest(MethodName, TestIteration, StartDate)
        SELECT @MethodName, @TestIteration, GETDATE()
    
        -- Run the algorithm
        DECLARE @i INT = 1;
        WHILE @i <= @NumberOfNumbers
        BEGIN
            INSERT INTO [Numbers].[Test](Number) VALUES (@i);
            SELECT @i = @i + 1;
        END;
    
        ALTER TABLE [Numbers].[Test] ADD CONSTRAINT PK_Numbers_Test_Number PRIMARY KEY CLUSTERED (Number)
    
        -- Record the end of the test
        UPDATE tt
            SET 
                EndDate = GETDATE()
        FROM #TimingTest tt
        WHERE tt.MethodName = @MethodName
        and tt.TestIteration = @TestIteration
    
        -- And the stats about the numbers in the sequence
        UPDATE tt
            SET 
                ItemCount = results.ItemCount,
                MaxNumber = results.MaxNumber,
                MinNumber = results.MinNumber
        FROM #TimingTest tt
        CROSS JOIN (
            SELECT COUNT(Number) as ItemCount, MAX(Number) as MaxNumber, MIN(Number) as MinNumber FROM [Numbers].[Test]
        ) results
        WHERE tt.MethodName = @MethodName
        and tt.TestIteration = @TestIteration
    */
    
    /*  -- This method requires GO statements, which would break the script, also - this answer does not appear to be the fastest *AND* seems to perform "magic"
        --
        -- METHOD - "Semi-Looping"
        --
    
        -- Prep for the test
        DROP TABLE IF EXISTS [Numbers].[Test];
        CREATE TABLE [Numbers].[Test] (Number INT NOT NULL);
    
        -- Method information
        SET @MethodName = 'SemiLoop';
    
        -- Record the start of the test
        INSERT INTO #TimingTest(MethodName, TestIteration, StartDate)
        SELECT @MethodName, @TestIteration, GETDATE()
    
        -- Run the algorithm 
        INSERT [Numbers].[Test] values (1);
    --    GO --required
    
        INSERT [Numbers].[Test] SELECT Number + (SELECT COUNT(*) FROM [Numbers].[Test]) FROM [Numbers].[Test]
    --    GO 14 --will create 16384 total rows
    
        ALTER TABLE [Numbers].[Test] ADD CONSTRAINT PK_Numbers_Test_Number PRIMARY KEY CLUSTERED (Number)
    
        -- Record the end of the test
        UPDATE tt
            SET 
                EndDate = GETDATE()
        FROM #TimingTest tt
        WHERE tt.MethodName = @MethodName
        and tt.TestIteration = @TestIteration
    
        -- And the stats about the numbers in the sequence
        UPDATE tt
            SET 
                ItemCount = results.ItemCount,
                MaxNumber = results.MaxNumber,
                MinNumber = results.MinNumber
        FROM #TimingTest tt
        CROSS JOIN (
            SELECT COUNT(Number) as ItemCount, MAX(Number) as MaxNumber, MIN(Number) as MinNumber FROM [Numbers].[Test]
        ) results
        WHERE tt.MethodName = @MethodName
        and tt.TestIteration = @TestIteration
    */
        --
        -- METHOD - Philip Kelley's algo 
        --          (needs tweaking to match the desired length of sequence in order to optimize its performance, relies more on the coder to properly tweak the algorithm)
        --
    
        -- Prep for the test
        DROP TABLE IF EXISTS [Numbers].[Test];
        CREATE TABLE [Numbers].[Test] (Number INT NOT NULL);
    
        -- Method information
        SET @MethodName = 'PhilKelley';
    
        -- Record the start of the test
        INSERT INTO #TimingTest(MethodName, TestIteration, StartDate)
        SELECT @MethodName, @TestIteration, GETDATE()
    
        -- Run the algorithm
        ; WITH
        RowSet0 as (select 1 as Item union all select 1),              --          2 rows   -- We only have to name the column in the first select, the second/union select inherits the column name
        RowSet1 as (select 1 as Item from RowSet0 as A, RowSet0 as B), --          4 rows
        RowSet2 as (select 1 as Item from RowSet1 as A, RowSet1 as B), --         16 rows
        RowSet3 as (select 1 as Item from RowSet2 as A, RowSet2 as B), --        256 rows
        RowSet4 as (select 1 as Item from RowSet3 as A, RowSet3 as B), --      65536 rows (65k)
        RowSet5 as (select 1 as Item from RowSet4 as A, RowSet4 as B), -- 4294967296 rows (4BB)
        -- Add more RowSetX to get higher and higher numbers of rows    
        -- Each successive RowSetX results in squaring the previously available number of rows
        Tally   as (select row_number() over (order by Item) as Number from RowSet5) -- This is what gives us the sequence of integers, always select from the terminal CTE expression
        -- Note: testing of this specific use case has shown that making Tally as a sub-query instead of a terminal CTE expression is slower (always) - be sure to follow this pattern closely for max performance
        INSERT INTO [Numbers].[Test] (Number)
        SELECT o.Number
        FROM Tally o
        WHERE o.Number <= @NumberOfNumbers
    
        ALTER TABLE [Numbers].[Test] ADD CONSTRAINT PK_Numbers_Test_Number PRIMARY KEY CLUSTERED (Number)
    
        -- Record the end of the test
        UPDATE tt
            SET 
                EndDate = GETDATE()
        FROM #TimingTest tt
        WHERE tt.MethodName = @MethodName
        and tt.TestIteration = @TestIteration
    
        -- And the stats about the numbers in the sequence
        UPDATE tt
            SET 
                ItemCount = results.ItemCount,
                MaxNumber = results.MaxNumber,
                MinNumber = results.MinNumber
        FROM #TimingTest tt
        CROSS JOIN (
            SELECT COUNT(Number) as ItemCount, MAX(Number) as MaxNumber, MIN(Number) as MinNumber FROM [Numbers].[Test]
        ) results
        WHERE tt.MethodName = @MethodName
        and tt.TestIteration = @TestIteration
    
        --
        -- METHOD - Mladen Prajdic answer
        --
    
        -- Prep for the test
        DROP TABLE IF EXISTS [Numbers].[Test];
        CREATE TABLE [Numbers].[Test] (Number INT NOT NULL);
    
        -- Method information
        SET @MethodName = 'MladenPrajdic';
    
        -- Record the start of the test
        INSERT INTO #TimingTest(MethodName, TestIteration, StartDate)
        SELECT @MethodName, @TestIteration, GETDATE()
    
        -- Run the algorithm
        INSERT INTO [Numbers].[Test](Number)
        SELECT TOP (@NumberOfNumbers) row_number() over(order by t1.number) as N
        FROM master..spt_values t1 
        CROSS JOIN master..spt_values t2
    
        ALTER TABLE [Numbers].[Test] ADD CONSTRAINT PK_Numbers_Test_Number PRIMARY KEY CLUSTERED (Number)
    
        -- Record the end of the test
        UPDATE tt
            SET 
                EndDate = GETDATE()
        FROM #TimingTest tt
        WHERE tt.MethodName = @MethodName
        and tt.TestIteration = @TestIteration
    
        -- And the stats about the numbers in the sequence
        UPDATE tt
            SET 
                ItemCount = results.ItemCount,
                MaxNumber = results.MaxNumber,
                MinNumber = results.MinNumber
        FROM #TimingTest tt
        CROSS JOIN (
            SELECT COUNT(Number) as ItemCount, MAX(Number) as MaxNumber, MIN(Number) as MinNumber FROM [Numbers].[Test]
        ) results
        WHERE tt.MethodName = @MethodName
        and tt.TestIteration = @TestIteration
    
        --
        -- METHOD - Single INSERT
        -- 
    
        -- Prep for the test
        DROP TABLE IF EXISTS [Numbers].[Test];
        -- The Table creation is part of this algorithm ...
    
        -- Method information
        SET @MethodName = 'SingleInsert';
    
        -- Record the start of the test
        INSERT INTO #TimingTest(MethodName, TestIteration, StartDate)
        SELECT @MethodName, @TestIteration, GETDATE()
    
        -- Run the algorithm
        SELECT TOP (@NumberOfNumbers) IDENTITY(int,1,1) AS Number
        INTO [Numbers].[Test]
        FROM sys.objects s1       -- use sys.columns if you don't get enough rows returned to generate all the numbers you need
        CROSS JOIN sys.objects s2 -- use sys.columns if you don't get enough rows returned to generate all the numbers you need
    
        ALTER TABLE [Numbers].[Test] ADD CONSTRAINT PK_Numbers_Test_Number PRIMARY KEY CLUSTERED (Number)
    
        -- Record the end of the test
        UPDATE tt
            SET 
                EndDate = GETDATE()
        FROM #TimingTest tt
        WHERE tt.MethodName = @MethodName
        and tt.TestIteration = @TestIteration
    
        -- And the stats about the numbers in the sequence
        UPDATE tt
            SET 
                ItemCount = results.ItemCount,
                MaxNumber = results.MaxNumber,
                MinNumber = results.MinNumber
        FROM #TimingTest tt
        CROSS JOIN (
            SELECT COUNT(Number) as ItemCount, MAX(Number) as MaxNumber, MIN(Number) as MinNumber FROM [Numbers].[Test]
        ) results
        WHERE tt.MethodName = @MethodName
        and tt.TestIteration = @TestIteration
    END
    
    -- Calculate the timespan for each of the runs
    UPDATE tt
        SET
            ElapsedTime = DATEDIFF(MICROSECOND, StartDate, EndDate)
    FROM #TimingTest tt
    
    --
    -- Report the results ...
    --
    SELECT 
        MethodName, AVG(ElapsedTime) / AVG(ItemCount) as TimePerRecord, CAST(AVG(ItemCount) as bigint) as SequenceLength,
        MAX(ElapsedTime) as MaxTime, MIN(ElapsedTime) as MinTime,
        MAX(MaxNumber) as MaxNumber, MIN(MinNumber) as MinNumber
    FROM #TimingTest tt
    GROUP by tt.MethodName
    ORDER BY TimePerRecord ASC, MaxTime ASC, MinTime ASC