Recursive UNION ALL to parse string taking very long - sql

I am trying to speed up this recursive UNION ALL as shown below, but I cannot think how to do it. Maybe a while loop but I am not sure. The movement data is stored as one long string of encoded movement data and the script recursively calls the select statement to parse/extract this data and then it is all casted.
I would really like to understand more about speeding up recursive union all's or finding another way. I don't believe indexing is a problem so this is not really a possible solution.
"RouteData" is the long string that is parsed by fixed length intervals.
Here is a sample of the encoded data:
ScenarioPID : 3
LegID :1
RoutePart : 0x0000000000000000000100000000000000000000000000
RouteData : 0x40323AAAAAAAAAAB00013FA6FFD663CCA3310000001F00403 ... (goes on)
cnt : 37
sequence : 1
StartTime : 8828
The final output data looks like this for one track.
ScenarioPID LegID sequence TrackID Offset TimeOffset Length StartTime
3 1 1 1 0 0 6300 8828
3 1 2 1 0.0449 31 6300 8828
3 1 3 1 0.8942 325 6300 8828
3 1 4 1 0.9736 356 6300 8828
3 1 5 1 1 369 6300 8828
USE nss_demo;
DECLARE #scenario1 INT;
DECLARE #DAY_START INT;
DECLARE #DAY_END INT;
DECLARE #TRAIN_TYPE VARCHAR(50);
DECLARE #TRACK_TYPE VARCHAR(50);
SET #scenario1 = 3;
SET #DAY_START = 0;
SET #DAY_END = 7;
SET #TRAIN_TYPE = 'Empty Train';
SET #TRACK_TYPE = 'East Track';
DECLARE #KM_START INT;
DECLARE #KM_END INT;
SET #KM_START = 0;
SET #KM_END = 200;
WITH movement
AS (SELECT m.scenariopid,
m.legid,
Substring(routedata, 1, 23) AS RoutePart,
Substring(routedata, 24, Len(routedata) - 23) AS RouteData,
Len(routedata) / 23 - 1 AS cnt,
1 AS sequence,
m.starttime
FROM output.movement m
WHERE scenariopid = #scenario1
AND m.starttime BETWEEN ( #DAY_START * 86400 ) AND
( #DAY_END * 86400 )
UNION ALL
SELECT scenariopid,
legid,
Substring(m1.routedata, 1, 23) AS RoutePart
,
Substring(m1.routedata, 24,
Len(m1.routedata) - 23) AS RouteData,
Len(m1.routedata) / 23 - 1 AS cnt,
sequence + 1 AS sequence,
m1.starttime
FROM movement m1
WHERE m1.cnt > 0),
casttable
AS (SELECT tt.scenariopid,
tt.legid,
tt.sequence,
tt.trackid,
tt.offset,
tt.timeoffset,
tr.[length],
tt.starttime
FROM (SELECT scenariopid,
legid,
sequence,
Cast(trackidbin AS SMALLINT) AS TrackID,
Sign(Cast(offsetbin AS BIGINT)) *
( 1.0 +
( Cast(offsetbin AS BIGINT) & 0x000FFFFFFFFFFFFF ) *
Power(Cast(2 AS FLOAT), -52) )
*
Power(Cast(2 AS FLOAT), ( Cast(offsetbin AS BIGINT) &
0x7ff0000000000000
) /
0x0010000000000000
- 1023) AS Offset,
Cast(timebin AS INT) AS TimeOffset,
starttime AS StartTime
FROM (SELECT legid,
scenariopid,
sequence,
Substring(routepart, 9, 2) AS TrackIDBin,
Substring(routepart, 11, 8) AS OffsetBin,
Substring(routepart, 19, 4) AS TimeBin,
starttime
FROM movement) t) tt
INNER JOIN input.track tr
ON tr.trackid = tt.trackid
AND tr.scenariopid = tt.scenariopid)
SELECT *
FROM casttable
ORDER BY legid,
sequence
OPTION (maxrecursion 20000)

Use a Numbers Table (zero-based assumed below) to create CTE movement like this:
WITH movement
AS (SELECT m.scenariopid,
m.legid,
Substring(routedata, n.N*23 + 1, 23) AS RoutePart,
n.N AS cnt,
-- 1 AS sequence, -- use a row_number function here instead, as per your vendor.
m.starttime
FROM output.movement m
JOIN Numbers n
on n < Len(routedata) / 23
WHERE scenariopid = #scenario1
AND m.starttime BETWEEN ( #DAY_START * 86400 ) AND
( #DAY_END * 86400 )
),
-- etc.
If you don't have a static Numbers Table, my answer here demonstrates how to create one dynamically in a CTE.

Related

Split a range of numbers in equal parts SQL Sever

I have following query to split the range of numbers in parts. For ex: i have range of 200 numbers and i have to group the numbers to 95 But Result is coming in reverse order.I Have attached expected result
declare #MinValue bigint = 1
declare #MaxValue bigint = 200;
declare #RowsPerGroup bigint =95
declare #RowsPerGroup1 bigint =(#RowsPerGroup-1)
;with src(val,rm) as (
select #MaxValue, (#MaxValue - #RowsPerGroup1) union all
select rm-1, case when rm-1 > #MinValue + #RowsPerGroup1 then rm-1 - #RowsPerGroup1 else #MinValue end from src where rm-1 >= #MinValue
)
select rm as 'Start', val as 'End',[Difference]=(val-rm)+1 from src order by rm asc
option(maxrecursion 0)
Current Result:
Start End Difference
1 10 10
11 105 95
106 200 95
Expected Result:
Pls let me know where am doing wrong
My variant:
DECLARE
#MinValue bigint = 1,
#MaxValue bigint = 200,
#RowsPerGroup bigint = 95 -- 300
;WITH cte AS(
SELECT #MinValue [Start],IIF(#RowsPerGroup>#MaxValue,#MaxValue,#RowsPerGroup) [End]
UNION ALL
SELECT [End]+1,IIF([End]+#RowsPerGroup>#MaxValue,#MaxValue,[End]+#RowsPerGroup)
FROM cte
WHERE [End]<#MaxValue
)
SELECT [Start],[End],[End]-[Start]+1 [Difference]
FROM cte
You can see the reason in the first line of the common table expression:
select #MaxValue, (#MaxValue - #RowsPerGroup1)
This inserts (200,106) intto src. The second select then counts down from existing rows. To adapt the CTE, exchange minimums with maximums (including for ranges), invert arithmetic, reverse comparisons and any other related exchanges:
select #MinValue, (#MinValue + #RowsPerGroup1) union all
select val+1, case
when val+1 < #MaxValue - #RowsPerGroup1
then val+1 + #RowsPerGroup1
else #MaxValue
end
from src
where val+1 <= #MaxValue
This particular statement can be simplified in parts:
select #MinValue, (#MinValue + #RowsPerGroup - 1) union all
select val + 1, case
when val + #RowsPerGroup < #MaxValue
then val + #RowsPerGroup
else #MaxValue
end
from src
where val < #MaxValue

How to select only armstrong numbers from the list?

I want is to select Armstrong numbers from the list below list I have searched of solution of this question bu unable to find in SQL-Server:
Numbers
121
113
423
153
541
371
I am sure most of you know what's the Armstrong number and how to calculate though I am describing is for the simplicity : sum of the cubes of its digits is equal to the number itself i.e.
1*1*1 + 5*5*5 + 3*3*3 = 153
3*3*3 + 7*7*7 + 1*1*1 = 371
Please help me on this as I am also trying but seeking for quick solution. It will be very helpful to me. Thanks in advance.
Obviously static processing during each query is not correct approach but we can create function like this and
create function dbo.IsArmstrongNumber(#n int)
returns int as
begin
declare #retValue int = 0
declare #sum int = 0
declare #num int = #n
while #num > 0
begin
set #sum += (#num%10) * (#num%10) * (#num%10)
set #num = #num/10
end
IF #sum = #n
set #retValue = 1
return #retValue
end
Pre-processing and selecting in IN clause is better
select * from #Numbers where dbo.IsArmstrongNumber(n) = 1
select 153 x into #temp;
insert #temp values(371);
insert #temp values(541);
with cte as (select x, substring(cast(x as nvarchar(40)) ,1,1) as u, 1 as N FROM #temp
union all
select x, substring(cast(x as nvarchar(40)),n+1,1) as u , n+1 from cte where len(cast(x as nvarchar(40))) > n
)
select x from cte group by x having SUM(POWER(cast(u as int),3)) = x
drop table #temp;
here is the mark 2 - you can change the #ORDER to explore power of 4,5 etc
declare #order int = 3;
declare #limit int = 50000;
with nos as (select 1 no
union all
select no + 1 from nos where no < #limit),
cte as (select no as x, substring(cast(no as nvarchar(40)) ,1,1) as u, 1 as N FROM nos
union all
select x, substring(cast(x as nvarchar(40)),n+1,1) as u , n+1 from cte where len(cast(x as nvarchar(40))) > n
)
select x from cte group by x having SUM(POWER(cast(u as int),#order)) = x
option (maxrecursion 0);
This is a quick mod to my sum of digits UDF
Declare #Table table (Numbers int)
Insert into #Table values
(121),
(113),
(423),
(153),
(541),
(371)
Select * from #Table where [dbo].[udf-Stat-Is-Armstrong](Numbers)=1
Returns
Numbers
153
371
The UDF
CREATE Function [dbo].[udf-Stat-Is-Armstrong](#Val bigint)
Returns Bit
As
Begin
Declare #RetVal as bigint
Declare #LenInp as bigint = len(cast(#Val as varchar(25)))
;with i AS (
Select #Val / 10 n, #Val % 10 d
Union ALL
Select n / 10, n % 10
From i
Where n > 0
)
Select #RetVal = IIF(SUM(power(d,#LenInp))=#Val,1,0) FROM i;
Return #RetVal
End
You can use the following to find Armstrong numbers using Sql functions:
WITH Numbers AS(
SELECT 0 AS number UNION ALL SELECT number + 1 FROM Numbers WHERE number < 10000)
SELECT number AS ArmstrongNumber FROM Numbers
WHERE
number = POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),1,1),0),3)
+ POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),2,1),0),3)
+ POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),3,1),0),3)
OPTION(MAXRECURSION 0)

