Generate sequence in SQL Server, poor performance with cross apply - sql

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

Related

Find missed max and min value in a sequence of numbers

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

Pass Table Values into Variables

I have a table with Names and some values. I want to pass the values associated to the names into another table to prepopulate a list of values. I'm not sure the best way to approach this either by creating a function or procedure.
Below is the query I have now where I have to SET the variables manually. But rather doing it manually I would like to pass the values from the other table into this. How would I do this?
Example I have a table called ABC and in that table 3 values
Name|AVG |DEV
A |1.89|.74
B |2.43|1.20
C |.74 |.12
I want to pass the all the values from that table in the query below. The AVG value in the table into the #AVG variable in the below query and the DEV value in the table into the #deviation table below.
The results can be put into a new table or in a query.
DECLARE #avg DECIMAL(4,1) = 1.89 --this would be row 1 (value A) avg
DECLARE #deviation DECIMAL(4,1) = 0.74 --this would be row 1 (value A) DEV
DECLARE #startnum DECIMAL(4,1)= #Avg - (#deviation * 3)
DECLARE #endnum DECIMAL(4,1)= #Avg + (#deviation * 3)
;
WITH gen AS (
SELECT CAST(#startnum AS decimal(4,1)) AS Mulitiple
UNION ALL
SELECT CAST(Mulitiple+.1 AS decimal(4,1)) FROM gen WHERE Mulitiple+.1<=#endnum
)
SELECT A.Mulitiple , CAST(((A.Mulitiple-#avg)/#deviation) AS DECImAL(4,2)) AS ZScore
,Z.Y AS Area
FROM gen AS A
LEFT JOIN STAT..ZScore AS Z ON CAST(((A.Mulitiple-#avg)/#deviation) AS DECImAL(4,2)) = CAST(Z.Zscore AS decimal(4,2))
--WHERE Z.Y IS NOT NULL
option (maxrecursion 10000)
This approach uses a tally function named dbo.fnNumbers to generate the range of zscores.
dbo.fnNumbers
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;
Query
drop table if exists #abc;
go
create table #abc(
[name] varchar(2),
[avg] decimal(4,1),
[dev] decimal(4,1));
insert #abc([name], [avg], [dev]) values
('a', 1.89, .74),
('b', 2.43, 1.20),
('c', 0.74, .12);
select a.[name], gen.multiple, gen_z.zscore, z.y as area
from #abc a
cross apply dbo.fnNumbers(1, cast(2*(a.dev*3)+0.1 as decimal(4,1))*10) fn
cross apply (values (cast((a.[avg]-(a.dev*3)+(fn.N-1)*0.1) as decimal(4,1)))) gen(multiple)
cross apply (values (cast(((gen.multiple-a.[avg])/a.dev) AS decimal(4,2)))) gen_z(zscore)
left join stat..zscore z on gen_z.zscore=cast(z.zscore as decimal(4,2))
order by a.[name], gen.multiple;
If I understand correctly, you can just use JOIN. In this case, you can introduce the table using CROSS JOIN:
SELECT ABC.*, A.Mulitiple ,
CAST(((A.Mulitiple - ABC.avg) / ABC.dev) AS DECImAL(4, 2)) AS ZScore
,Z.Y AS Area
FROM ABC CROSS JOIN
gen a LEFT JOIN
STAT..ZScore Z
ON CAST(((A.Mulitiple - abc.avg)/ abc.dev) AS DECIMAL(4,2)) = CAST(Z.Zscore AS decimal(4,2))
--WHERE Z.Y IS NOT NULL
You can avoid having the formula twice by using:
SELECT ABC.*, A.Mulitiple, v.ZScore, Z.Y AS Area
FROM ABC CROSS JOIN
gen a CROSS APPLY
(VALUES (CAST((A.Mulitiple - ABC.avg) / ABC.dev AS DECImAL(4, 2))
) v(Zscore) LEFT JOIN
STAT..ZScore Z
ON v.ZScore = CAST(Z.Zscore AS decimal(4,2))
--WHERE Z.Y IS NOT NULL

Create Custom Sequence

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.

How to calculate the total time while excluding the overlapped time in Microsoft SQL?

I have a problem with tackling the Gaps and Islands type in this situation.
I want to calculate the total downtime in Microsoft SQL. Is there any I can produce the following output? Thank you!
The actual downtime = Total downtime - overlapped time
In this case:
Machine A: 14 hours
Machine B: 5 hours but 4 hours overlapped
Machine C: 1 hour
Machine D: 2 hours
Machine E: 1 hour overlapped
Machine F: 2 hours but 1 hour overlapped
In total, it is 19 hours as the actual downtime'
My table is a query. Please let me know how I can put the query in. Thank you!
Here's a solution based on techniques from Itzik Ben-Gan (noted in the source below). The solution uses the DENSE_RANK function. The code is complete - it can be copied into an SSMS query window and executed.
USE tempdb
GO
IF OBJECT_ID('dbo.GetNums', 'IF') IS NOT NULL
DROP FUNCTION dbo.GetNums;
GO
/* dbo.GetNums function is from Itzik Ben-Gan's article on packing intervals:
(http://blogs.solidq.com/en/sqlserver/packing-intervals/). */
CREATE FUNCTION dbo.GetNums(#n AS BIGINT)
RETURNS TABLE
AS
RETURN
WITH
L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
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 n FROM L5)
SELECT TOP (#n) n FROM Nums ORDER BY n;
GO
IF OBJECT_ID('dbo.Production', 'U') IS NOT NULL
DROP TABLE dbo.Production;
GO
CREATE TABLE dbo.Production
(
production_line INT NOT NULL,
machine CHAR(1) NOT NULL,
[date] DATE NOT NULL,
time_started TIME NOT NULL,
time_completed TIME NOT NULL,
CONSTRAINT PK_Production PRIMARY KEY(production_line, machine)
);
INSERT INTO dbo.Production
(production_line, machine, [date], time_started, time_completed)
VALUES
(1, 'A', '2018-01-16', '00:00:00', '14:00:00'),
(1, 'B', '2018-01-16', '10:00:00', '15:00:00'),
(1, 'C', '2018-01-16', '17:00:00', '18:00:00'),
(1, 'D', '2018-01-16', '21:00:00', '23:00:00'),
(1, 'E', '2018-01-16', '21:30:00', '22:30:00'),
(1, 'F', '2018-01-16', '17:00:00', '19:00:00');
/* Algorithm adapted from "Microsoft SQL Server 2012
High-Performance T-SQL Using Window Functions" by
Itzik Ben-Gan (p. 198). */
DECLARE #production_date AS DATE = '2018-01-16';
DECLARE #from AS TIME = '00:00:00';
DECLARE #to AS TIME = '23:59:59';
WITH Hours AS
(
SELECT
DATEADD(hour, (nums.n - 1), #from) AS hr
FROM
dbo.GetNums(24 /* Hours in a day. */) AS nums
),
Groups AS
(
SELECT
H.hr,
DATEADD(hour, -1 * DENSE_RANK() OVER (ORDER BY H.hr), H.hr) AS grp
FROM
dbo.Production AS P
INNER JOIN Hours AS H ON H.hr BETWEEN P.time_started AND P.time_completed
WHERE
p.[date] = #production_date
),
Ranges AS
(
SELECT
MIN(hr) AS range_start,
MAX(hr) AS range_end
FROM
Groups
GROUP BY
grp
)
SELECT
SUM(DATEDIFF(hour, range_start, range_end)) AS hours_of_downtime
FROM
Ranges
DROP FUNCTION dbo.GetNums;
DROP TABLE dbo.Production;
EDIT: In response to OP's question about if their data comes from a query. This modified example removes the temporary dbo.Production table, and adds a Production Common Table Expression.
USE tempdb
GO
IF OBJECT_ID('dbo.GetNums', 'IF') IS NOT NULL
DROP FUNCTION dbo.GetNums;
GO
/* dbo.GetNums function is from Itzik Ben-Gan's article on packing intervals:
(http://blogs.solidq.com/en/sqlserver/packing-intervals/). */
CREATE FUNCTION dbo.GetNums(#n AS BIGINT)
RETURNS TABLE
AS
RETURN
WITH
L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
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 n FROM L5)
SELECT TOP (#n) n FROM Nums ORDER BY n;
GO
/* Algorithm adapted from "Microsoft SQL Server 2012
High-Performance T-SQL Using Window Functions" by
Itzik Ben-Gan (p. 198). */
DECLARE #production_date AS DATE = '2018-01-16';
DECLARE #from AS TIME = '00:00:00';
DECLARE #to AS TIME = '23:59:59';
WITH Hours AS
(
SELECT
DATEADD(hour, (nums.n - 1), #from) AS hr
FROM
dbo.GetNums(24 /* Hours in a day. */) AS nums
),
Production AS
(
SELECT
production_line,
machine,
[date],
time_started,
time_completed
FROM
production_table
WHERE
[date] = #production_date
),
Groups AS
(
SELECT
H.hr,
DATEADD(hour, -1 * DENSE_RANK() OVER (ORDER BY H.hr), H.hr) AS grp
FROM
Production AS P
INNER JOIN Hours AS H ON H.hr BETWEEN P.time_started AND P.time_completed
),
Ranges AS
(
SELECT
MIN(hr) AS range_start,
MAX(hr) AS range_end
FROM
Groups
GROUP BY
grp
)
SELECT
SUM(DATEDIFF(hour, range_start, range_end)) AS hours_of_downtime
FROM
Ranges
DROP FUNCTION dbo.GetNums;
This is pretty ugly, but here is what I did:
Combine all records
Check for overlaps
Get min start and max stop for overlaps
Remove overlaps from the original set
sum the start/stop deltas for overlaps and non-overlaps
I created a table using your sample data above and got your answer: 19.
And the code is:
WITH aset
AS (
SELECT [Machine]
, [Date]
, [TimeStarted]
, TimeCompleted
FROM [CEA_DBA].[dbo].[LineInteruptions]
WHERE date = '2018-01-16'),
overlaps
AS (
SELECT a.machine
AS a_machine
, b.machine
AS b_machine
, CASE
WHEN a.TimeStarted <= b.TimeStarted
THEN a.TimeStarted
ELSE b.TimeStarted
END
AS timeStarted
, CASE
WHEN a.TimeCompleted >= b.TimeCompleted
THEN a.TimeCompleted
ELSE b.TimeCompleted
END
AS timeCompleted
FROM aset
AS a
CROSS JOIN aset
AS b
WHERE b.TimeStarted <= a.timeCompleted
AND b.timecompleted >= a.timecompleted
AND a.Machine <> b.Machine),
nonoverlaps
AS (
SELECT aset.timeStarted
, aset.timeCompleted
FROM aset
LEFT OUTER JOIN overlaps
AS oa ON aset.Machine = oa.a_machine
LEFT OUTER JOIN overlaps
AS ob ON aset.Machine = ob.b_machine
WHERE oa.a_machine IS NULL
AND ob.b_machine IS NULL),
gset
AS (
SELECT TimeStarted
, TimeCompleted
FROM overlaps
UNION ALL
SELECT timestarted
, timecompleted
FROM nonoverlaps)
SELECT SUM(DATEDIFF(hour, TimeStarted, timeCompleted))
AS downtime
FROM gset;

How to generate decimal numbers sequence for particular number range

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.