如何在两个数字之间生成一个数字范围?
我有两个数字作为来自用户的input,例如1000
和1050
。
我如何生成这两个数字之间的数字,使用SQL查询,在单独的行? 我要这个:
1000 1001 1002 1003 . . 1050
您可以使用VALUES
关键字select虚数值。 一些JOIN
会产生很多很多的组合(可以扩展成几十万行)。
你的号码(从1000到1050):
SELECT ones.n + 10*tens.n + 1000 FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n), (VALUES(0),(1),(2),(3),(4),(5) ) tens(n) WHERE ones.n + 10*tens.n + 1000 BETWEEN 1000 AND 1050
演示
优点:
- 闪电快
- 无限
- 有种可读性
- 可以在视图中使用
缺点:
- 非常大的数字需要大量的连接
另一个例子,产生从0到9999的数字:
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
演示
一个较短,但不容易阅读的select:
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
演示
另一种解决scheme是recursionCTE:
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)
SELECT DISTINCT n = number FROM master..[spt_values] WHERE number BETWEEN @start AND @end
演示
请注意,这个表格最多有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;
演示
或使用自定义的数字表。 感谢Aaron Bertrand,我build议阅读整篇文章: 生成一个没有循环的集合或序列
我最近写了这个内联表值函数来解决这个问题。 除了内存和存储以外,其范围不受限制。 它不访问任何表,所以不需要磁盘读或写操作。 它在每次迭代时都会按指数增加连接值,所以即使在非常大的范围内也是非常快速的。 它在我的服务器上五秒内创build了一千万条logging。 它也适用于负值。
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);
date和时间范围也很方便:
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'));
您可以使用交叉应用连接来根据表中的值拆分logging。 因此,例如,要创build一个表中的时间范围内的每一分钟的logging,你可以这样做:
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
我用过的最好的选项如下:
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
我已经使用这个生成了数以百万计的logging,它的工作完美。
如果在服务器中安装CLR程序集没有问题,那么一个好的select是在.NET中编写一个表值函数。 这样,您可以使用简单的语法,使其与其他查询结合起来更容易,作为奖励也不会浪费内存,因为结果是stream式的。
创build一个包含以下类的项目:
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)
这里有几个相当优化和兼容的解决scheme:
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 ;
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)
这也会做
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
运行查询时的最佳速度
DECLARE @num INT = 1000 WHILE(@num<1050) begin INSERT INTO [dbo].[Codes] ( Code ) VALUES (@num) SET @num = @num + 1 end
我不得不使用类似的方法插入图片文件path到数据库中。 下面的查询工作正常:
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
通过消除对笛卡尔积的所有引用,并使用ROW_NUMBER()
( 比较执行计划 ), slartidan的答案可以得到改善,性能也有所提高:
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子句来select所需的数字:
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
它为我工作!
select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount from sys.all_objects a
-- 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 = an + (bn * 10) + (cn * 100) + (dn * 1000) + (en * 10000) FROM #NumRange a CROSS JOIN #NumRange b CROSS JOIN #NumRange c CROSS JOIN #NumRange d CROSS JOIN #NumRange e WHERE an + (bn * 10) + (cn * 100) + (dn * 1000) + (en * 10000) BETWEEN @MinNum AND @MaxNum ORDER BY an + (bn * 10) + (cn * 100) + (dn * 1000) + (en * 10000) DROP TABLE #NumRange
这只适用于序列,只要某些应用程序表有行。 假设我想从1..100的序列,并具有应用程序表dbo.foo与列(数字或stringtypes)foo.bar:
select top 100 row_number() over (order by dbo.foo.bar) as seq from dbo.foo
尽pipe它存在于order by子句中,但dbo.foo.bar不必具有不同的值,甚至不具有非null值。
当然,SQL Server 2012具有序列对象,所以在该产品中有一个自然的解决scheme。
指数大小的recursionCTE(即使默认为100次recursion,也可以build立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