How to generate a range of numbers between two numbers?

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 a sql query, in seperate rows? I want this:
1000
1001
1002
1003
.
.
1050
Select non-persisted values with the VALUES keyword. Then use JOINs to generate lots and lots of combinations (can be extended to create hundreds of thousands of rows and beyond).
Short and fast version (not that easy to read):
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
More verbose version:
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
Both versions can easily be extended with a WHERE clause, limiting the output of numbers to a user-specified range. If you want to reuse it, you can define a table-valued function for it.
an alternative solution is recursive 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)
SELECT DISTINCT n = number
FROM master..[spt_values]
WHERE number BETWEEN #start AND #end
Demo
Note that this table has a maximum of 2048 because then the numbers have gaps.
Here's a slightly better approach using a system view(since from 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
or use a custom a number-table. Credits to Aaron Bertrand, i suggest to read the whole article: Generate a set or sequence without loops
The best option I have used is as follows:
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
I have generated millions of records using this and it works perfect.
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
It work for me !
select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount
from sys.all_objects a
I do it with recursive ctes, but i'm not sure if it is the best way
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.
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
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)
slartidan's answer can be improved, performance wise, by eliminating all references to the cartesian product and using ROW_NUMBER() instead (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
Wrap it inside a CTE and add a where clause to select desired numbers:
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
Nothing new but I rewrote Brian Pressler solution to be easier on the eye, it might be useful to someone (even if it's just future me):
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
2 years later, but I found I had the same issue. Here is how I solved it. (edited to include parameters)
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)
I know I'm 4 years too late, but I stumbled upon yet another alternative answer to this problem. The issue for speed isn't just pre-filtering, but also preventing sorting. It's possible to force the join-order to execute in a manner that the Cartesian product actually counts up as a result of the join. Using slartidan's answer as a jump-off point:
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
If we know the range we want, we can specify it via #Upper and #Lower. By combining the join hint REMOTE along with TOP, we can calculate only the subset of values we want with nothing wasted.
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
The join hint REMOTE forces the optimizer to compare on the right side of the join first. By specifying each join as REMOTE from most to least significant value, the join itself will count upwards by one correctly. No need to filter with a WHERE, or sort with an ORDER BY.
If you want to increase the range, you can continue to add additional joins with progressively higher orders of magnitude, so long as they're ordered from most to least significant in the FROM clause.
Note that this is a query specific to SQL Server 2008 or higher.
If your SQL-server version is higher than 2022 or supports GENERATE_SERIES function, we can try to use GENERATE_SERIES function and declare START and STOP parameters.
GENERATE_SERIES returns a single-column table containing a sequence of values in which each differs from the preceding by STEP
declare #start int = 1000
declare #stop int = 1050
declare #step int = 2
SELECT [Value]
FROM GENERATE_SERIES(#start, #stop, #step)
Here are couple quite optimal and compatible solutions:
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
;
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
Update for SQL 2017 and later:
If the sequence you desire is < 8k then this will work:
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),' ')
This will also do
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
The best speed when run query
DECLARE #num INT = 1000
WHILE(#num<1050)
begin
INSERT INTO [dbo].[Codes]
( Code
)
VALUES (#num)
SET #num = #num + 1
end
I had to insert picture filepath into database using similar method. The query below worked fine:
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
The code for you would be:
DECLARE #num INT = 1000
WHILE(#num<1051)
begin
SELECT #num
SET #num = #num + 1
end
Here's what I came up with:
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
Generates up to 2^24 values. Join conditions keep it fast for small values.
This is what I do, it's pretty fast and flexible and not a lot of code.
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;
Note that (ORDER BY #count) is a dummy. It doesn't do anything but ROW_NUMBER() requires an ORDER BY.
Edit:
I realized that the original question was to get a range from x to y. My script can be modified like this to get a range:
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;
-- 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
This only works for sequences as long as some application table has rows. Assume I want sequence from 1..100, and have application table dbo.foo with column (of numeric or string type) foo.bar:
select
top 100
row_number() over (order by dbo.foo.bar) as seq
from dbo.foo
Despite its presence in an order by clause, dbo.foo.bar does not have to have distinct or even non-null values.
Of course, SQL Server 2012 has sequence objects, so there's a natural solution in that product.
This completed for me in 36 seconds on our DEV server. Like Brian's answer, focusing on filtering to the range is important from within the query; a BETWEEN still tries to generate all the initial records prior to the lower bound even though it doesn't need them.
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
Note that ROW_NUMBER is a bigint, so we can't go over 2^^64 (==16^^16) generated records with any method that uses it. This query therefore respects the same upper limit on generated values.
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
Oracle 12c; Quick but limited:
select rownum+1000 from all_objects fetch first 50 rows only;
Note: limited to row count of all_objects view;
The solution I've developed and used for quite some time now (riding some on the shared works of others) is slightly similar to at least one posted. It doesn't reference any tables and returns an unsorted range of up to 1048576 values (2^20) and can include negatives if desired. You can of course sort the result if necessary. It runs pretty quickly, especially on smaller ranges.
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
)
;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
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)

Generate unique random numbers using SQL

I have some SQL code which generates random numbers using the following technique:
DECLARE #Random1 INT, #Random2 INT, #Random3 INT, #Random4 INT, #Random5 INT, #Random6 INT, #Upper INT, #Lower INT
---- This will create a random number between 1 and 49
SET #Lower = 1 ---- The lowest random number
SET #Upper = 49; ---- The highest random number
with nums as (
select #lower as n
union all
select nums.n+1
from nums
where nums.n < #Upper
),
randnums as
(select nums.n, ROW_NUMBER() over (order by newid()) as seqnum
from nums
)
select #Random1 = MAX(case when rn.seqnum = 1 then rn.n end),
#Random2 = MAX(case when rn.seqnum = 2 then rn.n end),
#Random3 = MAX(case when rn.seqnum = 3 then rn.n end),
#Random4 = MAX(case when rn.seqnum = 4 then rn.n end),
#Random5 = MAX(case when rn.seqnum = 5 then rn.n end),
#Random6 = MAX(case when rn.seqnum = 6 then rn.n end)
from randnums rn;
select #Random1, #Random2, #Random3, #Random4, #Random5, #Random6
My question is how random is this number generation? and is there another way to do this which is more "random".
I am using:
Microsoft SQL Server 2008 (SP3) - 10.0.5512.0 (X64) Aug 22 2012 19:25:47 Copyright (c) 1988-2008 Microsoft Corporation Developer Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1)
The problem with most solutions is you'll end up with values like this: 14,29,8,14,27,27 I cannot have duplicate numbers!
I guess you could do something like this much simpler and much easier
DECLARE #Upper INT;
DECLARE #Lower INT;
SET #Lower = 1; /* -- The lowest random number */
SET #Upper = 49; /* -- The highest random number */
SELECT #Lower + CONVERT(INT, (#Upper-#Lower+1)*RAND());
For getting a random number without repetition, this will do the job
WITH CTE
AS
(
SELECT randomNumber, COUNT(1) countOfRandomNumber
FROM (
SELECT ABS(CAST(NEWID() AS binary(6)) %49) + 1 randomNumber
FROM sysobjects
) sample
GROUP BY randomNumber
)
SELECT TOP 5 randomNumber
FROM CTE
ORDER BY newid()
To set the highest limit, you can replace 49 with your highest limit number.
For Laravel:
public function generatUniqueId()
{
$rand = rand(10000, 99999);
$itemId = $rand;
while (true) {
if (!BookItem::whereBookItemId($itemId)->exists()) {
break;
}
$itemId = rand(10000, 99999);
}
return $itemId;
}
Create a list of random numbers. For this example I did 100, could be more, could be less (but no less than your limit)
Use row_number() function to detect duplicates
Once you delete the duplicates, select top 6 number in your list
with RandomNumbers as
(
select id = 1, number = round(((56 - 1 -1) * RAND(CHECKSUM(NEWID())) + 1), 0),
orderid = round(((56 - 1 -1) * RAND(CHECKSUM(NEWID())) + 1), 0)
union all
select id + 1, round(((56 - 1 -1) * RAND(CHECKSUM(NEWID())) + 1), 0), round(((56 - 1 -1) * RAND(CHECKSUM(NEWID())) + 1), 0)
from RandomNumbers where id < 100
),
NoDuplicates as
(
select number, id = row_number() over (partition by number orderid by order) from
(
select numeber, order, repeat = row_number() over (partition by numeber orderid by order)
from RandomNumbers
) a
where repeat = 1
)
select * from NoDuplicates where id <= 6
You can use Rand() function .
select CEILING(RAND() *<max of random numbers))

