【问题标题】:How to generate a range of numbers between two numbers?如何生成两个数字之间的数字范围?
【发布时间】:2014-02-20 22:18:12
【问题描述】:

我有两个数字作为用户输入,例如 10001050

如何使用 sql 查询在单独的行中生成这两个数字之间的数字?我想要这个:

 1000
 1001
 1002
 1003
 .
 .
 1050

【问题讨论】:

    标签: sql sql-server tsql sql-server-2008


    【解决方案1】:

    使用VALUES 关键字选择非持久值。然后使用JOINs 生成大量组合(可以扩展为创建数十万行甚至更多行)。

    简短而快速的版本(不是那么容易阅读):

    WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n))
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM x ones, x tens, x hundreds, x thousands
    ORDER BY 1
    

    Demo

    更详细的版本:

    SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
    FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
         (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
         (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
         (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
    ORDER BY 1
    

    Demo

    两个版本都可以使用WHERE 子句轻松扩展,将数字的输出限制在用户指定的范围内。如果你想复用它,你可以为它定义一个表值函数。

    【讨论】:

    • 你能解释一下语法吗?什么是 v(n)?
    • @Rafi v(n) 和数百(n) 等是表和列名/别名
    • 除了ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n,你也可以直接使用row_number() over (order by (select null))
    • @Rafi 简单地说,您可以将 v(n) 更改为 vals(n) 或其他任何内容。在此处查看更多信息:stackoverflow.com/questions/14155268/…
    【解决方案2】:

    另一种解决方案是递归 CTE:

    DECLARE @startnum INT=1000
    DECLARE @endnum INT=1050
    ;
    WITH gen AS (
        SELECT @startnum AS num
        UNION ALL
        SELECT num+1 FROM gen WHERE num+1<=@endnum
    )
    SELECT * FROM gen
    option (maxrecursion 10000)
    

    【讨论】:

    • 不要尝试在视图定义中使用 maxrecusion 选项。相反,您必须 SELECT * FROM CTE_VIEW OPTION (MAXRECURSION 10000) - 如果您的客户端应用程序想要按原样使用视图,则会出现问题。
    • 有一个最大值 maxrecursion 设置为 32767(在 SQL Server 2012 中)。
    • 澄清一下,如果你需要32767以上的递归,那么可以设置为0表示nomax,
    • 这个答案是Demo
    • 我将此答案与其他答案进行了比较,执行计划显示此答案(查询成本最低,)最快。
    【解决方案3】:
    SELECT DISTINCT n = number 
    FROM master..[spt_values] 
    WHERE number BETWEEN @start AND @end
    

    Demo

    请注意,此表的最大值为 2048,因为数字之间存在差距。

    这是使用系统视图的稍微好一点的方法(从 SQL-Server 2005 开始):

    ;WITH Nums AS
    (
      SELECT n = ROW_NUMBER() OVER (ORDER BY [object_id]) 
      FROM sys.all_objects 
    
    )
    SELECT n FROM Nums 
    WHERE n BETWEEN @start AND @end
    ORDER BY n;
    

    Demo

    或使用自定义的数字表。感谢 Aaron Bertrand,我建议阅读整篇文章:Generate a set or sequence without loops

    【讨论】:

    • @user3211705:注意我的编辑,这个表最多有2048个。我建议阅读整篇文章。
    • 认为你可以添加WHERE type = 'P'并避免SELECT DISTINCT
    • 你的第一个“演示”链接一直告诉我String index out of range: 33
    • 你是对的。但这似乎是 SqlFiddle 的问题。它在您的数据库中有效吗?
    • 快速注意,像这样的跨数据库查询不适用于 SQL Azure
    【解决方案4】:

    我最近写了这个内联表值函数来解决这个问题。除了内存和存储之外,它的范围不受限制。它不访问表,因此通常不需要磁盘读取或写入。它在每次迭代中以指数方式添加连接值,因此即使对于非常大的范围也非常快。它在五秒钟内在我的服务器上创建了一千万条记录。它也适用于负值。

    CREATE FUNCTION [dbo].[fn_ConsecutiveNumbers]
    (   
        @start int,
        @end  int
    ) RETURNS TABLE 
    RETURN 
    
    select
        x268435456.X
        | x16777216.X
        | x1048576.X
        | x65536.X
        | x4096.X
        | x256.X
        | x16.X
        | x1.X
        + @start
         X
    from
    (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15)) as x1(X)
    join
    (VALUES (0),(16),(32),(48),(64),(80),(96),(112),(128),(144),(160),(176),(192),(208),(224),(240)) as x16(X)
    on x1.X <= @end-@start and x16.X <= @end-@start
    join
    (VALUES (0),(256),(512),(768),(1024),(1280),(1536),(1792),(2048),(2304),(2560),(2816),(3072),(3328),(3584),(3840)) as x256(X)
    on x256.X <= @end-@start
    join
    (VALUES (0),(4096),(8192),(12288),(16384),(20480),(24576),(28672),(32768),(36864),(40960),(45056),(49152),(53248),(57344),(61440)) as x4096(X)
    on x4096.X <= @end-@start
    join
    (VALUES (0),(65536),(131072),(196608),(262144),(327680),(393216),(458752),(524288),(589824),(655360),(720896),(786432),(851968),(917504),(983040)) as x65536(X)
    on x65536.X <= @end-@start
    join
    (VALUES (0),(1048576),(2097152),(3145728),(4194304),(5242880),(6291456),(7340032),(8388608),(9437184),(10485760),(11534336),(12582912),(13631488),(14680064),(15728640)) as x1048576(X)
    on x1048576.X <= @end-@start
    join
    (VALUES (0),(16777216),(33554432),(50331648),(67108864),(83886080),(100663296),(117440512),(134217728),(150994944),(167772160),(184549376),(201326592),(218103808),(234881024),(251658240)) as x16777216(X)
    on x16777216.X <= @end-@start
    join
    (VALUES (0),(268435456),(536870912),(805306368),(1073741824),(1342177280),(1610612736),(1879048192)) as x268435456(X)
    on x268435456.X <= @end-@start
    WHERE @end >=
        x268435456.X
        | isnull(x16777216.X, 0)
        | isnull(x1048576.X, 0)
        | isnull(x65536.X, 0)
        | isnull(x4096.X, 0)
        | isnull(x256.X, 0)
        | isnull(x16.X, 0)
        | isnull(x1.X, 0)
        + @start
    
    GO
    
    SELECT X FROM fn_ConsecutiveNumbers(5, 500);
    

    日期和时间范围也很方便:

    SELECT DATEADD(day,X, 0) DayX 
    FROM fn_ConsecutiveNumbers(datediff(day,0,'5/8/2015'), datediff(day,0,'5/31/2015'))
    
    SELECT DATEADD(hour,X, 0) HourX 
    FROM fn_ConsecutiveNumbers(datediff(hour,0,'5/8/2015'), datediff(hour,0,'5/8/2015 12:00 PM'));
    

    您可以在其上使用交叉应用联接来根据表中的值拆分记录。因此,例如要在表中的时间范围内为每分钟创建一条记录,您可以执行以下操作:

    select TimeRanges.StartTime,
        TimeRanges.EndTime,
        DATEADD(minute,X, 0) MinuteX
    FROM TimeRanges
    cross apply fn_ConsecutiveNumbers(datediff(hour,0,TimeRanges.StartTime), 
            datediff(hour,0,TimeRanges.EndTime)) ConsecutiveNumbers
    

    【讨论】:

    • 它适用于 1-100 但随后失败。即使您生成 5-500 的示例对我也不起作用,它显示 5, 21, ... 484, 500
    • 如果要对其进行排序,则必须添加 order by 子句:SELECT X FROM fn_ConsecutiveNumbers(5, 500) ORDER BY X;
    【解决方案5】:

    我用过的最好的选择如下:

    DECLARE @min bigint, @max bigint
    SELECT @Min=919859000000 ,@Max=919859999999
    
    SELECT TOP (@Max-@Min+1) @Min-1+row_number() over(order by t1.number) as N
    FROM master..spt_values t1 
        CROSS JOIN master..spt_values t2
    

    我已经使用它生成了数百万条记录,并且效果很好。

    【讨论】:

    • 这是这里最优雅的解决方案,但我认为很多人很难理解它(我一直在用 master.sys.all_columns 做这个)。 @STLDevoper,是的,它适用于 2008 年及更高版本。
    • 限制为 6345361 个值
    【解决方案6】:

    它对我有用!

    select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount
    from sys.all_objects a
    

    【讨论】:

    • 不错的单行 - 但请注意,最大行数将取决于 sys.all_objects - 对于小于 2000 个项目的小范围,这不是问题。不确定它是否会出现权限问题?非常适合快速生成一批测试数据。
    • @freedomn-m 增加最大行数的一种方法是执行自交叉连接。 select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount from sys.all_objects a, sys.all_objects b。以前只能生成 2384 行,现在可以生成 5683456 行。
    【解决方案7】:

    我用递归 ctes 来做,但我不确定这是否是最好的方法

    declare @initial as int = 1000;
    declare @final as int =1050;
    
    with cte_n as (
        select @initial as contador
        union all
        select contador+1 from cte_n 
        where contador <@final
    ) select * from cte_n option (maxrecursion 0)
    

    感谢。

    【讨论】:

    • 这非常有用。我修改了代码,以便可以插入 100.000 行。使用我的解决方案大约需要 13 分钟;使用你的,花了五秒钟。 Muchísimas gracias。
    • 实际上,递归 CTE 是最糟糕的计数方法之一。它们甚至可以在事务中被 While 循环击败,而 While 循环将产生更少的读取。 cCTE 方法(Cascading CTE,最初由 Itizik Ben-Gan 开发)速度更快,并且产生零读数。
    【解决方案8】:
    declare @start int = 1000
    declare @end    int =1050
    
    ;with numcte  
    AS  
    (  
      SELECT @start [SEQUENCE]  
      UNION all  
      SELECT [SEQUENCE] + 1 FROM numcte WHERE [SEQUENCE] < @end 
    )      
    SELECT * FROM numcte
    

    【讨论】:

    • 这与@Jayvee 的回答不同吗?
    • 是的,在其中提到的条件为 num + 1
    • 对现有答案的基本相同的编辑(或评论)将提供比全新答案更多的价值。
    【解决方案9】:

    如果在服务器中安装 CLR 程序集没有问题,那么一个不错的选择是在 .NET 中编写表值函数。这样您就可以使用简单的语法,从而可以轻松地与其他查询连接,并且不会因为结果是流式传输而浪费内存。

    创建一个包含以下类的项目:

    using System;
    using System.Collections;
    using System.Data;
    using System.Data.Sql;
    using System.Data.SqlTypes;
    using Microsoft.SqlServer.Server;
    
    namespace YourNamespace
    {
       public sealed class SequenceGenerator
        {
            [SqlFunction(FillRowMethodName = "FillRow")]
            public static IEnumerable Generate(SqlInt32 start, SqlInt32 end)
            {
                int _start = start.Value;
                int _end = end.Value;
                for (int i = _start; i <= _end; i++)
                    yield return i;
            }
    
            public static void FillRow(Object obj, out int i)
            {
                i = (int)obj;
            }
    
            private SequenceGenerator() { }
        }
    }
    

    将程序集放在服务器上的某处并运行:

    USE db;
    CREATE ASSEMBLY SqlUtil FROM 'c:\path\to\assembly.dll'
    WITH permission_set=Safe;
    
    CREATE FUNCTION [Seq](@start int, @end int) 
    RETURNS TABLE(i int)
    AS EXTERNAL NAME [SqlUtil].[YourNamespace.SequenceGenerator].[Generate];
    

    现在你可以运行了:

    select * from dbo.seq(1, 1000000)
    

    【讨论】:

    • 我尝试了这个解决方案,效果很好,只是速度不是很快。如果您只生成 1,000 个数字,或者可能是 10,000 个,则相当快。如果您像我一样必须生成数十亿个数字,那么与 SQL CLR 相比,Brian Pressler 的以下解决方案速度快得令人难以置信。
    • @DerreckDean 你是对的。我认为他是最好的解决方案,因为它很容易创建和使用(正如你所说的那样快)。就我而言,我已经有一个用于连接字符串的程序集,所以我只是将它添加到那里。
    • 我也有一个现有的程序集并尝试了这两种方法。我正在生成要添加到日期的不确定数量的数字(基本上,我重新创建了 SQL Server 代理调度程序来为我们的内部应用程序生成日期,并且 100 级递归不会因为生成多年日期时间,可能下降到第二个。),所以我能够从这个线程彻底测试多个解决方案。感谢您的贡献!
    【解决方案10】:

    没有什么新东西,但我重写了 Brian Pressler 的解决方案,以便更容易理解,它可能对某人有用(即使它只是未来的我):

    alter function [dbo].[fn_GenerateNumbers]
    (   
        @start int,
        @end  int
    ) returns table
    return
    
    with 
    b0 as (select n from (values (0),(0x00000001),(0x00000002),(0x00000003),(0x00000004),(0x00000005),(0x00000006),(0x00000007),(0x00000008),(0x00000009),(0x0000000A),(0x0000000B),(0x0000000C),(0x0000000D),(0x0000000E),(0x0000000F)) as b0(n)),
    b1 as (select n from (values (0),(0x00000010),(0x00000020),(0x00000030),(0x00000040),(0x00000050),(0x00000060),(0x00000070),(0x00000080),(0x00000090),(0x000000A0),(0x000000B0),(0x000000C0),(0x000000D0),(0x000000E0),(0x000000F0)) as b1(n)),
    b2 as (select n from (values (0),(0x00000100),(0x00000200),(0x00000300),(0x00000400),(0x00000500),(0x00000600),(0x00000700),(0x00000800),(0x00000900),(0x00000A00),(0x00000B00),(0x00000C00),(0x00000D00),(0x00000E00),(0x00000F00)) as b2(n)),
    b3 as (select n from (values (0),(0x00001000),(0x00002000),(0x00003000),(0x00004000),(0x00005000),(0x00006000),(0x00007000),(0x00008000),(0x00009000),(0x0000A000),(0x0000B000),(0x0000C000),(0x0000D000),(0x0000E000),(0x0000F000)) as b3(n)),
    b4 as (select n from (values (0),(0x00010000),(0x00020000),(0x00030000),(0x00040000),(0x00050000),(0x00060000),(0x00070000),(0x00080000),(0x00090000),(0x000A0000),(0x000B0000),(0x000C0000),(0x000D0000),(0x000E0000),(0x000F0000)) as b4(n)),
    b5 as (select n from (values (0),(0x00100000),(0x00200000),(0x00300000),(0x00400000),(0x00500000),(0x00600000),(0x00700000),(0x00800000),(0x00900000),(0x00A00000),(0x00B00000),(0x00C00000),(0x00D00000),(0x00E00000),(0x00F00000)) as b5(n)),
    b6 as (select n from (values (0),(0x01000000),(0x02000000),(0x03000000),(0x04000000),(0x05000000),(0x06000000),(0x07000000),(0x08000000),(0x09000000),(0x0A000000),(0x0B000000),(0x0C000000),(0x0D000000),(0x0E000000),(0x0F000000)) as b6(n)),
    b7 as (select n from (values (0),(0x10000000),(0x20000000),(0x30000000),(0x40000000),(0x50000000),(0x60000000),(0x70000000)) as b7(n))
    
    select s.n
    from (
        select
              b7.n
            | b6.n
            | b5.n
            | b4.n
            | b3.n
            | b2.n
            | b1.n
            | b0.n
            + @start
             n
        from b0
        join b1 on b0.n <= @end-@start and b1.n <= @end-@start
        join b2 on b2.n <= @end-@start
        join b3 on b3.n <= @end-@start
        join b4 on b4.n <= @end-@start
        join b5 on b5.n <= @end-@start
        join b6 on b6.n <= @end-@start
        join b7 on b7.n <= @end-@start
    ) s
    where @end >= s.n
    
    GO
    

    【讨论】:

    • 我相信您已经将漂亮算法的精髓提炼成一些非常漂亮的代码。
    • 结果以一种奇怪但不混乱的顺序排列。在 5 到 500 的范围内对其进行测试。它返回 5,21,37,..., 245,6,22,... 你知道排序会如何影响性能吗?基于ROW_NUMBER() 的解决方案没有这个问题。
    • 我不是专家,但凭直觉我猜 SQL 服务器需要将所有结果放入内存并在返回它们之前对其进行排序,这样更多的内存使用和延迟响应,而不是仅仅流式传输结果他们来了。
    【解决方案11】:

    slartidan's answer 可以通过消除对笛卡尔积的所有引用并改用ROW_NUMBER() (execution plan compared) 来提高性能:

    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n FROM 
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x1(x),
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x2(x),
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x3(x),
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x4(x),
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x5(x)
    ORDER BY n
    

    将其包装在 CTE 中并添加 where 子句以选择所需的数字:

    DECLARE @n1 AS INT = 100;
    DECLARE @n2 AS INT = 40099;
    WITH numbers AS (
        SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n FROM 
        (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x1(x),
        (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x2(x),
        (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x3(x),
        (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x4(x),
        (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x5(x)
    )
    SELECT numbers.n
    FROM numbers
    WHERE n BETWEEN @n1 and @n2
    ORDER BY n
    

    【讨论】:

    • ROW_NUMBER 仅从 1 开始。我们如何使用您的方法从零开始?
    • @stomy SELECT ROW_NUMBER() OVER (...) - 1 AS n。在某些情况下,这可能会影响性能。
    【解决方案12】:

    2 年后,但我发现我遇到了同样的问题。这是我解决它的方法。 (已编辑以包含参数)

    DECLARE @Start INT, @End INT
    SET @Start = 1000
    SET @End = 1050
    
    SELECT  TOP (@End - @Start+1) ROW_NUMBER() OVER (ORDER BY S.[object_id])+(@Start - 1) [Numbers]
    FROM    sys.all_objects S WITH (NOLOCK)
    

    【讨论】:

      【解决方案13】:

      我知道我已经晚了 4 年,但我偶然发现了这个问题的另一个替代答案。速度问题不仅在于预过滤,还在于防止排序。可以强制连接顺序以笛卡尔积作为连接结果实际计数的方式执行。以 slartidan 的回答为出发点:

          WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n))
      SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
      FROM x ones,     x tens,      x hundreds,       x thousands
      ORDER BY 1
      

      如果我们知道我们想要的范围,我们可以通过@Upper 和@Lower 指定它。通过将连接提示 REMOTE 与 TOP 相结合,我们可以只计算我们想要的值的子集,而不会浪费任何东西。

      WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n))
      SELECT TOP (1+@Upper-@Lower) @Lower + ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
      FROM x thousands
      INNER REMOTE JOIN x hundreds on 1=1
      INNER REMOTE JOIN x tens on 1=1
      INNER REMOTE JOIN x ones on 1=1
      

      连接提示 REMOTE 强制优化器首先在连接的右侧进行比较。通过将每个连接指定为从最高到最低有效值的 REMOTE,连接本身将正确地向上计数。无需使用 WHERE 进行过滤,也无需使用 ORDER BY 进行排序。

      如果您想增加范围,您可以继续添加数量级逐渐增加的附加连接,只要它们在 FROM 子句中从最重要到最不重要进行排序。

      请注意,这是特定于 SQL Server 2008 或更高版本的查询。

      【讨论】:

      • 确实很好。同样的技术也可以应用于 Brian Pressler 的出色答案和 Guillaume86 的可爱重写。
      【解决方案14】:

      这里有几个非常理想且兼容的解决方案:

      USE master;
      
      declare @min as int;    set @min = 1000;
      declare @max as int;    set @max = 1050;    --null returns all
      
      --  Up to 256 - 2 048 rows depending on SQL Server version
      select  isnull(@min,0)+number.number  as  number
      FROM    dbo.spt_values  AS  number
      WHERE   number."type"                   =   'P'     --integers
          and (   @max                            is null     --return all
              or  isnull(@min,0)+number.number    <=  @max    --return up to max
          )
      order by    number
      ;
      
      --  Up to 65 536 - 4 194 303 rows depending on SQL Server version
      select  isnull(@min,0)+value1.number+(value2.number*numberCount.numbers)  as  number
      FROM  dbo.spt_values            AS  value1
        cross join  dbo.spt_values    AS  value2
        cross join (  --get the number of numbers (depends on version)
          select  sum(1)  as  numbers
          from    dbo.spt_values
          where   spt_values."type"   =   'P' --integers
        )                             as  numberCount
      WHERE   value1."type" = 'P'   --integers
          and value2."type" = 'P'   --integers
          and (   @max    is null     --return all
              or  isnull(@min,0)+value1.number+(value2.number*numberCount.numbers)    
                  <=  @max            --return up to max
          )
      order by    number
      ;
      

      【讨论】:

      • 这种方法比简单的selecting where spt_values.number between @min and @max更好吗?
      • Type='P' 过滤器是必需的,以防止重复数字。使用此过滤器,表格将返回数字 0 - 2047。因此,只要变量在该范围内,“@min 和 @max 之间的数字”过滤器就会起作用。我的解决方案将允许您在整数范围 (-2,147,483,648) - (2,147,483,647) 内获得多达 2048 行。
      • 上述逻辑只有在最大和最小数之差小于2048的情况下才有用,一次最多可以有2048条记录
      【解决方案15】:

      SQL 2017 及更高版本的更新: 如果您想要的序列是

      Declare @start_num int = 1000
      ,   @end_num int = 1050
      
      Select [number] = @start_num + ROW_NUMBER() over (order by (Select null))
      from string_split(replicate(' ',@end_num-@start_num-1),' ')
      

      【讨论】:

        【解决方案16】:

        这样也可以

        DECLARE @startNum INT = 1000;
        DECLARE @endNum INT = 1050;
        INSERT  INTO dbo.Numbers
                ( Num
                )
                SELECT  CASE WHEN MAX(Num) IS NULL  THEN @startNum
                             ELSE MAX(Num) + 1
                        END AS Num
                FROM    dbo.Numbers
        GO 51
        

        【讨论】:

          【解决方案17】:

          运行查询时的最佳速度

          DECLARE @num INT = 1000
          WHILE(@num<1050)
          begin
           INSERT  INTO [dbo].[Codes]
              (   Code
              ) 
              VALUES (@num)
              SET @num = @num + 1
          end
          

          【讨论】:

            【解决方案18】:

            指数大小的递归 CTE(即使默认为 100 次递归,也可以建立多达 2^100 个数字):

            DECLARE @startnum INT=1000
            DECLARE @endnum INT=1050
            DECLARE @size INT=@endnum-@startnum+1
            ;
            WITH numrange (num) AS (
                SELECT 1 AS num
                UNION ALL
                SELECT num*2 FROM numrange WHERE num*2<=@size
                UNION ALL
                SELECT num*2+1 FROM numrange WHERE num*2+1<=@size
            )
            SELECT num+@startnum-1 FROM numrange order by num
            

            【讨论】:

            • 根据OP,我认为@startnumendnum应该由用户输入?
            【解决方案19】:

            我不得不使用类似的方法将图片文件路径插入数据库。下面的查询运行良好:

            DECLARE @num INT = 8270058
            WHILE(@num<8270284)
            begin
                INSERT  INTO [dbo].[Galleries]
                (ImagePath) 
                VALUES 
                ('~/Content/Galeria/P'+CONVERT(varchar(10), @num)+'.JPG')
            
                SET @num = @num + 1
            end
            

            你的代码是:

            DECLARE @num INT = 1000
            WHILE(@num<1051)
            begin
                SELECT @num
            
                SET @num = @num + 1
            end
            

            【讨论】:

              【解决方案20】:

              这就是我所做的,它非常快速和灵活,而且代码不多。

              DECLARE @count  int =   65536;
              DECLARE @start  int =   11;
              DECLARE @xml    xml =   REPLICATE(CAST('<x/>' AS nvarchar(max)), @count);
              
              ; WITH GenerateNumbers(Num) AS
              (
                  SELECT  ROW_NUMBER() OVER (ORDER BY @count) + @start - 1
                  FROM    @xml.nodes('/x') X(T)
              )
              SELECT  Num
              FROM    GenerateNumbers;
              

              请注意, (ORDER BY @count) 是一个虚拟对象。它什么也不做,但 ROW_NUMBER() 需要一个 ORDER BY。

              编辑: 我意识到最初的问题是得到从 x 到 y 的范围。我的脚本可以这样修改以获得一个范围:

              DECLARE @start  int =   5;
              DECLARE @end    int =   21;
              DECLARE @xml    xml =   REPLICATE(CAST('<x/>' AS nvarchar(max)), @end - @start + 1);
              
              ; WITH GenerateNumbers(Num) AS
              (
                  SELECT  ROW_NUMBER() OVER (ORDER BY @end) + @start - 1
                  FROM    @xml.nodes('/x') X(T)
              )
              SELECT  Num
              FROM    GenerateNumbers;
              

              【讨论】:

                【解决方案21】:
                -- Generate Numeric Range
                -- Source: http://www.sqlservercentral.com/scripts/Miscellaneous/30397/
                
                CREATE TABLE #NumRange(
                    n int
                )
                
                DECLARE @MinNum int
                DECLARE @MaxNum int
                DECLARE @I int
                
                SET NOCOUNT ON
                
                SET @I = 0
                WHILE @I <= 9 BEGIN
                    INSERT INTO #NumRange VALUES(@I)
                    SET @I = @I + 1
                END
                
                
                SET @MinNum = 1
                SET @MaxNum = 1000000
                
                SELECT  num = a.n +
                    (b.n * 10) +
                    (c.n * 100) +
                    (d.n * 1000) +
                    (e.n * 10000)
                FROM    #NumRange a
                CROSS JOIN #NumRange b
                CROSS JOIN #NumRange c
                CROSS JOIN #NumRange d
                CROSS JOIN #NumRange e
                WHERE   a.n +
                    (b.n * 10) +
                    (c.n * 100) +
                    (d.n * 1000) +
                    (e.n * 10000) BETWEEN @MinNum AND @MaxNum
                ORDER BY a.n +
                    (b.n * 10) +
                    (c.n * 100) +
                    (d.n * 1000) +
                    (e.n * 10000) 
                
                DROP TABLE #NumRange
                

                【讨论】:

                  【解决方案22】:

                  这仅适用于序列,只要某些应用程序表有行。假设我想要从 1..100 开始的序列,并且应用程序表 dbo.foo 具有列(数字或字符串类型)foo.bar:

                  select 
                  top 100
                  row_number() over (order by dbo.foo.bar) as seq
                  from dbo.foo
                  

                  尽管它存在于 order by 子句中,但 dbo.foo.bar 不必具有不同的甚至非空值。

                  当然,SQL Server 2012 具有序列对象,因此该产品中有一个自然的解决方案。

                  【讨论】:

                    【解决方案23】:

                    这是我想出的:

                    create or alter function dbo.fn_range(@start int, @end int)  returns table
                    return
                    with u2(n) as (
                        select n 
                        from (VALUES (0),(1),(2),(3)) v(n)
                    ), 
                    u8(n) as (
                        select
                            x0.n | x1.n * 4 | x2.n * 16 | x3.n * 64 as n
                        from u2 x0, u2 x1, u2 x2, u2 x3
                    )
                    select 
                        @start + s.n as n
                    from (
                        select
                            x0.n | isnull(x1.n, 0) * 256 | isnull(x2.n, 0) * 65536 as n
                        from u8 x0 
                        left join u8 x1 on @end-@start > 256
                        left join u8 x2 on @end-@start > 65536
                    ) s
                    where s.n < @end - @start
                    

                    最多生成 2^24 个值。连接条件对于较小的值保持快速。

                    【讨论】:

                      【解决方案24】:

                      这在我们的 DEV 服务器上为我在 36 秒内完成。就像布赖恩的回答一样,从查询中关注范围的过滤很重要; a BETWEEN 仍然尝试在下限之前生成所有初始记录,即使它不需要它们。

                      declare @s bigint = 10000000
                          ,   @e bigint = 20000000
                      
                      ;WITH 
                      Z AS (SELECT 0 z FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15)) T(n)),
                      Y AS (SELECT 0 z FROM Z a, Z b, Z c, Z d, Z e, Z f, Z g, Z h, Z i, Z j, Z k, Z l, Z m, Z n, Z o, Z p),
                      N AS (SELECT ROW_NUMBER() OVER (PARTITION BY 0 ORDER BY z) n FROM Y)
                      
                      SELECT TOP (1+@e-@s) @s + n - 1 FROM N
                      

                      请注意,ROW_NUMBER 是一个 bigint,因此我们不能使用任何使用方法生成超过 2^^64 (==16^^16) 条记录它。因此,此查询遵循相同的生成值上限。

                      【讨论】:

                        【解决方案25】:

                        这使用过程代码和表值函数。缓慢,但容易且可预测。

                        CREATE FUNCTION [dbo].[Sequence] (@start int, @end int)
                        RETURNS
                        @Result TABLE(ID int)
                        AS
                        begin
                        declare @i int;
                        set @i = @start;
                        while @i <= @end 
                            begin
                                insert into @result values (@i);
                                set @i = @i+1;
                            end
                        return;
                        end
                        

                        用法:

                        SELECT * FROM dbo.Sequence (3,7);
                        ID
                        3
                        4
                        5
                        6
                        7
                        

                        这是一个表格,因此您可以将它与其他数据结合使用。我最常使用此函数作为 GROUP BY 小时、天等连接的左侧,以确保时间值的连续序列。

                        SELECT DateAdd(hh,ID,'2018-06-20 00:00:00') as HoursInTheDay FROM dbo.Sequence (0,23) ;
                        
                        HoursInTheDay
                        2018-06-20 00:00:00.000
                        2018-06-20 01:00:00.000
                        2018-06-20 02:00:00.000
                        2018-06-20 03:00:00.000
                        2018-06-20 04:00:00.000
                        (...)
                        

                        性能平淡无奇(100 万行需要 16 秒),但对于许多用途来说已经足够了。

                        SELECT count(1) FROM [dbo].[Sequence] (
                           1000001
                          ,2000000)
                        GO
                        

                        【讨论】:

                          【解决方案26】:

                          甲骨文 12c;快速但有限:

                          select rownum+1000 from all_objects fetch first 50 rows only;
                          

                          注意:仅限于 all_objects 视图的行数;

                          【讨论】:

                            【解决方案27】:

                            我已经开发和使用了很长一段时间的解决方案(利用其他人的共享作品)与至少发布的一个稍微相似。它不引用任何表并返回最多 1048576 个值 (2^20) 的未排序范围,并且可以根据需要包含负数。如果需要,您当然可以对结果进行排序。它的运行速度非常快,尤其是在较小的范围内。

                            Select value from dbo.intRange(-500, 1500) order by value  -- returns 2001 values
                            
                            create function dbo.intRange 
                            (   
                                @Starting as int,
                                @Ending as int
                            )
                            returns table
                            as
                            return (
                                select value
                                from (
                                    select @Starting +
                                        ( bit00.v | bit01.v | bit02.v | bit03.v
                                        | bit04.v | bit05.v | bit06.v | bit07.v
                                        | bit08.v | bit09.v | bit10.v | bit11.v
                                        | bit12.v | bit13.v | bit14.v | bit15.v
                                        | bit16.v | bit17.v | bit18.v | bit19.v
                                        ) as value
                                    from       (select 0 as v union ALL select 0x00001 as v) as bit00
                                    cross join (select 0 as v union ALL select 0x00002 as v) as bit01
                                    cross join (select 0 as v union ALL select 0x00004 as v) as bit02
                                    cross join (select 0 as v union ALL select 0x00008 as v) as bit03
                                    cross join (select 0 as v union ALL select 0x00010 as v) as bit04
                                    cross join (select 0 as v union ALL select 0x00020 as v) as bit05
                                    cross join (select 0 as v union ALL select 0x00040 as v) as bit06
                                    cross join (select 0 as v union ALL select 0x00080 as v) as bit07
                                    cross join (select 0 as v union ALL select 0x00100 as v) as bit08
                                    cross join (select 0 as v union ALL select 0x00200 as v) as bit09
                                    cross join (select 0 as v union ALL select 0x00400 as v) as bit10
                                    cross join (select 0 as v union ALL select 0x00800 as v) as bit11
                                    cross join (select 0 as v union ALL select 0x01000 as v) as bit12
                                    cross join (select 0 as v union ALL select 0x02000 as v) as bit13
                                    cross join (select 0 as v union ALL select 0x04000 as v) as bit14
                                    cross join (select 0 as v union ALL select 0x08000 as v) as bit15
                                    cross join (select 0 as v union ALL select 0x10000 as v) as bit16
                                    cross join (select 0 as v union ALL select 0x20000 as v) as bit17
                                    cross join (select 0 as v union ALL select 0x40000 as v) as bit18
                                    cross join (select 0 as v union ALL select 0x80000 as v) as bit19
                                ) intList
                                where @Ending - @Starting < 0x100000
                                    and intList.value between @Starting and @Ending
                            )
                            

                            【讨论】:

                              【解决方案28】:
                              ;WITH u AS (
                                  SELECT Unit FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(Unit)
                              ),
                              d AS (
                                  SELECT 
                                      (Thousands+Hundreds+Tens+Units) V
                                  FROM 
                                         (SELECT Thousands = Unit * 1000 FROM u) Thousands 
                                         ,(SELECT Hundreds = Unit * 100 FROM u) Hundreds 
                                         ,(SELECT Tens = Unit * 10 FROM u) Tens 
                                         ,(SELECT Units = Unit FROM u) Units
                                  WHERE
                                         (Thousands+Hundreds+Tens+Units) <= 10000
                              )
                              
                              SELECT * FROM d ORDER BY v
                              

                              【讨论】:

                                【解决方案29】:

                                我在阅读了这个帖子后做了下面的函数。简单快速:

                                go
                                create function numbers(@begin int, @len int)
                                returns table as return
                                with d as (
                                    select 1 v from (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d(v)
                                )
                                select top (@len) @begin -1 + row_number() over(order by (select null)) v
                                from d d0
                                cross join d d1
                                cross join d d2
                                cross join d d3
                                cross join d d4
                                cross join d d5
                                cross join d d6
                                cross join d d7
                                go
                                
                                select * from numbers(987654321,500000)
                                

                                【讨论】:

                                  【解决方案30】:

                                  虽然关于这个话题有很多很好的答案,但我认为更新的 TSQL (2019 +) 语法允许更简单和易于理解的解决方案来解决这个问题:

                                  Declare @n1 int = 1000
                                  Declare @n2 int = 1050
                                  Declare @n  int = @n2 -@n1
                                  SELECT @n1 -1 + Row_Number() over ( partition by 1 order by value) as Val FROM 
                                  STRING_SPLIT ( (SELECT REPLICATE(';', @n)) , ';' )  
                                  

                                  【讨论】:

                                    猜你喜欢
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 2015-03-05
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 2011-09-20
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 2022-01-23
                                    相关资源
                                    最近更新 更多