Consider following example for above question.
Suppose we have series of decimal numbers like (12.50 ,13.20 etc.)
I want result as
12.51001
12.51002
....
13.19999
13.20000
Upto 5 decimal places it to be generated.
Is it possible in sql ?
Here is one trick using Recursive CTE
Cast your data to 5 decimal places in CTE to get the result in required format
;WITH cte
AS (SELECT Cast(12.50 AS NUMERIC(22, 5)) AS num --Min value from your data
UNION ALL
SELECT Cast(num + 0.00001 AS NUMERIC(22, 5))
FROM cte
WHERE num < Cast(13.20 AS NUMERIC(22, 5))) -- Max value from your data
SELECT *
FROM cte
OPTION (maxrecursion 0)
In your expected result, Data starts from 12.51001 though your sample data starts from 12.50. If you really need to start from 12.51001 then add 0.01001 to the source query of CTE
Cast(12.50 +0.01001 AS NUMERIC(22, 5)) AS num
use a number table or recursive cte
; with rcte as
(
select n = 1250000
union all
select n = n + 1
from rcte
where n < 1320000
)
select convert(decimal(10,5), n / 100000.0)
from rcte
option (maxrecursion 0)
DECLARE #startnum decimal(18,5)=12.50
DECLARE #endnum decimal(18,5)=13.20
set #startnum = #startnum+0.01
;WITH cte
AS (SELECT Cast(#startnum AS NUMERIC(22, 5)) AS num
UNION ALL
SELECT Cast(num + 0.00001 AS NUMERIC(22, 5))
FROM cte
WHERE num < Cast(#endnum AS NUMERIC(22, 5)))
SELECT *
FROM cte
OPTION (maxrecursion 0)
I would suggest not using a loop to generate your sequence, instead use a tally table. Aaron Bertrand has tested the various methods of generating a set and it comfortably out performs the recursive CTE. In fact, the recursive CTE is so bad it is removed from most of the results because it distorts the scale on the graphs by so much.
Generate a set or sequence without loops – part 1
Generate a set or sequence without loops – part 2
Generate a set or sequence without loops – part 3
So you could using something like:
DECLARE #StartNumber DECIMAL(10, 5) = 12.50,
#EndNumber DECIMAL(10, 5) = 13.20,
#Increment DECIMAL(10, 5) = 0.00001;
WITH N1 AS (SELECT N FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
N4 (N) AS (SELECT 1 FROM N3 AS N1 CROSS JOIN N3 AS N2)
SELECT TOP (1 + CONVERT(INT, CEILING((#EndNumber - #StartNumber) / #Increment)))
#StartNumber + ((ROW_NUMBER() OVER(ORDER BY N) - 1) * #Increment)
FROM N4;
As a quick benchmark on your requirement, if I change the end number up to 23.2 this consistently takes about 4 seconds to run on my machine, the recursive CTE takes about 10 seconds to produce the same set.
Related
For example, I have a sequence of numbers: {1, 2, 5, 7}.
I need to find the smallest and the biggest one, which are missed in this sequence (min=3 and max=6 for this example). Values can also be negative.
Here is my solution, but it doesn't pass on extra checking database (Wrong number of records (less by 1)), so I can't say what is exactly wrong. I also tried versions with LEFT OUTER JOIN and EXCEPT predicates - same problem. Please, help me to improve my solution.
WITH AA AS (SELECT MAX(Q_ID) MX
FROM UTQ),
BB AS (SELECT MIN(Q_ID) CODE
FROM UTQ
UNION ALL
SELECT CODE + 1
FROM BB
WHERE CODE < (SELECT MX
FROM AA)
)
SELECT MIN(CODE) MIN_RES, MAX(CODE) MAX_RES
FROM BB
WHERE CODE NOT IN (SELECT Q_ID
FROM UTQ)
One method is not exists:
select min(q_id + 1)
from utq
where not exists (select 1 from utq utq2 where utq2.q_id = utq.id + 1)
union all
select max(q_id - 1)
from utq
where not exists (select 1 from utq utq2 where utq2.q_id = utq.id - 1);
You can also use lead() and lag():
select min(case when next_q_id <> q_id + 1 then q_id + 1 end),
max(case when prev_q_id <> q_id - 1 then q_id - 1 end)
from (select utq.*,
lag(q_id) over (order by q_id) as prev_q_id,
lead(q_id) over (order by q_id) as next_q_id
from utq
) utq;
A tally based method seems like a good approach here. Especially if the sequences are large.
The first CTE summarizes the maximum and minimum q_id's in the test table. The second CTE selects the missing integers by generating the complete sequence (using the fnNumbers tvf) between the minimum and maximum q_id values and comparing WHERE NOT EXISTS to the original sequence. Something like this.
numbers function
create function [dbo].[fnNumbers](
#zero_or_one bit,
#n bigint)
returns table with schemabinding as return
with n(n) as (select null from (values (1),(2),(3),(4)) n(n))
select 0 n where #zero_or_one = 0
union all
select top(#n) row_number() over(order by (select null)) n
from n na, n nb, n nc, n nd, n ne, n nf, n ng, n nh,
n ni, n nj, n nk, n nl, n nm, n np, n nq, n nr;
data and query
drop table if exists #seq;
go
create table #seq(
q_id int unique not null);
insert #seq values (1),(2),(5),(7);
with
max_min_cte(max_q, min_q) as (
select max(q_id), min(q_id)
from #seq),
missing_cte(q_id) as (
select mm.min_q+fn.n
from max_min_cte mm
cross apply dbo.fnNumbers(0, mm.max_q-mm.min_q) fn
where not exists (select 1
from #seq s
where (mm.min_q+fn.n)=s.q_id))
select max(q_id) max_missing, min(q_id) min_missing
from missing_cte;
output
max_missing min_missing
6 3
You can try like following using LEAD
SELECT MIN(Q_ID + 1) AS MinValue
,MAX(Q_ID + 1) AS MaxValue
FROM (
SELECT *,LEAD(Q_ID) OVER (ORDER BY Q_ID) NQ_ID
FROM (VALUES (1),(2),(5),(7)) v(Q_ID)
) t
WHERE NQ_ID - Q_ID <> 1
I need to create a Custom Sequence which should create sequence for below ranges
XV00AA-XV99ZZ and many other ranges
Example:
XV00AA, XV01AA, XV02AA, ......XV99AA
The first 2 characters remain the same (example - XV series); the last 2 characters remain the same (example - AA series); but the middle 2 characters should increment from 0 to 99. (Example XV01AA, XV02AA, XV03AA and so on)
Once it reaches 99 (i.e. XV99AA) then it should repeat for AB series
So output should be
XV00AB, XV01AB, XV02AB, .....XV99AB
And then
XV00AC,XV01AC,XV02AC....XV99AC
So sample final output:
XV00AA
XV01AA
XV02AA
....
XV99AA
XV00AB
XV01AB
XV02AB
....
XV99AB
XV00AC
XV01AC
XV02AC
...
XV99AC
XV00AD
XV01AD
XV02AD
...
XV99AD
and so on. Is there any easy way to create these series? Any help would be appreciated
This should do the trick.
DECLARE #how_many_do_you_want INT = 67599; -- 67599 is where it runs out of legit values.
WITH
cte_n1 (n) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (n)),
cte_n2 (n) AS (SELECT 1 FROM cte_n1 a CROSS JOIN cte_n1 b),
cte_n3 (n) AS (SELECT 1 FROM cte_n2 a CROSS JOIN cte_n2 b),
cte_Tally (n) AS (
SELECT TOP (#how_many_do_you_want)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
cte_n3 a CROSS JOIN cte_n3 b
)
SELECT
rn = t.n,
CONCAT('XV', cn.char_num, a.alpha_1, a.alpha_2)
FROM
cte_Tally t
CROSS APPLY ( VALUES (t.n % 100) ) m (mod_100)
CROSS APPLY ( VALUES (((t.n - m.mod_100) / 100) % 26 + 1) ) g1 (group_1)
CROSS APPLY ( VALUES (t.n / 2600 + 1) ) g2 (group_2)
CROSS APPLY ( VALUES (RIGHT(CONCAT('0', m.mod_100), 2)) ) cn (char_num)
CROSS APPLY ( VALUES (CHAR(g1.group_1 + 64), CHAR(g2.group_2 + 64)) ) a (alpha_1, alpha_2);
The following code uses a CTE to generate a table of numbers from 0 to 67,599. The values are then split apart: modulus (%) provides the value for the digits and integer division (/) provides the value for the letters. A little fiddling for formatting and conversion and Bob's your uncle.
with Numbers as (
select 0 as N
union all
select N + 1
from Numbers
where N < 67599 )
select N, N % 100 as DigitsValue, N / 100 as LettersValue,
Right( '0' + Cast( N % 100 as VarChar(2) ), 2 ) as LeftPaddedDigits,
Char( ASCII( 'A' ) + ( N / 100 ) % 26 ) as LeastSignificantLetter,
Char( ASCII( 'A' ) + ( N / 100 ) / 26 ) as MostSignificantLetter
from Numbers
option ( MaxRecursion 0 )
Putting the complete string together with "XV" as a prefix is left to the reader.
I have a field that contains numbers such as the examples below in #Numbers. Each number within each row in #Numbers relates
to many different values that are contained within the #Area table.
I need to make a relationship from #Numbers to #Area using each number within each row.
CREATE TABLE #Numbers
(
Number int
)
INSERT INTO #Numbers
(
Number
)
SELECT 102 UNION
SELECT 1 UNION
SELECT 2 UNION
select * from #Numbers
CREATE TABLE #Area
(
Number int,
Area varchar(50)
)
INSERT INTO #Area
(
Number,
Area
)
SELECT 0,'Area1' UNION
SELECT 1,'Area2' UNION
SELECT 1,'Area3' UNION
SELECT 1,'Area5' UNION
SELECT 1,'Area8' UNION
SELECT 1,'Area9' UNION
SELECT 2,'Area12' UNION
SELECT 2,'Area43' UNION
SELECT 2,'Area25' UNION
select * from #Area
It would return the following for 102:
102,Area2
102,Area3
102,Area5
102,Area8
102,Area9
102,Area1
102,Area12
102,Area43
102,Area25
For 1 it would return:
1,Area2
1,Area3
1,Area5
1,Area8
1,Area9
For 2 it would return:
2,Area12
2,Area43
2,Area25
Note how the numbers match up to the individual Areas and return the values accordingly.
Well, the OP marked an answer already, which even got votes. Maybe he will not read this, but here is another option using direct simple select, which (according to the EP) seems like using a lot less resources:
SELECT *
FROM #Numbers t1
LEFT JOIN #Area t2 ON CONVERT(VARCHAR(10), t1.Number) like '%' + CONVERT(CHAR(1), t2.Number) + '%'
GO
Note! According to Execution Plan this solution uses only 27% while the selected answer (written by Squirrel) uses 73%, but Execution Plan can be misleading sometimes and you should check IO and TIME statistics as well using the real table structure and real data.
looks like you need to extract individual digit from #Number and then used it to join to #Area
; with tally as
(
select n = 1
union all
select n = n + 1
from tally
where n < 10
)
select n.Number, a.Area
from #Numbers n
cross apply
(
-- here it convert n.Number to string
-- then extract 1 digit
-- and finally convert back to integer
select num = convert(int,
substring(convert(varchar(10), n.Number),
t.n,
1)
)
from tally t
where t.n <= len(convert(varchar(10), n.Number))
) d
inner join #Area a on d.num = a.Number
order by n.Number
or if you prefer to do it in arithmetic and not string
; with Num as
(
select Number, n = 0, Num = Number / power(10, 0) % 10
from #Numbers
union all
select Number, n = n + 1, Num = Number / power(10, n + 1) % 10
from Num
where Number > power(10, n + 1)
)
select n.Number, a.Area
from Num n
inner join #Area a on n.Num = a.Number
order by n.Number
Here is my idea. In theory, it should work.
Have a table (temp or permanent) with the values and it's translation
I.E.
ID value
1 Area1, Area2, Area7, Area8, Area15
2 Area28, Area35
etc
Take each row and put a some special character between each number. Use a function like string_split with that character to turn it into a column of values.
e.g 0123 will then be something like 0|1|2|3 and when you run that through string_split you would get
0
1
2
3
Now join each value to your lookup table and return the Value.
Now you have a row with all the values that you want. Use another function like STUFF FOR XML and put those values back into a single column.
This doesn't sound very efficient.. but this is one way of achieving what you desire..
Another is to do a replace().. but that would be very messy!
Create a third table called n which contains a single column also called n that contains integers from 1 to the maximum number of digits in your number. Make it 1000 if you like, doesn't matter. Then:
select #numbers.number, substring(convert(varchar,#numbers.number),n,1) as chr, Area
from #numbers
join n on n>0 and n <=len(convert(varchar,number))
join #area on #area.number=substring(convert(varchar,#numbers.number),n,1)
The middle column chr is just there to show you what it's doing, and would be removed from the final result.
Have a column ID that I would like to ORDER in a specific format. Column has a varchar data type and always has an alphabetic value, typically P in front followed by three to four numeric values. Possibly even followed by an underscore or another alphabetic value. I have tried few options and none are returning what I desire.
SELECT [ID] FROM MYTABLE
ORDER BY
(1) LEN(ID), ID ASC
/ (2) LEFT(ID,2)
OPTIONS TRIED (3) SUBSTRING(ID,2,4) ASC
\ (4) ROW_NUMBER() OVER (ORDER BY SUBSTRING(ID,2,4))
(5) SUBSTRING(ID,PATINDEX('%[0-9]%',ID),LEN(ID))
(6) LEFT(ID, PATINDEX('%[0-9]%', ID)-1)
Option 1 seems to be closest to what I am looking for except when an _ or Alphabetic values follow the numeric value. See results from Option 1 below
P100
P208
P218
P301
P305
P306
P4200
P4510
P4511
P4512
P5011
P1400A
P4125H
P4202A
P4507L
P4706A
P1001_2
P2103_B
P4368_RL
Would like to see..
P100
P208
P218
P301
P305
P306
P1001_2
P1400A
P2103_B
P4125H
P4200
P4202A
P4368_RL
P4507L
P4510
P4511
P4512
P4706A
P5011
ORDER BY
CAST(SUBSTRING(id, 2, 4) AS INT),
SUBSTRING(id, 6, 3)
http://sqlfiddle.com/#!6/9eecb7db59d16c80417c72d1e1f4fbf1/9464
And one that's still less complex than a getOnlyNumbers() UDF, but copes with varying length of numeric part.
CROSS APPLY
(
SELECT
tail_start = PATINDEX('%[0-9][^0-9]%', id + '_')
)
stats
CROSS APPLY
(
SELECT
numeric = CAST(SUBSTRING(id, 2, stats.tail_start-1) AS INT),
alpha = RIGHT(id, LEN(id) - stats.tail_start)
)
id_tuple
ORDER BY
id_tuple.numeric,
id_tuple.alpha
http://sqlfiddle.com/#!6/9eecb7db59d16c80417c72d1e1f4fbf1/9499
Finally, one that can cope with there being no number at all (but still assumes the first character exists and should be ignored).
CROSS APPLY
(
SELECT
tail_start = NULLIF(PATINDEX('%[0-9][^0-9]%', id + '_'), 0)
)
stats
CROSS APPLY
(
SELECT
numeric = CAST(SUBSTRING(id, 2, stats.tail_start-1) AS INT),
alpha = RIGHT(id, LEN(id) - ISNULL(stats.tail_start, 1))
)
id_tuple
ORDER BY
id_tuple.numeric,
id_tuple.alpha
http://sqlfiddle.com/#!6/9eecb7db59d16c80417c72d1e1f4fbf1/9507
This is a rather strange way to sort but now that I understand it I figured out a solution. I am using a table valued function here to strip out only the numbers from a string. Since the function returns all numeric characters I also need to check for the _ and only pass in the part of the string before that.
Here is the function.
create function GetOnlyNumbers
(
#SearchVal varchar(8000)
) returns table as return
with MyValues as
(
select substring(#SearchVal, N, 1) as number
, t.N
from cteTally t
where N <= len(#SearchVal)
and substring(#SearchVal, N, 1) like '[0-9]'
)
select distinct NumValue = STUFF((select number + ''
from MyValues mv2
order by mv2.N
for xml path('')), 1, 0, '')
from MyValues mv
This function is using a tally table. If you have one you can tweak that code slightly to fit. Here is my tally table. I keep it as a view.
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
GO
Next of course we need to have some data to work. In this case I just created a table variable to represent your actual table.
declare #Something table
(
SomeVal varchar(10)
)
insert #Something values
('P100')
, ('P208')
, ('P218')
, ('P301')
, ('P305')
, ('P306')
, ('P4200')
, ('P4510')
, ('P4511')
, ('P4512')
, ('P5011')
, ('P1400A')
, ('P4125H')
, ('P4202A')
, ('P4507L')
, ('P4706A')
, ('P1001_2')
, ('P2103_B')
, ('P4368_RL')
With all the legwork and setup behind us we can get to the actual query needed to accomplish this.
select s.SomeVal
from #Something s
cross apply dbo.GetOnlyNumbers(case when charindex('_', s.SomeVal) = 0 then s.SomeVal else left(s.SomeVal, charindex('_', s.SomeVal) - 1) end) x
order by convert(int, x.NumValue)
This returns the rows in the order you listed them in your question.
You can break down ID in steps to extract the number. Then, order by the number and ID. I like to break down long string manipulation into steps using CROSS APPLY. You can do it inline (it'd be long) or bundle it into an inline TVF.
SELECT t.*
FROM MYTABLE t
CROSS APPLY (SELECT NoP = STUFF(ID, 1, 1, '')) nop
CROSS APPLY (SELECT FindNonNumeric = LEFT(NoP, ISNULL(NULLIF(PATINDEX('%[^0-9]%', NoP)-1, -1), LEN(NoP)))) fnn
CROSS APPLY (SELECT Number = CONVERT(INT, FindNonNumeric)) num
ORDER BY Number
, ID;
I think your best bet is to create a function that strips the numbers out of the string, like this one, and then sort by that. Even better, as #SeanLange suggested, would be to use that function to store the number value in a new column and sort by that.
I obtained the following code from the web many years ago and it has served my very well. It is simply a function that generates a sequence of numbers from 1 to whatever you pass in.
Basically it's a way of doing a for loop in a SQL statement.
CREATE FUNCTION [SequenceCreate]
(#MaxValue INT)
RETURNS TABLE
AS
RETURN
WITH
Num1 (n) AS (SELECT 1 UNION ALL SELECT 1),
Num2 (n) AS (SELECT 1 FROM Num1 AS X, Num1 AS Y),
Num3 (n) AS (SELECT 1 FROM Num2 AS X, Num2 AS Y),
Num4 (n) AS (SELECT 1 FROM Num3 AS X, Num3 AS Y),
Num5 (n) AS (SELECT 1 FROM Num4 AS X, Num4 AS Y),
Num6 (n) AS (SELECT 1 FROM Num5 AS X, Num5 AS Y),
Nums (n) AS
(SELECT ROW_NUMBER() OVER(ORDER BY n)
FROM Num6)
SELECT n AS [Value] FROM Nums
WHERE n BETWEEN 1 AND #MaxValue;
This generally works very well and is fast but I have found its performance to be very poor when using a cross apply statement, eg
DECLARE #T TABLE(StartNum INT, ItemCount INT)
INSERT INTO #T VALUES (100, 5)
INSERT INTO #T VALUES (110, 7)
INSERT INTO #T VALUES (55, 3)
SELECT Seq.Value + StartNum FROM #T
CROSS APPLY he.SequenceCreate(ItemCount) AS Seq
This is very slow on my machine. Does anyone know why it works fine when executed once but runs very badly when executed 3 times via cross apply? Even if the #T table contains only 1 row the performance is still terrible. Is there a better way to write this?
Thanks in advance,
Michael
The query optimizer figures it is better to only execute the function once and then use the result in a join to get the rows you want. It is done like that because your function is an inline table valued function. If you instead make your function a multi-statement valued function it will execute the function once for each row in your source table. However I would recommend that you create a numbers table instead as suggested by Igor.
CREATE FUNCTION [SequenceCreate]
(#MaxValue INT)
RETURNS #T TABLE ([Value] INT NOT NULL PRIMARY KEY)
AS
BEGIN
WITH
Num1 (n) AS (SELECT 1 UNION ALL SELECT 1),
Num2 (n) AS (SELECT 1 FROM Num1 AS X, Num1 AS Y),
Num3 (n) AS (SELECT 1 FROM Num2 AS X, Num2 AS Y),
Num4 (n) AS (SELECT 1 FROM Num3 AS X, Num3 AS Y),
Num5 (n) AS (SELECT 1 FROM Num4 AS X, Num4 AS Y),
Num6 (n) AS (SELECT 1 FROM Num5 AS X, Num5 AS Y),
Nums (n) AS
(SELECT ROW_NUMBER() OVER(ORDER BY n)
FROM Num6)
INSERT INTO #T
SELECT n AS [Value] FROM Nums
WHERE n BETWEEN 1 AND #MaxValue;
RETURN
END
If you look at estimated execution plans of both of your queries you will see a lot of Constant Scans wich outputs are joined by Nested Loops.
In case of
select * from dbo.SequenceCreate (100)
Estimated number of rows for every Constant Scan is 1
In case of
SELECT N.N + StartNum
FROM #T t
LEFT JOIN Numbers AS N ON N.N <= T.ItemCount
Estimated number of rows for every Constant Scan is 2. So this is good example of geometric progression. The last Neste Loops returns 4294970000 rows - 36 GB.
I cannot say why optimizer choose this plan but it choose it.
You can use following approach instead.
First, create table with consecutiv numbers
CREATE TABLE Numbers(N INT PRIMARY KEY NOT NULL IDENTITY(1,1));
GO
INSERT INTO Numbers DEFAULT VALUES;
GO 1000 -- it takes about 2 minutes for 1000 but you need to execut it just once
Use following script:
DECLARE #T TABLE(StartNum INT, ItemCount INT)
INSERT INTO #T VALUES (100, 5)
INSERT INTO #T VALUES (110, 7)
INSERT INTO #T VALUES (55, 3)
SELECT N.N + StartNum
FROM #T t
LEFT JOIN Numbers AS N ON N.N <= T.ItemCount
Your single biggest issue is the following line of code...
WHERE n BETWEEN 1 AND #MaxValue;
Itzik Ben-Gan point out in his second post when he first came up with the idea of the very effective cascading CTE's (cCTEs for short)that SQL server sometime does things a little crazy and can generate ALL of the numbers that the cCTEs are capable of generating before the WHERE clause takes effect.
To the best of my knowledge, the following is a copy of Itzik's latest code.
----------------------------------------------------------------------
-- © Itzik Ben-Gan
-- For more, see 5-day Advanced T-SQL Course:
-- http://tsql.Lucient.com/t-sql-courses/
----------------------------------------------------------------------
IF OBJECT_ID(N'dbo.GetNums', N'IF') IS NOT NULL DROP FUNCTION dbo.GetNums;
GO
CREATE FUNCTION dbo.GetNums(#low AS BIGINT, #high AS BIGINT) RETURNS TABLE
AS
RETURN
WITH
L0 AS (SELECT c FROM (SELECT 1 UNION ALL SELECT 1) AS D(c)),
L1 AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
L2 AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
L3 AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
L4 AS (SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
L5 AS (SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B),
Nums AS (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum
FROM L5)
SELECT TOP(#high - #low + 1) #low + rownum - 1 AS n
FROM Nums
ORDER BY rownum;
GO
The source link for that code is as follows:
http://tsql.lucient.com/SourceCodes/GetNums.txt
And here's the article that I was speaking of...
https://www.itprotoday.com/open-source-sql/fangraphs-tags-cloud-database-keep-big-show