Generate a random number in the range 1 - 10

Since my approach for a test query which I worked on in this question did not work out, I'm trying something else now. Is there a way to tell pg's random() function to get me only numbers between 1 and 10?
If by numbers between 1 and 10 you mean any float that is >= 1 and < 10, then it's easy:
select random() * 9 + 1
This can be easily tested with:
# select min(i), max(i) from (
select random() * 9 + 1 as i from generate_series(1,1000000)
) q;
min | max
-----------------+------------------
1.0000083274208 | 9.99999571684748
(1 row)
If you want integers, that are >= 1 and < 10, then it's simple:
select trunc(random() * 9 + 1)
And again, simple test:
# select min(i), max(i) from (
select trunc(random() * 9 + 1) as i from generate_series(1,1000000)
) q;
min | max
-----+-----
1 | 9
(1 row)
To summarize and a bit simplify, you can use:
-- 0 - 9
select floor(random() * 10);
-- 0 - 10
SELECT floor(random() * (10 + 1));
-- 1 - 10
SELECT ceil(random() * 10);
And you can test this like mentioned by #user80168
-- 0 - 9
SELECT min(i), max(i) FROM (SELECT floor(random() * 10) AS i FROM generate_series(0, 100000)) q;
-- 0 - 10
SELECT min(i), max(i) FROM (SELECT floor(random() * (10 + 1)) AS i FROM generate_series(0, 100000)) q;
-- 1 - 10
SELECT min(i), max(i) FROM (SELECT ceil(random() * 10) AS i FROM generate_series(0, 100000)) q;
If you are using SQL Server then correct way to get integer is
SELECT Cast(RAND()*(b-a)+a as int);
Where
'b' is the upper limit
'a' is lower limit
(trunc(random() * 10) % 10) + 1
The correct version of hythlodayr's answer.
-- ERROR: operator does not exist: double precision % integer
-- LINE 1: select (trunc(random() * 10) % 10) + 1
The output from trunc has to be converted to INTEGER. But it can be done without trunc. So it turns out to be simple.
select (random() * 9)::INTEGER + 1
Generates an INTEGER output in range [1, 10] i.e. both 1 & 10 inclusive.
For any number (floats), see user80168's answer. i.e just don't convert it to INTEGER.
Example
SELECT RAND()*(min-max)+max
Actually I don't know you want to this.
try this
INSERT INTO my_table (my_column)
SELECT
(random() * 10) + 1
;
This stored procedure inserts a rand number into a table. Look out, it inserts an endless numbers. Stop executing it when u get enough numbers.
create a table for the cursor:
CREATE TABLE [dbo].[SearchIndex](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Cursor] [nvarchar](255) NULL)
GO
Create a table to contain your numbers:
CREATE TABLE [dbo].[ID](
[IDN] [int] IDENTITY(1,1) NOT NULL,
[ID] [int] NULL)
INSERTING THE SCRIPT :
INSERT INTO [SearchIndex]([Cursor]) SELECT N'INSERT INTO ID SELECT FLOOR(rand() * 9 + 1) SELECT COUNT (ID) FROM ID
CREATING AND EXECUTING THE PROCEDURE:
CREATE PROCEDURE [dbo].[RandNumbers] AS
BEGIN
Declare CURSE CURSOR FOR (SELECT [Cursor] FROM [dbo].[SearchIndex] WHERE [Cursor] IS NOT NULL)
DECLARE #RandNoSscript NVARCHAR (250)
OPEN CURSE
FETCH NEXT FROM CURSE
INTO #RandNoSscript
WHILE ##FETCH_STATUS IS NOT NULL
BEGIN
Print #RandNoSscript
EXEC SP_EXECUTESQL #RandNoSscript;
END
END
GO
Fill your table:
EXEC RandNumbers
Try This:
Select (ROW_NUMBER() OVER (ORDER BY ItemDesc ASC)+15000) as ID, ItemCode, ItemDesc
Using Postgres, here is how to generate random number between any 2 numbers, say, min and max:
Including min and Excluding max,
SELECT floor(random() * (max - min)) + min;
Including both min and max,
SELECT floor(random() * (max - min + 1)) + min;
So to get numbers between 1 and 10 (including 10),
min = 1, max = 10
SELECT floor(random() * (10 - 1 + 1)) + 1;
In general, you can use this formula to get random integer numbers between any min and max numbers.