How to generate a range of numbers between two numbers?

后端 未结 30 1871
执念已碎
执念已碎 2020-11-22 10:16

I have two numbers as input from the user, like for example 1000 and 1050.

How do I generate the numbers between these two numbers, using

相关标签:
30条回答
  • 2020-11-22 10:28

    This uses procedural code and a table-valued function. Slow, but easy and predictable.

    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
    

    Usage:

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

    It's a table, so you can use it in joins with other data. I most frequently use this function as the left side of a join against a GROUP BY hour, day etc to ensure a contiguous sequence of time values.

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

    Performance is uninspiring (16 seconds for a million rows) but good enough for many purposes.

    SELECT count(1) FROM [dbo].[Sequence] (
       1000001
      ,2000000)
    GO
    
    0 讨论(0)
  • 2020-11-22 10:29

    recursive CTE in exponential size (even for default of 100 recursion, this can build up to 2^100 numbers):

    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
    
    0 讨论(0)
  • 2020-11-22 10:30

    If you don't have a problem installing a CLR assembly in your server a good option is writing a table valued function in .NET. That way you can use a simple syntax, making it easy to join with other queries and as a bonus won't waste memory because the result is streamed.

    Create a project containing the following class:

    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() { }
        }
    }
    

    Put the assembly somewhere on the server and run:

    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];
    

    Now you can run:

    select * from dbo.seq(1, 1000000)
    
    0 讨论(0)
  • 2020-11-22 10:31

    I recently wrote this inline table valued function to solve this very problem. It's not limited in range other than memory and storage. It accesses no tables so there's no need for disk reads or writes generally. It adds joins values exponentially on each iteration so it's very fast even for very large ranges. It creates ten million records in five seconds on my server. It also works with negative values.

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

    It's handy for date and time ranges as well:

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

    You could use a cross apply join on it to split records based on values in the table. So for example to create a record for every minute on a time range in a table you could do something like:

    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
    
    0 讨论(0)
  • 2020-11-22 10:31

    The best way is using recursive 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)
    

    saludos.

    0 讨论(0)
  • 2020-11-22 10:31

    I made the below function after reading this thread. Simple and fast:

    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)
    
    0 讨论(0)
提交回复
热议问题