Specific Time Range Query With Hours and Minutes in SQL Server - sql-server

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;

Related

Group and count by 16 hours time interval not working

I am trying to get the number of records for a 16 hour time interval. Below is the code that I am using now.
;With Cte_hours as ( --hours generation
Select top(6) hr = (Row_number() over (order by (Select NULL))-1)*4 from master..spt_values
), cte2 as ( --getting range
Select DateAdd(HH, c.hr, Convert(datetime,d.dts) ) as Dts_Start, DateAdd(MS, -2, DateAdd(HH, c.hr+ 4, Convert(datetime,d.dts) ) ) Dts_end
from (select distinct convert(date, dt) as dts from TEST2 ) d
cross apply Cte_hours c
) --actual query
Select c2.Dts_Start as DT, Sum(case when t.Dt is not null then 1 else 0 end) No_of_records,LD_VOY_N,LD_VSL_M
from cte2 c2
Left Join TEST2 t
on t.Dt between c2.Dts_Start and c2.Dts_end
group by c2.Dts_Start,LD_VOY_N,LD_VSL_M
order by LD_VOY_N, LD_VSL_M, Dts_Start ASC
This code is able to count the number of records I have based on a 4,6, and 12 hour interval. However, if I try to count based on a 16 hour interval, it somehow does not work. Below is my code and output that I used for the 16 hour interval.
;With Cte_hours as ( --hours generation
Select top(6) hr = (Row_number() over (order by (Select NULL))-1)*16 from master..spt_values
), cte2 as ( --getting range
Select DateAdd(HH, c.hr, Convert(datetime,d.dts) ) as Dts_Start, DateAdd(MS, -2, DateAdd(HH, c.hr+ 16, Convert(datetime,d.dts) ) ) Dts_end
from (select distinct convert(date, dt) as dts from TEST2 ) d
cross apply Cte_hours c
) --actual query
Select c2.Dts_Start as DT, Sum(case when t.Dt is not null then 1 else 0 end) No_of_records,LD_VOY_N,LD_VSL_M
from cte2 c2
Left Join TEST2 t
on t.Dt between c2.Dts_Start and c2.Dts_end
group by c2.Dts_Start,LD_VOY_N,LD_VSL_M
order by LD_VOY_N, LD_VSL_M, Dts_Start ASC
Result:
DT No_of_records LD_VOY_N LD_VSL_M
2017-05-05 16:00:00.000 14 0002W pqo emzmnwp
2017-05-06 00:00:00.000 14 0002W pqo emzmnwp
2017-05-06 08:00:00.000 12 0002W pqo emzmnwp
2017-05-06 16:00:00.000 12 0002W pqo emzmnwp
2017-05-01 16:00:00.000 1 0007E omq ynzmeoyn
2017-05-02 00:00:00.000 1 0007E omq ynzmeoyn
It is taking the 8 hour timing as well. Do any of you have any idea why?

SQL Server - Generating schedule list of repeating dates

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;

T-SQL - Minutes per hour between two datetimes

