We have a set of records where each record (Job) has a cycle (or frequency) in days that determines when the job appears on somebody's work list. E.G
Job A is generated every 7 days
Job B is generated every 14 days.
Each Job also has a start date.
I need to generate a table which contains all the possible job dates between a date range.
Here is my first part of code which generates the possible dates.
DECLARE #ForecastEarliestStartDate as DATETIME, #formStartDate as DATE,#formEndDate as DATE, #cycleInDays as BIGINT;
--set input param values
SET #ForecastEarliestStartDate = CAST('2017-07-03' AS DATETIME)
SET #formStartDate = getdate();
SET #formEndDate = getdate()+60;
SET #cycleInDays = 28;
WITH mycte AS
(
SELECT #ForecastEarliestStartDate DateValue
UNION ALL
SELECT DateValue + #cycleInDays
FROM mycte
WHERE DateValue + #cycleInDays < #formEndDate
)
SELECT *
FROM mycte
WHERE DateValue between #formStartDate and #formEndDate
OPTION (MAXRECURSION 0);
This works fine as for a single Job, but how can I run it against a whole table of Jobs where #ForecastEarliestStartDate, #formStartDate, #formEndDate, #cycleInDays are fields in a table?
I'll often use a TVF to create dynamic date/time ranges. Often faster than a recursive CTE and it is parameter driven. You supply the date/time range, datepart, and increment.
For Example
Select * From [dbo].[udf-Range-Date]('2017-07-03',getdate()+60,'DD',7)
Returns
RetSeq RetVal
1 2017-07-03
2 2017-07-10
3 2017-07-17
4 2017-07-24
5 2017-07-31
6 2017-08-07
7 2017-08-14
8 2017-08-21
9 2017-08-28
The UDF if Interested
CREATE FUNCTION [dbo].[udf-Range-Date] (#R1 datetime,#R2 datetime,#Part varchar(10),#Incr int)
Returns Table
Return (
with cte0(M) As (Select 1+Case #Part When 'YY' then DateDiff(YY,#R1,#R2)/#Incr When 'QQ' then DateDiff(QQ,#R1,#R2)/#Incr When 'MM' then DateDiff(MM,#R1,#R2)/#Incr When 'WK' then DateDiff(WK,#R1,#R2)/#Incr When 'DD' then DateDiff(DD,#R1,#R2)/#Incr When 'HH' then DateDiff(HH,#R1,#R2)/#Incr When 'MI' then DateDiff(MI,#R1,#R2)/#Incr When 'SS' then DateDiff(SS,#R1,#R2)/#Incr End),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
cte3(N,D) As (Select 0,#R1 Union All Select N,Case #Part When 'YY' then DateAdd(YY, N*#Incr, #R1) When 'QQ' then DateAdd(QQ, N*#Incr, #R1) When 'MM' then DateAdd(MM, N*#Incr, #R1) When 'WK' then DateAdd(WK, N*#Incr, #R1) When 'DD' then DateAdd(DD, N*#Incr, #R1) When 'HH' then DateAdd(HH, N*#Incr, #R1) When 'MI' then DateAdd(MI, N*#Incr, #R1) When 'SS' then DateAdd(SS, N*#Incr, #R1) End From cte2 )
Select RetSeq = N+1
,RetVal = D
From cte3,cte0
Where D<=#R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1)
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1)
*/
Edit - Working Example of CROSS APPLY
Declare #YourTable table (JobName varchar(50),StartDate date,Interval int)
Insert Into #YourTable values
('Job A','2017-07-03',7)
,('Job B','2017-07-03',14)
Select A.JobName
,JobDate = B.RetVal
From #YourTable A
Cross Apply [dbo].[udf-Range-Date](A.StartDate,getdate()+60,'DD',A.Interval) B
Returns
JobName JobDate
Job A 2017-07-03
Job A 2017-07-10
Job A 2017-07-17
Job A 2017-07-24
Job A 2017-07-31
Job A 2017-08-07
Job A 2017-08-14
Job A 2017-08-21
Job A 2017-08-28
Job B 2017-07-03 -- << Notice differant span for Job B
Job B 2017-07-17
Job B 2017-07-31
Job B 2017-08-14
Job B 2017-08-28
The first thing to note is that a recursive CTE is one of the worst ways to generate a set or series (the worst being with explicit loops). Before going any further I would recommend reading the following series of articles:
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
For example's sake, I will assume that you don't have a numbers table, and can't create one, so I will use the stacked CTE method. This query will get you a list of numbers from 0 to 99,999.
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)
SELECT N = ROW_NUMBER() OVER(ORDER BY N) - 1
FROM N3
If you need more numbers, you can add cross joins, if you need less you can remove them.
You can then just join these numbers to your table of jobs, each time adding (n * CycleInDays) to your start date:
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),
Numbers (Number) AS (SELECT N = ROW_NUMBER() OVER(ORDER BY N) - 1 FROM N3)
SELECT t.*,
Iteration = n.Number,
Date = DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
FROM (VALUES
(1, CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-09-03'), 28 ),
(2, CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-09-03'), 19)
) t (JobID, ForecastEarliestStartDate, formStartDate, formEndDate, cycleInDays)
INNER JOIN Numbers AS N
ON t.formEndDate >= DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
AND t.formStartDate <= DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
ORDER BY t.JobID, n.Number;
This gives:
JobID ForecastEarliestStartDate formStartDate formEndDate cycleInDays Iteration Date
------------------------------------------------------------------------------------------------------------------
1 2017-07-03 2017-07-03 2017-09-03 28 0 2017-07-03
1 2017-07-03 2017-07-03 2017-09-03 28 1 2017-07-31
1 2017-07-03 2017-07-03 2017-09-03 28 2 2017-08-28
2 2017-07-03 2017-07-03 2017-09-03 19 0 2017-07-03
2 2017-07-03 2017-07-03 2017-09-03 19 1 2017-07-22
2 2017-07-03 2017-07-03 2017-09-03 19 2 2017-08-10
2 2017-07-03 2017-07-03 2017-09-03 19 3 2017-08-29
ADDENDUM
In response to the comments:
Great. So the general idea is that a loop is slower but if you genuinely don't know how man iterations there could be, what then? A loop?
No, as long as your numbers table is big enough to cover the maximum iterations then you don't need a loop. The example I have used with 100,000 rows is enough to cover 273 years of jobs that run every single day. I would have thought this would suffice.
BTW I do have a numbers table. Could you show me how to solve my problem without the need generating it please
Sure, simply remove the CTE that generates the numbers, and change the reference to the Numbers CTE to whatever your numbers table is called:
SELECT t.*,
Iteration = n.Number,
Date = DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
FROM (VALUES
(1, CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-09-03'), 28 ),
(2, CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-09-03'), 19)
) t (JobID, ForecastEarliestStartDate, formStartDate, formEndDate, cycleInDays)
INNER JOIN dbo.Numbers AS N ---- CHANGE TO WHATEVER YOUR NUMBERS TABLE IS CALLED
ON t.formEndDate >= DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
AND t.formStartDate <= DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
ORDER BY t.JobID, n.Number;
Just noticed you are hard coding the Jobs into the query. They exist in another table, so not sure how this solves my issue
I have hard coded the jobs into the query just to emulate the table that you have (I have had to guess a bit since there is not much information about this table in the question). Simply replace the table-value constructor I have used with your actual table. e.g.
SELECT t.*,
Iteration = n.Number,
Date = DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
FROM dbo.[Your Job Table] AS t
INNER JOIN dbo.[Your Numbers Table] AS N
ON t.formEndDate >= DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
AND t.formStartDate <= DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
ORDER BY t.JobID, n.Number;
Related
I have below query and i want to get datetime in 30 min intervals between 2 datetime. Basicly I got it, but is limitited and wouln't return al results if the timediff is over 24 hrs.
For example:
#DateTime1 = 24/11/2016 18:00:00
#DateTime2 = 25/11/2016 06:00:00
Result: (in format "dd-HH:mm")
24-18:00
24-18:30
24-19:00
24-19:30
24-20:00
...
...
25-05:00
25-05:30
25-06:00
What I've tried.
SELECT number, DATEADD(MINUTE, number, #DateTime1) AS DateTimeLine, DATEPART(DAY, DATEADD(MINUTE, number, #DateTime1)) AS Days, DATEPART(MONTH,
DATEADD(MINUTE, number, #DateTime1)) AS Months, DATEPART(YEAR, DATEADD(MINUTE, number, #DateTime1)) AS Years, DATEPART(HOUR, DATEADD(MINUTE,
number, #DateTime1)) AS Hours, DATEPART(MINUTE, DATEADD(MINUTE, number, #DateTime1)) AS Minute, CAST(DATEADD(MINUTE, number, #DateTime1)
AS DATE) AS Date, CAST(DATEADD(MINUTE, number, #DateTime1) AS TIME) AS Time
FROM master.dbo.spt_values
WHERE (type = 'P') AND (DATEPART(MINUTE, DATEADD(MINUTE, number, #DateTime1)) = 30 OR DATEPART(MINUTE, DATEADD(MINUTE, number, #DateTime1)) = 0) AND (DATEADD(MINUTE, number, #DateTime1) <= #DateTime2)
ORDER BY number
A tally table is a great way to deal with this type of thing. I keep one in a view to avoid using spt_values.
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
Then your code becomes really simple too. A small amount of datemath and voila.
declare #DateTime1 datetime = '2016/11/24 18:00:00'
, #DateTime2 datetime = '2016/11/25 06:00:00'
select FORMAT(DATEADD(minute, (t.N - 1) * 30, #DateTime1), 'dd-HH:mm')
from cteTally t
where t.N <= (DATEDIFF(hour, #DateTime1, #DateTime2) * 2) + 1
I have a TVF which generates dynamic date/time ranges. It is faster than a recursive cte, and I think more flexible. You pass the date range, desired DatePart, and increment.
Declare #DateTime1 DateTime = '2016-11-24 18:00:00'
Declare #DateTime2 DateTime = '2016-11-25 06:00:00'
Select Format(RetVal,'dd-HH:mm') from [dbo].[udf-Range-Date](#DateTime1,#DateTime2,'MI',30)
Returns
24-18:00
24-18:30
24-19:00
24-19:30
24-20:00
24-20:30
24-21:00
24-21:30
24-22:00
24-22:30
24-23:00
24-23:30
25-00:00
....
25-04:30
25-05:00
25-05:30
25-06:00
The UDF if needed
CREATE FUNCTION [dbo].[udf-Range-Date] (#R1 datetime,#R2 datetime,#Part varchar(10),#Incr int)
Returns Table
Return (
with cte0(M) As (Select 1+Case #Part When 'YY' then DateDiff(YY,#R1,#R2)/#Incr When 'QQ' then DateDiff(QQ,#R1,#R2)/#Incr When 'MM' then DateDiff(MM,#R1,#R2)/#Incr When 'WK' then DateDiff(WK,#R1,#R2)/#Incr When 'DD' then DateDiff(DD,#R1,#R2)/#Incr When 'HH' then DateDiff(HH,#R1,#R2)/#Incr When 'MI' then DateDiff(MI,#R1,#R2)/#Incr When 'SS' then DateDiff(SS,#R1,#R2)/#Incr End),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
cte3(N,D) As (Select 0,#R1 Union All Select N,Case #Part When 'YY' then DateAdd(YY, N*#Incr, #R1) When 'QQ' then DateAdd(QQ, N*#Incr, #R1) When 'MM' then DateAdd(MM, N*#Incr, #R1) When 'WK' then DateAdd(WK, N*#Incr, #R1) When 'DD' then DateAdd(DD, N*#Incr, #R1) When 'HH' then DateAdd(HH, N*#Incr, #R1) When 'MI' then DateAdd(MI, N*#Incr, #R1) When 'SS' then DateAdd(SS, N*#Incr, #R1) End From cte2 )
Select RetSeq = N+1
,RetVal = D
From cte3,cte0
Where D<=#R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1)
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1)
*/
using a recursive Common Table Expression [CTE] is one pretty clean method. For the formatting I am showing FORMAT() from SQL-Server 2012+ you may consider using DATEPART etc to do it though as FORMAT() can have performance impact.
I do agree with #RossBush's comment if you do things like this a lot generating a calendar (dates) and a time dimensions is very helpful for these purposes.
DECLARE #DateTime1 DATETIME = '2016/11/24 18:00:00'
DECLARE #DateTime2 DATETIME = '2016/11/25 06:00:00'
;WITH cte30MinIncrements AS (
SELECT #DateTime1 as DT
UNION ALL
SELECT DATEADD(MINUTE,30,DT)
FROM
cte30MinIncrements
WHERE DATEADD(MINUTE,30,DT) <= #DateTime2
)
SELECT
*
,FORMAT(DT,'dd-HH:mm') as Formated
FROM
cte30MinIncrements
Please see if this works.
declare #DateTime1 DateTime = '2016-11-24 18:00:00'
declare #DateTime2 DateTime = '2016-11-25 18:00:00'
declare #Interval DateTime = #DateTime1
declare #vartmptable table(DT DateTime)
While (#Interval < #DateTime2)
begin
--select #Interval, FORMAT(#Interval,'dd-HH:mm')
insert into #vartmptable select #Interval
set #Interval = DATEADD(mi,30,#Interval)
end
select FORMAT(DT,'dd-HH:mm') from #vartmptable
What about this? You can use variables/ fixed values as necessary.
WITH CTE_Numbers
AS (
SELECT n = 1
UNION ALL
SELECT n + 1
FROM CTE_Numbers
WHERE n < 100
)
SELECT FORMAT(DATEADD(mi, n * 30, '2016/11/03'),'dd-HH:mm')
FROM CTE_Numbers
I would like to ask you for a help with the query. I´m trying to get list of random dates from 7.2.2016 - 14.2.2016, but I would like to exclude from it 11. and 12.2.2016, which will be weekends.
This is, what I have:
SELECT DATEADD(DAY, ABS(CHECKSUM(NEWID()) % 8), '2016-02-07')
I´m using SQL server 2016.
and the dates should be allocated randomly to other columns:
enter image description here
SMS_send_day should be those dates, excluding 11.2. and 12.2.
Thank you for your advices!
Not sure if this helps, but this gets you a single date excluding the two you asked for. I assume you meant 2017, because those days weren't weekends in 2016.
declare #date date
while 1=1
begin
select #date = DATEADD(DAY, ABS(CHECKSUM(NEWID()) % 8), '2017-02-07')
if #date not in ('2/11/2017', '2/12/2017')
break
end
select #date
Edit Saw the comment...If a list is needed you can try something like this:
t-sql get all dates between 2 dates and then just use a where clause to filter out the weekend dates.
A Calendar or Tally table would do the trick, but I often use a TVF to create dynamic date/time ranges. It is parameter driven, you define the Date/Time Range, DatePart and Increment
Example
Declare #D1 date = '2016-02-07'
Declare #D2 date = '2016-02-14'
Select Top 1 D=RetVal
From [dbo].[udf-Range-Date](#D1,#D2,'DD',1)
Where DatePart(DW,RetVal) not in (7,1)
Order By NewID()
Returns
D
2016-02-11 00:00:00.000
The UDF if Interested
CREATE FUNCTION [dbo].[udf-Range-Date] (#R1 datetime,#R2 datetime,#Part varchar(10),#Incr int)
Returns Table
Return (
with cte0(M) As (Select 1+Case #Part When 'YY' then DateDiff(YY,#R1,#R2)/#Incr When 'QQ' then DateDiff(QQ,#R1,#R2)/#Incr When 'MM' then DateDiff(MM,#R1,#R2)/#Incr When 'WK' then DateDiff(WK,#R1,#R2)/#Incr When 'DD' then DateDiff(DD,#R1,#R2)/#Incr When 'HH' then DateDiff(HH,#R1,#R2)/#Incr When 'MI' then DateDiff(MI,#R1,#R2)/#Incr When 'SS' then DateDiff(SS,#R1,#R2)/#Incr End),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
cte3(N,D) As (Select 0,#R1 Union All Select N,Case #Part When 'YY' then DateAdd(YY, N*#Incr, #R1) When 'QQ' then DateAdd(QQ, N*#Incr, #R1) When 'MM' then DateAdd(MM, N*#Incr, #R1) When 'WK' then DateAdd(WK, N*#Incr, #R1) When 'DD' then DateAdd(DD, N*#Incr, #R1) When 'HH' then DateAdd(HH, N*#Incr, #R1) When 'MI' then DateAdd(MI, N*#Incr, #R1) When 'SS' then DateAdd(SS, N*#Incr, #R1) End From cte2 )
Select RetSeq = N+1
,RetVal = D
From cte3,cte0
Where D<=#R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1)
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1)
This will do the trick. I have used Date & Convert Functions.
DECLARE #i INT = 0
WHILE (#i<=7)
BEGIN
DECLARE #T TABLE
(
[DATE] DATE
)
INSERT INTO #T SELECT CONVERT (DATE,DATEADD(DAY,#i,'2016-02-07'))
SET #i = #i+1
IF(CONVERT (DATE,DATEADD(DAY,#i,'2016-02-07')) ='2016-02-11' OR CONVERT (DATE,DATEADD(DAY,#i,'2016-02-07')) ='2016-02-12')
SET #i = #i+2
END
SELECT * FROM #T
GO
Declare #i as int
Declare #dr as date
Set #i = 1
while #i < 8
Begin
SELECT #dr = DATEADD(DAY, #i, '2016-02-07')
while (((DATEPART(dw, #dr) + ##DATEFIRST) % 7) NOT IN (5, 6))
begin
print #dr
break;
end
Set #i = #i +1
End
go
If you get yourself a calendar table and you have a very simple query on your hands `
SELECT TOP 1 Date
FROM Calendar
WHERE IsWeekday = 1
AND Date >= #StartDate
AND Date <= #EndDate
ORDER BY NEWID();
You could always generate the dates on the fly though:
SET DATEFIRST 1;
DECLARE #Start DATE = '20160207',
#End DATE = '20160214';
WITH Calendar (Date) AS
( SELECT TOP (DATEDIFF(DAY, #Start, #End) + 1)
DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY N1.N) - 1, #Start)
FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n1 (N)
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n2 (N)
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n3 (N)
)
SELECT TOP 1 Date
FROM Calendar
WHERE DATEPART(WEEKDAY, Date) NOT IN (6, 7)
ORDER BY NEWID();
Here the calendar CTE cross joins 3 table valued constructors to generate a maximum of 1,000 rows (10 x 10 x 10), then limits that to the number of days required using
TOP (DATEDIFF(DAY, #Start, #End) + 1)
Then generates a list of dates onward from the start by using ROW_NUMBER() to generate values from 1 to n. So the basic element is:
DECLARE #Start DATE = '20160207',
#End DATE = '20160214';
WITH Calendar (Date) AS
( SELECT TOP (DATEDIFF(DAY, #Start, #End) + 1)
DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY N1.N) - 1, #Start)
FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n1 (N)
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n2 (N)
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n3 (N)
)
SELECT Date
FROM Calendar
Which gives:
Date
------------
2016-02-07
2016-02-08
2016-02-09
2016-02-10
2016-02-11
2016-02-12
2016-02-13
It is then a simple case of removing weekends with WHERE DATEPART(WEEKDAY, Date) NOT IN (6, 7) and selecting a random row with TOP 1 ... ORDER BY NEWID(). As as aside, when using something setting sensitive like DATEPART(WEEKDAY, ...) you should always explicitly set the value you need rather than relying on defaults.
I may have misunderstood your requirements though, this last step is not necessary if you just want a list of all the dates
How about something like this:
SELECT inp.d AS [Start_Date]
,incr.r AS Increment
,DATEADD(DAY, incr.r, inp.d) AS New_Date
,FORMAT(DATEADD(DAY, incr.r, inp.d), 'ddd') AS New_Date_Weekday
FROM (
-- Input date goes here
SELECT CAST('2016-02-07' AS DATE) AS d
) inp
CROSS JOIN
(
-- Generate 7 rows
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) - 1 AS r
FROM (VALUES(1),(1),(1),(1),(1),(1),(1)) v(n)
) incr
-- Convert week-day to 'Monday = 1 and Sunday = 7'-base;
-- Select a random week-day between 1 and 5
WHERE 1 + ( 5 + DATEPART(DW, DATEADD(DAY, incr.r, inp.d)) + ##DATEFIRST) % 7 = 1 + CAST(RAND() * 5 AS INT)
Thanks to Kakkarot for his answer explaining how to convert week-day count to be based on Monday = 1 and Sunday = 7.
I have below query and i want to get datetime in 30 min intervals between 2 datetime. Basicly I got it, but is limitited and wouln't return al results if the timediff is over 24 hrs.
For example:
#DateTime1 = 24/11/2016 18:00:00
#DateTime2 = 25/11/2016 06:00:00
Result: (in format "dd-HH:mm")
24-18:00
24-18:30
24-19:00
24-19:30
24-20:00
...
...
25-05:00
25-05:30
25-06:00
What I've tried.
SELECT number, DATEADD(MINUTE, number, #DateTime1) AS DateTimeLine, DATEPART(DAY, DATEADD(MINUTE, number, #DateTime1)) AS Days, DATEPART(MONTH,
DATEADD(MINUTE, number, #DateTime1)) AS Months, DATEPART(YEAR, DATEADD(MINUTE, number, #DateTime1)) AS Years, DATEPART(HOUR, DATEADD(MINUTE,
number, #DateTime1)) AS Hours, DATEPART(MINUTE, DATEADD(MINUTE, number, #DateTime1)) AS Minute, CAST(DATEADD(MINUTE, number, #DateTime1)
AS DATE) AS Date, CAST(DATEADD(MINUTE, number, #DateTime1) AS TIME) AS Time
FROM master.dbo.spt_values
WHERE (type = 'P') AND (DATEPART(MINUTE, DATEADD(MINUTE, number, #DateTime1)) = 30 OR DATEPART(MINUTE, DATEADD(MINUTE, number, #DateTime1)) = 0) AND (DATEADD(MINUTE, number, #DateTime1) <= #DateTime2)
ORDER BY number
A tally table is a great way to deal with this type of thing. I keep one in a view to avoid using spt_values.
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
Then your code becomes really simple too. A small amount of datemath and voila.
declare #DateTime1 datetime = '2016/11/24 18:00:00'
, #DateTime2 datetime = '2016/11/25 06:00:00'
select FORMAT(DATEADD(minute, (t.N - 1) * 30, #DateTime1), 'dd-HH:mm')
from cteTally t
where t.N <= (DATEDIFF(hour, #DateTime1, #DateTime2) * 2) + 1
I have a TVF which generates dynamic date/time ranges. It is faster than a recursive cte, and I think more flexible. You pass the date range, desired DatePart, and increment.
Declare #DateTime1 DateTime = '2016-11-24 18:00:00'
Declare #DateTime2 DateTime = '2016-11-25 06:00:00'
Select Format(RetVal,'dd-HH:mm') from [dbo].[udf-Range-Date](#DateTime1,#DateTime2,'MI',30)
Returns
24-18:00
24-18:30
24-19:00
24-19:30
24-20:00
24-20:30
24-21:00
24-21:30
24-22:00
24-22:30
24-23:00
24-23:30
25-00:00
....
25-04:30
25-05:00
25-05:30
25-06:00
The UDF if needed
CREATE FUNCTION [dbo].[udf-Range-Date] (#R1 datetime,#R2 datetime,#Part varchar(10),#Incr int)
Returns Table
Return (
with cte0(M) As (Select 1+Case #Part When 'YY' then DateDiff(YY,#R1,#R2)/#Incr When 'QQ' then DateDiff(QQ,#R1,#R2)/#Incr When 'MM' then DateDiff(MM,#R1,#R2)/#Incr When 'WK' then DateDiff(WK,#R1,#R2)/#Incr When 'DD' then DateDiff(DD,#R1,#R2)/#Incr When 'HH' then DateDiff(HH,#R1,#R2)/#Incr When 'MI' then DateDiff(MI,#R1,#R2)/#Incr When 'SS' then DateDiff(SS,#R1,#R2)/#Incr End),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
cte3(N,D) As (Select 0,#R1 Union All Select N,Case #Part When 'YY' then DateAdd(YY, N*#Incr, #R1) When 'QQ' then DateAdd(QQ, N*#Incr, #R1) When 'MM' then DateAdd(MM, N*#Incr, #R1) When 'WK' then DateAdd(WK, N*#Incr, #R1) When 'DD' then DateAdd(DD, N*#Incr, #R1) When 'HH' then DateAdd(HH, N*#Incr, #R1) When 'MI' then DateAdd(MI, N*#Incr, #R1) When 'SS' then DateAdd(SS, N*#Incr, #R1) End From cte2 )
Select RetSeq = N+1
,RetVal = D
From cte3,cte0
Where D<=#R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1)
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1)
*/
using a recursive Common Table Expression [CTE] is one pretty clean method. For the formatting I am showing FORMAT() from SQL-Server 2012+ you may consider using DATEPART etc to do it though as FORMAT() can have performance impact.
I do agree with #RossBush's comment if you do things like this a lot generating a calendar (dates) and a time dimensions is very helpful for these purposes.
DECLARE #DateTime1 DATETIME = '2016/11/24 18:00:00'
DECLARE #DateTime2 DATETIME = '2016/11/25 06:00:00'
;WITH cte30MinIncrements AS (
SELECT #DateTime1 as DT
UNION ALL
SELECT DATEADD(MINUTE,30,DT)
FROM
cte30MinIncrements
WHERE DATEADD(MINUTE,30,DT) <= #DateTime2
)
SELECT
*
,FORMAT(DT,'dd-HH:mm') as Formated
FROM
cte30MinIncrements
Please see if this works.
declare #DateTime1 DateTime = '2016-11-24 18:00:00'
declare #DateTime2 DateTime = '2016-11-25 18:00:00'
declare #Interval DateTime = #DateTime1
declare #vartmptable table(DT DateTime)
While (#Interval < #DateTime2)
begin
--select #Interval, FORMAT(#Interval,'dd-HH:mm')
insert into #vartmptable select #Interval
set #Interval = DATEADD(mi,30,#Interval)
end
select FORMAT(DT,'dd-HH:mm') from #vartmptable
What about this? You can use variables/ fixed values as necessary.
WITH CTE_Numbers
AS (
SELECT n = 1
UNION ALL
SELECT n + 1
FROM CTE_Numbers
WHERE n < 100
)
SELECT FORMAT(DATEADD(mi, n * 30, '2016/11/03'),'dd-HH:mm')
FROM CTE_Numbers
It appears there is a lot of information out there regarding this topic, but I don't have enough SQL knowledge to apply it to my situation.
This is the query I'm currently working with:
/* Number of successful logins per minute for a given date range */
SELECT
DATEADD(minute, DATEDIFF(minute, 0, AuditMessage.EventDateTime), 0) AS Time,
COUNT(AuditMessage.EventTypeCodeUid) AS CountSuccessfulLoginAttemptsPerMinute
FROM IRWSDB.dbo.AuditMessage
JOIN AuditEventTypeCode
ON AuditEventTypeCode.EventTypeCodeUid = AuditMessage.EventTypeCodeUid
WHERE AuditEventTypeCode.DisplayName = 'Login'
AND AuditMessage.EventDateTime >= CONVERT(DATETIME, '2016-03-03 00:00:00', 120)
AND AuditMessage.EventDateTime <= CONVERT(DATETIME, '2016-03-04 00:00:00', 120)
GROUP BY DATEADD(minute, DATEDIFF(minute, 0, AuditMessage.EventDateTime), 0)
ORDER BY DATEADD(minute, DATEDIFF(minute, 0, AuditMessage.EventDateTime), 0)
Example Output (works as expected):
Time CountSuccessfulLoginAttemptsPerMinute
2016-03-03 17:48:00.000 1
2016-03-03 17:49:00.000 1
2016-03-03 17:50:00.000 1
2016-03-03 17:55:00.000 2
Desired Output:
Time CountSuccessfulLoginAttemptsPerMinute
2016-03-03 17:48:00.000 1
2016-03-03 17:49:00.000 1
2016-03-03 17:50:00.000 1
2016-03-03 17:51:00.000 0
2016-03-03 17:52:00.000 0
2016-03-03 17:53:00.000 0
2016-03-03 17:54:00.000 0
2016-03-03 17:55:00.000 2
I tried to modify the above query to get the desired output, but every path I've gone down has been a dead end. Any help would be greatly appreciated. Thank you.
If your database doesn't have every min of the day, it won't be able to get desired output. Unless you backend/whatever will insert a record to your table every min or refer to this link Fill empty dates in a matrix SSRS. Then you can modify your query like this
SELECT
DATEADD(minute, DATEDIFF(minute, 0, AuditMessage.EventDateTime), 0) AS Time,
ISNULL(COUNT(AuditMessage.EventTypeCodeUid),0) AS CountSuccessfulLoginAttemptsPerMinute
FROM IRWSDB.dbo.AuditMessage
JOIN AuditEventTypeCode
ON AuditEventTypeCode.EventTypeCodeUid = AuditMessage.EventTypeCodeUid
WHERE AuditEventTypeCode.DisplayName = 'Login'
AND AuditMessage.EventDateTime >= CONVERT(DATETIME, '2016-03-03 00:00:00', 120)
AND AuditMessage.EventDateTime <= CONVERT(DATETIME, '2016-03-04 00:00:00', 120)
GROUP BY DATEADD(minute, DATEDIFF(minute, 0, AuditMessage.EventDateTime), 0)
ORDER BY DATEADD(minute, DATEDIFF(minute, 0, AuditMessage.EventDateTime), 0)
First, you need to generate all minutes in the date range. To do this, you need a Tally Table. Then do a LEFT JOIN on your original query.
DECLARE #start DATETIME,
#end DATETIME
SELECT #start = '20160303', #end = '20160304'
;WITH E1(N) AS(
SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b),
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b),
CteTally(N) AS(
SELECT TOP(DATEDIFF(MINUTE, #start, #end) + 1) ROW_NUMBER() OVER(ORDER BY(SELECT NULL))
FROM E4
),
CteMinute(dt) AS(
SELECT dt = DATEADD(MINUTE, N-1, #start) FROM CteTally
)
SELECT
cm.dt AS [Time],
ISNULL(t.cnt, 0) AS CountSuccessfulLoginAttemptsPerMinute
FROM CteMinute cm
LEFT JOIN (
SELECT
DATEADD(MINUTE, DATEDIFF(MINUTE, 0, am.EventDateTime), 0) AS [Time],
COUNT(am.EventTypeUid) AS cnt
FROM IRWSDB.dbo.AuditMessage am
INNER JOIN AuditEventTypeCode atc
ON atc.EventTypeCodeUid = am.EventTypeCodeUid
WHERE
atc.DisplayName = 'Login'
AND am.EventDateTime >= #start
AND am.EventDateTime <= #end
GROUP BY DATEADD(MINUTE, DATEDIFF(MINUTE, 0, am.EventDateTime), 0)
) t
ON cm.dt = t.[Time]
GROUP BY cm.dt
One way is to use LEFT JOIN of "list of every minutes" to your current query
2nd way is to UNION ALL the current query with the "list of every minutes" and then do a SUM on the Count
There are may ways to create the "list of every minutes" like using tally/number table, recursive cte, cross join etc.
Here is a method using recursive cte
;with minutes as
(
select Time = convert(datetime, '2016-03-03')
union all
select Time = dateadd(minute, 1, Time)
from minutes
where Time < '2016-03-05'
)
select Time
from minues
Method 1 : LEFT JOIN
;with minutes as
(
select Time = convert(datetime, '2016-03-03')
union all
select Time = dateadd(minute, 1, Time)
from minutes
where Time < '2016-03-05'
),
data as
(
< your current query here without the order by clause>
)
select m.Time, isnull(CountSuccessfulLoginAttemptsPerMinute, 0) as CountSuccessfulLoginAttemptsPerMinute
from minues m
left join data d on m.Time = d.Time
Method 2 : UNION ALL
;with minutes as
(
select Time = convert(datetime, '2016-03-03')
union all
select Time = dateadd(minute, 1, Time)
from minutes
where Time < '2016-03-05'
)
select Time, sum(CountSuccessfulLoginAttemptsPerMinute) as CountSuccessfulLoginAttemptsPerMinute
FROM
(
< your current query here without the order by clause>
union all
select Time, 0 as CountSuccessfulLoginAttemptsPerMinute
from mintues
) d
group by Time
I'm trying to query a specific range of time:
i.e. 3/1/2014 - 09/31/2014 between 15:30 - 18:30 each day Tues/Wed/Thurs only
I've seen that you can get data for a particular range, but only for start to end and this is quite a bit more specific. DATEPART only allows for one time element and I didn't see any SQL Server commands that would directly help me on this, so does anybody else have any thoughts on how you would form this?
Thanks!
SELECT *
FROM [Order]
WHERE CustomerId = [Customer].Id
AND BusinessDate BETWEEN '2014-03-01' AND '2014-09-31'
AND DATEPART(HOUR, FirstSendTime) >= 15
AND DATEPART(HOUR, FirstSendTime) <= 18
SELECT * FROM [Order]
WHERE CustomerId = [Customer].Id
AND BusinessDate BETWEEN '2014-03-01' AND '2014-09-31'
AND (
DATEPART(HOUR, FirstSendTime) IN (16, 17)
OR (DATEPART(HOUR, FirstSendTime) = 15 AND DATEPART(MINUTE, FirstSendTime) >= 30)
OR (DATEPART(HOUR, FirstSendTime) = 18 AND DATEPART(MINUTE, FirstSendTime) <= 30)
)
AND DATEPART(WEEKDAY, FirstSendTime) BETWEEN 3 AND 5
Several ways of range 15:30 - 18:30:
DATEPART(HOUR, FirstSendTime)*60+DATEPART(MINUTE, FirstSendTime) between 15*60+30 and 18*60+30
LEFT(CONVERT(TIME, FirstSendTime, 114), 5) between '15:30' and '18:30'
CONVERT(CHAR(5), FirstSendTime, 114) between '15:30' and '18:30'
Several ways of range Tues/Wed/Thurs:
DATEPART(WEEKDAY, FirstSendTime) BETWEEN 3 AND 5
DATENAME(WEEKDAY, FirstSendTime) in ('Tuesday','Wednesday','Thursday')
Below is one method, assuming that you have a Customer table that wasn't included in your sample query. This might perform better than applying functions to the datetime column of the Orders table.
WITH
t4 AS (SELECT n FROM (VALUES(0),(0),(0),(0)) t(n))
, t1k AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS num FROM t4 AS a CROSS JOIN t4 AS b CROSS JOIN t4 AS c CROSS JOIN t4 AS d CROSS JOIN t4 AS e)
, time_ranges AS (
SELECT
DATEADD(minute, (15*60)+30, DATEADD(day, num, '2014-03-01')) AS start_time
, DATEADD(minute, (18*60)+30, DATEADD(day, num, '2014-03-01')) AS end_time
FROM t1k
WHERE
num <= DATEDIFF(day, '2014-03-01', '2014-09-30')
AND DATENAME(weekday, DATEADD(day, num, '2014-03-01')) IN ('Tuesday', 'Wednesday', 'Thursday')
)
SELECT *
FROM dbo.[Order]
JOIN dbo.Customer ON Customer.Id = [Order].CustomerId
CROSS JOIN time_ranges
WHERE
[Order].BusinessDate BETWEEN start_time AND end_time;