I have to following data:
| tid | startdate | enddate |
| 1 | 2016-12-26 12:30 | 2016-12-26 15:30 |
| 2 | 2016-12-26 13:15 | 2016-12-26 15:15 |
I would like to create a result with the hour number and then the amount of minutes the date time falls within that hour.
Example result:
| tid | hour | minutes_in |
| 1 | 12 | 30 |
| 1 | 13 | 60 |
| 1 | 14 | 60 |
| 1 | 15 | 30 |
| 2 | 13 | 45 |
| 2 | 14 | 60 |
| 2 | 15 | 15 |
Any suggestions?
First You need a numbers table to get your hours from 0 - 23, which can be fairly easily created on the fly with a table value constructor:
SELECT N
FROM (VALUES
(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),
(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23)
) n (N);
Then you can join this to your original data to split rows out into the number required. Then you just need a case expression to apply the correct logic for calculating the minutes:
WITH Numbers (Number) AS
( SELECT N
FROM (VALUES
(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),
(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23)
) n (N)
), SampleData (tid, StartDate, EndDate) AS
( SELECT tid, CONVERT(DATETIME2, StartDate), CONVERT(DATETIME2, EndDate)
FROM (VALUES
(1, '2016-12-26 12:30', '2016-12-26 15:30'),
(2, '2016-12-26 13:15', '2016-12-26 15:15')
) d (tid, StartDate, EndDate)
)
SELECT d.tid,
[Hour] = n.Number,
Minutes_in = CASE
-- SPECIAL CASE: START HOUR = END HOUR
WHEN DATEPART(HOUR, d.StartDate) = DATEPART(HOUR, d.EndDate)
THEN DATEDIFF(MINUTE, d.StartDate, d.EndDate)
-- FULL HOURS IN BETWEEN START AND END
WHEN n.Number > DATEPART(HOUR, d.StartDate)
AND n.Number < DATEPART(HOUR, d.EndDate) THEN 60
-- START HOUR
WHEN n.Number = DATEPART(HOUR, d.StartDate)
THEN 60 - DATEPART(MINUTE, d.StartDate)
-- END HOUR
WHEN n.Number = DATEPART(HOUR, d.EndDate)
THEN DATEPART(MINUTE, d.EndDate)
END
FROM SampleData d
INNER JOIN Numbers n
ON n.Number >= DATEPART(HOUR, d.StartDate)
AND n.Number <= DATEPART(HOUR, d.EndDate);
ADDENDUM
If you need to span days, then you could alter the logic slightly, generate a larger set of numbers to cover more hours difference, then rather than joining on the hour of the day, join the numbers on the hours difference from the start datetime to the end datetime:
SELECT *
FROM SampleData d
INNER JOIN Numbers n
ON n.Number <= DATEDIFF(HOUR, d.StartDate, d.EndDate)
This means where the range crosses over days, then there is no issue, the hours just keep incrementing. e.g.
WITH Numbers (Number) AS
( SELECT ROW_NUMBER() OVER(ORDER BY N1.N) - 1
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)
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N4 (N)
), SampleData (tid, StartDate, EndDate) AS
( SELECT tid, CONVERT(DATETIME2, StartDate), CONVERT(DATETIME2, EndDate)
FROM (VALUES
(1, '2016-12-26 12:30', '2016-12-26 15:30'),
(2, '2016-12-26 13:15', '2016-12-26 15:15'),
(3, '2016-12-26 13:15', '2016-12-27 15:15')
) d (tid, StartDate, EndDate)
)
SELECT d.tid,
[Date] = CONVERT(DATE, d.StartDate),
[Hour] = CONVERT(TIME(0), DATEADD(HOUR, DATEPART(HOUR, d.StartDate) + n.Number, 0)),
Minutes_in = CASE
-- SPECIAL CASE: START HOUR = END HOUR
WHEN DATEPART(HOUR, d.StartDate) = DATEPART(HOUR, d.EndDate)
AND DATEDIFF(DAY, d.StartDate, d.EndDate) = 0
THEN DATEDIFF(MINUTE, d.StartDate, d.EndDate)
-- START HOUR
WHEN n.Number = 0
THEN 60 - DATEPART(MINUTE, d.StartDate)
-- END HOUR
WHEN n.Number = DATEDIFF(HOUR, d.StartDate, d.EndDate)
THEN DATEPART(MINUTE, d.EndDate)
-- FULL HOURS IN BETWEEN START AND END
ELSE 60
END
FROM SampleData d
INNER JOIN Numbers n
ON n.Number <= DATEDIFF(HOUR, d.StartDate, d.EndDate)
ORDER BY d.tid, n.Number;
Method -I
You can achieve this with a UDF (Another Simplest Way)
Lets build schema of your provided data
CREATE TABLE #TAB ( TID INT, STARTDATE DATETIME, ENDDATE DATETIME)
INSERT INTO #TAB
SELECT 1,'2016-12-26 12:30','2016-12-26 15:30'
UNION ALL
SELECT 2,'2016-12-26 13:15','2016-12-26 15:15'
Create one UDF to generate values between from and To
ALTER FUNCTION [dbo].[FN_GENERATE] (#FROM_NBR INT, #TO_NBR INT)
RETURNS
#RESULT TABLE(HR INT)
AS
BEGIN
;WITH CTE AS
(
SELECT #FROM_NBR AS FROM_NBR,#TO_NBR AS TO_NBR
UNION ALL
SELECT FROM_NBR+1 ,TO_NBR FROM CTE WHERE FROM_NBR<TO_NBR
)
INSERT INTO #RESULT
SELECT FROM_NBR FROM CTE
RETURN
END
Now query for data by calling the function.
;WITH CTE AS (
SELECT TID,STARTDATE,ENDDATE,DATEPART(HH,STARTDATE) FROM_HR , DATEPART(HH,ENDDATE) TO_HR FROM #TAB T
)
SELECT C1.TID,F.HR, COALESCE(DATEPART(MINUTE,FRM_HR_MINUTS.STARTDATE),DATEPART(MINUTE,TO_HR_MINUTS.ENDDATE),60 )
FROM CTE C1
CROSS APPLY
(
SELECT * FROM DBO.[FN_GENERATE] (C1.FROM_HR, C1.TO_HR)
)AS F
LEFT JOIN CTE FRM_HR_MINUTS ON C1.TID= FRM_HR_MINUTS.TID AND DATEPART(HH,FRM_HR_MINUTS.STARTDATE)= F.HR
LEFT JOIN CTE TO_HR_MINUTS ON C1.TID= TO_HR_MINUTS.TID AND DATEPART(HH,TO_HR_MINUTS.ENDDATE)= F.HR
Edit :
Method - II
Without using UDF & using MASTER.DBO.SPT_VALUES
;WITH CTE AS (
--PREPARING START HR, END HR, START_MIN, END_MIN FROM #TAB
SELECT TID,STARTDATE,ENDDATE
,DATEPART(HH,STARTDATE) FROM_HR
, DATEPART(HH,ENDDATE) TO_HR
, DATEPART(MINUTE, STARTDATE) AS STARTMIN
, DATEPART(MINUTE, ENDDATE) ENDMIN
FROM #TAB T
)
SELECT TID
, NUMBER AS HRS
--if Outer APply produce Null Display Minutes from CTE else 60 Mins
, CASE ISNULL(OA.FRM_MINS, C1.STARTMIN) + ISNULL(TO_MINS,C1.ENDMIN)
WHEN 0
THEN 60
ELSE ISNULL(OA.FRM_MINS, C1.STARTMIN) + ISNULL(TO_MINS,C1.ENDMIN)
END AS MINS
FROM CTE C1
OUTER APPLY --JOINING NUMBERS BETWEEN FROM_HR & TO_HR using MASTER.DBO.SPT_VALUES
(
SELECT NUMBER
--IF FROM_HR matched NULL Else 0
, CASE C1.FROM_HR WHEN NUMBER THEN NULL ELSE 0 END AS FRM_MINS
--IF TO_HR matched NULL Else 0
,CASE C1.TO_HR WHEN NUMBER THEN NULL ELSE 0 END AS TO_MINS
FROM MASTER.DBO.SPT_VALUES
WHERE [type]='P' AND number>0 AND number BETWEEN FROM_HR AND TO_HR
)AS OA

SQLSERVER 2008: Breaking a output for the entire day into 2 records for 12 hours

I am looking for the count of records as below.
PLANNED_SHIP_From_Date PLANNED_SHIP_To Date Total_Lines_Count ....
1) 09-04-2016 07:00:01 09-04-2016 18:59:59 165 .....
2) 09-04-2016 19:00:00 10-04-2016 07:00:00 121 .....
3) 10-04-2016 07:00:01 10-04-2016 18:59:59 165 .....
4) 10-04-2016 19:00:00 11-04-2016 07:00:00 123 .....
5) 11-04-2016 07:00:01 11-04-2016 18:59:59 234 .....
.
Currently my query is counting the records as per date.
SELECT
cast(shdr.PLANNED_SHIP_DATE as date),
SUM(sdtl_1_1.TOTAL_LINES_COUNT) AS TOTAL_LINES_COUNT
FROM
dbo.SHIPMENT_HEADER AS shdr WITH (NOLOCK)
INNER JOIN
(
SELECT
SHIPMENT_ID,
COUNT(*) AS TOTAL_LINES_COUNT
FROM
dbo.SHIPMENT_DETAIL AS SHIPMENT_DETAIL_1 WITH (NOLOCK)
WHERE
(
STATUS1 >= 401
)
AND (
DATEDIFF(day, PLANNED_SHIP_DATE, CONVERT(date, SYSDATETIME())) < 4
)
GROUP BY
SHIPMENT_ID
) AS sdtl_1_1
ON sdtl_1_1.SHIPMENT_ID = shdr.SHIPMENT_ID
WHERE
(
shdr.TRAILING_STS >= 401
)
AND (
DATEDIFF(day, shdr.PLANNED_SHIP_DATE, CONVERT(date, SYSDATETIME())) < 4
)
GROUP BY
cast(shdr.PLANNED_SHIP_DATE as date)
Try this -
DECLARE #ReportDays int = 30,
#StartHr int = 7,
#Today DATETIME2 = CAST(SYSDATETIME() AS DATE);
--http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/you-require-a-numbers-table.aspx
WITH
a AS (SELECT 1 AS i UNION ALL SELECT 1),
b AS (SELECT 1 AS i FROM a AS x, a AS y),
c AS (SELECT 1 AS i FROM b AS x, b AS y),
d AS (SELECT 1 AS i FROM c AS x, c AS y),
e AS (SELECT 1 AS i FROM d AS x, d AS y),
numbers as (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS number FROM e),
StartDates AS (
SELECT
DATEADD(
HH,
#StartHr + (n2.number * 12),
DATEADD(D, 0-n1.number, #Today)
) AS StartDT
FROM
(SELECT * FROM numbers WHERE Number BETWEEN 0 AND #ReportDays) n1
CROSS JOIN (SELECT * FROM numbers WHERE Number IN (0,1)) n2
),
DateRanges AS
(SELECT StartDT, DATEADD(hh, 12, StartDT) AS EndDT FROM StartDates),
Shipments AS
(SELECT
StartDT AS PLANNED_SHIP_From_Date,
EndDT AS PLANNED_SHIP_To_Date,
1 AS Shipment
FROM
DateRanges dr
LEFT JOIN dbo.SHIPMENT_DETAIL sd
ON sd.Status1 >=401
AND sd.PLANNED_SHIP_DATE BETWEEN dr.StartDT AND dr.EndDT)
SELECT
PLANNED_SHIP_From_Date,
PLANNED_SHIP_To_Date,
SUM(Shipment) AS TOTAL_LINES_COUNT
FROM
Shipments
ORDER BY
PLANNED_SHIP_From_Date;
What we're doing is -
Building a numbers table
Using that to pull a list of days, with two records per day
Working out the start & finish times for each time window
Joining the time windows to the records and summing
Hope that helps :-)
Add another column to your select....
CASE
WHENE DATEPART(HOUR, Planned_SHIP_DATE) < 12 THEN 'AM' ELSE 'PM'
END AS ShipPeriod
You could then add that column into a GROUPING to seperate the 'AM's from 'PM's
Of course I have assuumed you wanted AM/PM. But you can modify the CASE statement to break the hours up as you see fit.
Hope this helps
Thank you all for helping me out.
I have created a SQL query which worked for me. This query gives the count of records from morning 7 AM to 7 PM as MORNING_SHIFT count and 7PM to next day 7AM morning as EVENING_SHIFT for dates greater than 14 days in the past.
SELECT
CASE
WHEN convert(VARCHAR(50), sh.PLANNED_SHIP_DATE, 120) BETWEEN
(convert(VARCHAR(10), sh.PLANNED_SHIP_DATE, 120) + ' 07:00:00') AND
(convert(VARCHAR(10), sh.PLANNED_SHIP_DATE, 120) + ' 18:59:59')
THEN (CONCAT(cast(sh.PLANNED_SHIP_DATE as date),' ','morning_shift'))
WHEN convert(VARCHAR(50), sh.PLANNED_SHIP_DATE, 120) BETWEEN
(convert(VARCHAR(10), sh.PLANNED_SHIP_DATE, 120) + ' 00:00:00') AND
(convert(VARCHAR(10), sh.PLANNED_SHIP_DATE, 120) + ' 06:59:59')
then (CONCAT(cast(DATEADD(DAY, -1, sh.PLANNED_SHIP_DATE) as date),' ','EVENING_shift'))
when
convert(VARCHAR(50), DATEADD(DAY, -1, sh.PLANNED_SHIP_DATE) , 120) BETWEEN (convert(VARCHAR(10), cast(DATEADD(DAY, -1, sh.PLANNED_SHIP_DATE) as date), 120) + ' 19:00:00') AND
(convert(VARCHAR(10), cast(DATEADD(DAY, -1, sh.PLANNED_SHIP_DATE) as date), 120) + ' 23:59:59')
THEN (CONCAT(cast(DATEADD(DAY, -1, sh.PLANNED_SHIP_DATE) as date),' ','EVENING_shift'))
END AS 'actual_date_time', sh.PLANNED_SHIP_DATE
FROM dbo.SHIPMENT_HEADER AS sh WITH (nolock)
WHERE (shdr.TRAILING_STS >= 401) AND (DATEDIFF(day, shdr.ACTUAL_SHIP_DATE_TIME, CONVERT(date, SYSDATETIME())) < 14)
group by sh.ACTUAL_SHIP_DATE_TIME;

How to count number of logins every minute and return zero if the result is null

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

Resources