Count how many times a time occurs within a date range - sql

I could not think of a good way to phrase this question to search properly if its already been asked.
I'm looking for a way in SQL 2008 R2 to count how many times 6pm occurs between two datetime values.
For example between '2017-04-17 19:00:00' and '2017-04-19 17:00:00' 6pm only occurs once even though the times span 3 different days.
Between '2017-04-17 18:00:00' and '2017-04-19 18:00:00' it occurs 3 times whilst also spanning 3 days.
Heres a really silly made up expression of what I want for illustration.
timecount(hh, 6, min(datefield), max(datefield))
Thank you

A simple query to count:
DECLARE #StartDate datetime = '2017-04-17 18:00:00'
DECLARE #EndDate datetime = '2017-04-19 18:00:00'
SELECT
CASE
WHEN CAST(#StartDate AS time) <= '18:00' AND CAST(#EndDate AS time) >= '18:00'
THEN datediff(day, #StartDate, #EndDate) + 1
WHEN CAST(#StartDate AS time) <= '18:00' AND CAST(#EndDate AS time) < '18:00'
THEN datediff(day, #StartDate, #EndDate)
WHEN CAST(#StartDate AS time) > '18:00' AND CAST(#EndDate AS time) >= '18:00'
THEN datediff(day, #StartDate, #EndDate)
ELSE datediff(day, #StartDate, #EndDate) - 1
END AS TotalCount

This will give you each hour and the number of occurences:
select datepart(hh, DateColumn) as TheHours, count(*) as occurs
from MyTable
where DateColumn between #SomeDate and #SomeOtherDate
group by datepart(hh, DateColumn)
Or just for 6pm:
select count(*)
from MyTable
where datepart(hh, DateColumn) = 18
and DateColumn between #SomeDate and #SomeOtherDate

DECLARE
#Time time = '18:00',
#From datetime = '2017-04-17 18:00:00',
#To datetime = '2017-04-19 18:00:00'
SELECT
CASE
-- Same date
WHEN DATEDIFF(DAY, #From, #To) = 0 THEN
CASE WHEN CAST(CAST(#From AS date) AS datetime) + #Time BETWEEN #From AND #To THEN 1 ELSE 0 END
-- Not same date
WHEN #From <= #To THEN
CASE WHEN #Time >= CAST(#From AS time) THEN 1 ELSE 0 END
+ DATEDIFF(DAY, #From, #To) - 1
+ CASE WHEN #Time <= CAST(#To AS time) THEN 1 ELSE 0 END
-- Invalid range
ELSE 0
END AS CountOfTime

Try below formula I have tried with different scenario and it works, let me know if I miss any scenario and not work as per your requirement.
DECLARE #firstDate Datetime='17-Apr-2017 17:00:00'
DECLARE #secondDate Datetime='17-Apr-2017 18:59:00'
SELECT
CASE WHEN DATEDIFF(day,#firstDate,#secondDate)=0
THEN IIF(CAST(#firstDate AS TIME) <='18:00' AND DATEPART(hh,#secondDate) >=18,1,0)
ELSE
CASE WHEN
(
CAST(#firstDate AS TIME) <='18:00' AND CAST(#secondDate AS TIME) <'18:00'
OR
CAST(#firstDate AS TIME) >'18:00' AND CAST(#secondDate AS TIME) >='18:00'
)
THEN DATEDIFF(day,#firstDate,#secondDate)
WHEN CAST(#firstDate AS TIME) <='18:00' AND CAST(#secondDate AS TIME) >='18:00' THEN DATEDIFF(day,#firstDate,#secondDate)+1
ELSE DATEDIFF(day,#firstDate,#secondDate)-1
END
END AS TotalCount

Try the below script, using CTE
DECLARE #F_DATE AS DATETIME = '2017-04-17 19:00:00'
,#T_DATE AS DATETIME = '2017-04-19 17:00:00'
;WITH CTE
AS (
SELECT (CASE WHEN DATEPART(HH,#F_DATE) <= 18
THEN #F_DATE
ELSE (CASE WHEN DATEDIFF(DAY,#F_DATE,#T_DATE) > 0
THEN DATEADD(DAY,1,#F_DATE) END)
END) AS CDATE
UNION ALL
SELECT DATEADD(DAY,1,CDATE)
FROM CTE
WHERE DATEADD(DAY,1,CDATE) <= #T_DATE
)
SELECT COUNT(CDATE) DATE_COUNT
FROM CTE
OPTION ( MAXRECURSION 0 )

Here's the count of every 6pm between two datetime:
DECLARE #StartDate datetime
DECLARE #EndDate datetime
set #StartDate = '2017-04-17 19:00:00'
set #EndDate = '2017-04-19 17:00:00'
;WITH cte1 (S) AS (
SELECT 1 FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) n (S)
),
cte2 (S) AS (SELECT 1 FROM cte1 AS cte1 CROSS JOIN cte1 AS cte2),
cte3 (S) AS (SELECT 1 FROM cte1 AS cte1 CROSS JOIN cte2 AS cte2)
select count(datepart(hour,result)) as count from
(SELECT TOP (DATEDIFF(hour, #StartDate, #EndDate) + 1)
result = DATEADD(hour, ROW_NUMBER() OVER(ORDER BY S) - 1, #StartDate)
FROM cte3) as res where datepart(hour,result) = 18
Here's the detailed view of 6pm between two datetime:
DECLARE #StartDate datetime
DECLARE #EndDate datetime
set #StartDate = '2017-04-17 19:00:00'
set #EndDate = '2017-04-19 17:00:00'
;WITH cte1 (S) AS (
SELECT 1 FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) n (S)
),
cte2 (S) AS (SELECT 1 FROM cte1 AS cte1 CROSS JOIN cte1 AS cte2),
cte3 (S) AS (SELECT 1 FROM cte1 AS cte1 CROSS JOIN cte2 AS cte2)
select result,datepart(hour,result) from
(SELECT TOP (DATEDIFF(hour, #StartDate, #EndDate) + 1)
result = DATEADD(hour, ROW_NUMBER() OVER(ORDER BY S) - 1, #StartDate)
FROM cte3) as res where datepart(hour,result) = 18

Here gives the count between any date ranges
declare #time datetime='06:00:00'
declare #startDate datetime='04/20/2017 05:00:00'
declare #enddate datetime='04/21/2017 05:00:00'
SELECT
case
WHEN datediff(ss,#time, convert(time(0),#startDate)) <=0 and datediff(ss,#time, convert(time(0),#enddate)) >=0
THEN datediff(dd,#startDate,#enddate) +1
WHEN (datediff(ss,#time, convert(time(0),#startDate)) <=0 and
datediff(ss,#time, convert(time(0),#enddate)) <=0
OR
datediff(ss,#time, convert(time(0),#startDate))> 0 and
datediff(ss,#time, convert(time(0),#enddate)) >0
OR
datediff(ss,#time, convert(time(0),#startDate))> 0 and datediff(ss,#time, convert(time(0),#enddate)) <=0
)
then datediff(dd,#startDate,#enddate)
ELSE
datediff(dd,#startDate,#enddate)-1
END

Related

Is there an efficient way to break a date range into hours per day?

In SQL Server I am attempting to break a date range into hours per day and have the following bit of code which is OK for a short time frame, but rather inefficient for longer periods of time. Could anyone suggest a more efficient approach?
DECLARE #StartDate datetime = '2015-01-27 07:32:35.000',
#EndDate datetime = '2015-04-29 14:39:35.000',
#TempDate datetime = '';
SET #TempDate = #StartDate;
DECLARE #dateTimeTable TABLE (dt datetime, minCol INT);
WHILE #TempDate < #EndDate
BEGIN
INSERT INTO #dateTimeTable VALUES (CONVERT(date,#TempDate), 1)
SET #TempDate = DATEADD(minute,1,#TempDate)
END
Select dt,
FORMAT(SUM(minCol) / 60.0,'F') as Hours
from #dateTimeTable
GROUP BY dt
Thanks,
Carl
The best way would be to use recursive cte :
DECLARE #StartDate datetime = '2015-01-27 07:32:35.000',
#EndDate datetime = '2015-04-29 14:39:35.000';
WITH cte AS (
SELECT CAST(#StartDate AS DATE) startdate,DATEDIFF(minute, #StartDate, DATEADD(DAY, 1, CAST(#StartDate AS DATE) ) ) / 60.0 hours
UNION ALL
SELECT DATEADD(DAY,1, startdate), DATEDIFF(minute, DATEADD(DAY,1, startdate), CASE WHEN DATEADD(DAY,2, startdate) > #EndDate
THEN #enddate ELSE DATEADD(DAY,2, startdate) END) / 60.0
FROM cte
WHERE startdate <> CAST(#EndDate AS DATE)
)
SELECT * FROM cte
db<>fiddle here

Getting Minutes by Hour for Date Range

I'm trying to write a SQL query (SQL Server) and part of it is determining the number of minutes per hour between two datetimes.
Example: 11/1/2018 09:05 - 11/1/2018 13:15
Hour 09: 55 minutes
Hour 10: 60 minutes
Hour 11: 60 minutes
Hour 12: 60 minutes
Hour 13: 15 minutes
These would then get put into a temp table and grouped by some other data which will then be used to calculate dollar amounts from these minutes.
Is there a way to accomplish something like this via SQL that isn't too slow or laborious?
Thanks!
I think a recursive CTE is possibly the best approach:
with cte as (
select startTime, endTime,
startTime_hour as hourStart,
(case when endTime < dateadd(hour, 1, startTime_hour) then endTime
else dateadd(hour, 1, startTime_hour)
end) as hourEnd
from (select t.*,
dateadd(hour, datediff(hour, 0, startTime), 0) as startTime_hour
from t
) t
union all
select startTime, endTime,
dateadd(hour, 1, hourStart) as hourStart,
(case when endTime < dateadd(hour, 2, hourStart) then endTime
else dateadd(hour, 2, hourStart)
end) as endHour
from cte
where hourEnd < endTime
)
select cte.hourStart,
(case when hourStart > startTime then datediff(minute, hourStart, hourEnd) else datediff(minute, startTime, hourEnd) end) as minutes
from cte
order by hourStart;
Here is a db<>fiddle.
Here is an alternative dynamic solution that you can work with two parameters (start/end dates) only:
create table #temp
([hour] int, [minutes] int)
declare #startTime datetime='11/1/2018 09:05'
declare #EndTime datetime='11/1/2018 13:15'
declare #tempStartTime datetime = #startTime
declare #nextTimeRounded datetime
declare #hourdiff int = DATEDIFF(HOUR,#startTime,#EndTime)
declare #counter int = DATEPART(HH,#startTime)
declare #limit int = #counter + #hourdiff + 1
while #counter < #limit
begin
insert into #temp ([hour]) values (#counter)
set #nextTimeRounded= (dateadd(hour,
1 + datepart(hour, #tempStartTime),
cast(convert(varchar(10),#tempStartTime, 112) as datetime))
)
if #nextTimeRounded > #EndTime
begin
set #nextTimeRounded = #EndTime
end
update #temp
set [minutes] = (case when DATEDIFF(MINUTE,#tempStartTime,#nextTimeRounded)=0 then 60 else DATEDIFF(MINUTE,#tempStartTime,#nextTimeRounded) end)
where [hour] = #counter
set #counter = #counter + 1
set #tempStartTime = DATEADD(MINUTE,DATEDIFF(MINUTE,#tempStartTime,#nextTimeRounded),#tempStartTime);
end
select * from #temp
Sample Data
Below, we pump four time ranges, with associated values, into a table. All time ranges are different, but the first two are 10h 30m apart. The second two are 9h 45m apart.
declare #times table (
startTime time,
endTime time,
val float
);
insert #times values
('2018-10-01 01:00:00', '2018-10-01 10:45:00', 7),
('2018-10-02 01:00:00', '2018-10-02 10:45:00', 8),
('2018-10-01 01:00:00', '2018-10-01 11:30:00', 1),
('2018-10-02 01:00:00', '2018-10-02 11:30:00', 3);
Solution
You can use the 'datediff' function to aggregate as you so desire. Use the modulo operator to convert your minutes into just the minutes that remain over when whole hours are discounted.
select ap.h,
ap.m,
sumVal = sum(val)
from #times
cross apply (select
h = datediff(hour, startTime, endTime),
m = datediff(minute, startTime, endTime) % 60
) ap
group by ap.h,
ap.m

How to break datetime in 15 minute interval in sql sever 2014

I have split the below query in 15 minute interval on the basis of Start datetime but this query is not providing the exact result set
as i am expecting.
Below is the example of query i want to execute.
select Date_Stamp,
Case when substring(convert(char(8),starttime,114), 1, 8) between '12:00:01 AM'and '12:15:00 AM' then '0015'
when substring(convert(char(8),starttime,114), 1, 8) between '12:15:01 AM'and '12:30:00 AM' then '0030'
when substring(convert(char(8),starttime,114), 1, 8) between '12:30:01 AM'and '12:45:00 AM' then '0045'
when substring(convert(char(8),starttime,114), 1, 8) between '12:45:01 AM'and '01:00:00 AM' then '0100'
and i want the result as
Date Need result set
12:01 AM '0015'
'12:15:01 '0030'
'12:30:01 '0045'
'12:45:01 '0100'
'01:00:01 '0115'
'01:15:01 '0130'
'01:30:01 '0145'
'01:45:01 '0200'
'02:00:01 '0215'
'02:15:01 '0230'
'02:30:01 '0245'
3:00:00 ' '0015'
'12:30:00 '0030'
'12:45:00 '0045'
'01:00:00 '0100'
'01:15:00 '0115'
'01:30:00 '0130'
'01:45:00 '0145'
'02:00:00 '0200'
'02:15:00 '0215'
'02:30:00 '0230'
'02:45:00 '0245'
Just change #starttime with your column name
DECLARE #starttime datetime = getdate()
SELECT CONCAT(CASE WHEN DATEPART(HH, #starttime) <= 9
THEN '00'+ CAST(DATEPART(HH, #starttime) AS VARCHAR(2))
ELSE '0'+CAST(DATEPART(HH, #STARTTIME) AS VARCHAR(2))
END,
CASE WHEN DATEPART(MINUTE, #STARTTIME) BETWEEN 1 AND 15
THEN 15
WHEN DATEPART(MINUTE, #STARTTIME) BETWEEN 16 AND 30
THEN 30
WHEN DATEPART(MINUTE, #STARTTIME) BETWEEN 31 AND 45
THEN 45
WHEN DATEPART(MINUTE, #STARTTIME) BETWEEN 46 AND 59 OR DATEPART(MINUTE, #STARTTIME) = 0
THEN 00
END)
You can use this date generator:
DEMO
DECLARE #Break INT = 15
;WITH Numbers (n) as
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1
FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0)) a(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d(n)
)
,Dates as
(
SELECT dt
FROM Numbers
CROSS APPLY
(
VALUES (DATEADD(MINUTE , n, CAST(CAST(GETDATE() AS DATE) AS DATETIME)))
) X(Dt)
WHERE N % #Break = 0
AND CAST(DT AS DATE) = CAST(GETDATE() AS DATE) --Only for today's date
)
SELECT CONVERT(VARCHAR(10),Dt,108) [Time] , REPLACE(CONVERT(VARCHAR(5),ISNULL(Lead(Dt) OVER (ORDER BY Dt) , DATEADD(MINUTE,#Break,Dt)),108), ':','') Grp
FROM Dates
It appears you're using datetime and have only taken the substring of the time. A string cannot be compared to a time, without being casted to the time datatype.
For example:
DECLARE #mytable TABLE (starttime datetime)
INSERT INTO #mytable VALUES ('2018-03-13 00:00:01'), ('2018-03-15 00:00:01')
SELECT * FROM #mytable
select CAST(starttime as time(0)) AS [thetime],
Case when CAST(starttime as time) between '12:00:01 AM'and '12:15:00 AM' then '0015'
when CAST(starttime as time) between '12:15:01 AM'and '12:30:00 AM' then '0030'
when CAST(starttime as time) between '12:30:01 AM'and '12:45:00 AM' then '0045'
when CAST(starttime as time) between '12:45:01 AM'and '01:00:00 AM' then '0100'
END AS [Interval]
FROM #mytable
Produces:
thetime Interval
00:00:01 0015
00:15:01 0030

How to generate a table, where each row represents the last day of each month?

I need to generate a table with the date values that look like this:
01/31/2015
02/28/2015
03/31/2015
04/30/2015
....
12/31/2015
Each value represents the last day of each month. What is the best approach to accomplish this?
#sDate is the starting date, #eDate is the ending date.
declare #sDate datetime,
#eDate datetime
select #sDate = '2013-02-25',
#eDate = '2013-04-25'
;with cte as (
select convert(date,left(convert(varchar,#sdate,112),6) + '01') startDate,
month(#sdate) n
union all
select dateadd(month,n,convert(date,convert(varchar,year(#sdate)) + '0101')) startDate,
(n+1) n
from cte
where n < month(#sdate) + datediff(month,#sdate,#edate)
)
select dateadd(day,-1,dateadd(month,1,startdate)) as enddate
from cte
Results:
2013-02-28
2013-03-31
2013-04-30
SELECT MonthEnd = DATEADD(ms, -3, DATEADD(m, DATEDIFF(m, 0, GETDATE()) + 1 + A.N, 0))
FROM (VALUES -- Feel free to replace this with some sort of numbers table
(0), (1), (2), (3), (4), (5)
) A (N)
You can use EOMONTH to get the end of each month, and a while loop depending on when you want to stop. Of course, you can select these into a temp table or something for later use....
DECLARE #STARTDATE DATETIME
DECLARE #ENDDATE DATETIME
SET #STARTDATE = GETDATE()
SET #ENDDATE = '2015-12-31'
WHILE #STARTDATE < #ENDDATE
BEGIN
SELECT EOMONTH(#STARTDATE)
SET #STARTDATE = DATEADD(M,+1,#STARTDATE)
END

write a query that will run all the days and the name of the day between two set dates [duplicate]

This question already has answers here:
Get a list of dates between two dates
(23 answers)
Closed 8 years ago.
I am trying to write a query that will run all the days and the name of the day between two set dates.
Example:
Date1 = 12/28/2005
Date2 = 12/30/2006
Results:
12/28/2005 Wednesday
12/29/2005 Thursday
12/30/2005 Friday
12/31/2005 Saturday
01/01/2006 Sunday
01/02/2006 Monday
01/03/2006 Tuesday
Any help is appreciated!
You may check this fiddle.
The code:
DECLARE #Date1 DATETIME
DECLARE #Date2 DATETIME
SET #Date1 = '20051228'
SET #Date2 = '20061230'
;WITH cteSequence ( SeqNo) as
(
SELECT 0
UNION ALL
SELECT SeqNo + 1
FROM cteSequence
WHERE SeqNo < DATEDIFF(d,#Date1,#Date2)
)
SELECT CONVERT(VARCHAR,DATEADD(d,SeqNo,#Date1),1) + ' ' + DATENAME(dw,DATEADD(d,SeqNo,#Date1))
FROM cteSequence
OPTION ( MAXRECURSION 0);
GO
You can use that table-valued function:
create function DateTable
(
#FirstDate datetime,
#LastDate datetime,
#handle nvarchar(10)='day',
#handleQuantity int=1
)
returns #datetable table (
[date] datetime
)
AS
begin
with CTE_DatesTable
as
(
select #FirstDate AS [date]
union ALL
select case #handle
when 'month' then dateadd(month, #handleQuantity, [date])
when 'year' then dateadd(year, #handleQuantity, [date])
when 'hour' then dateadd(hour, #handleQuantity, [date])
when 'minute' then dateadd(minute, #handleQuantity, [date])
when 'second' then dateadd(second, #handleQuantity, [date])
else dateadd(day, #handleQuantity, [date])
end
from CTE_DatesTable
where #LastDate >=
case #handle
when 'month' then dateadd(month, #handleQuantity, [date])
when 'year' then dateadd(year, #handleQuantity, [date])
when 'hour' then dateadd(hour, #handleQuantity, [date])
when 'minute' then dateadd(minute, #handleQuantity, [date])
when 'second' then dateadd(second, #handleQuantity, [date])
else DATEADD(day, #handleQuantity, [date])
end
)
insert #datetable ([date])
select [date] from CTE_DatesTable
option (MAXRECURSION 0)
return
end
You can call it like:
select [date],datepart(weekday,[date]) from dbo.DateTable('12/28/2005','12/30/2006',default,default)
You didn't specify your DBMS so I'm assuming Postgres:
select i::date, to_char(i, 'Day')
from generate_series(timestamp '2005-12-28 00:00:00',
timestamp '2006-12-30 00:00:00', interval '1' day) i;
The ANSI SQL solution for this would be:
with recursive date_list (the_date) as (
values ( date '2005-12-28' )
union all
select cast(p.the_date + interval '1' day as date)
from date_list p
where p.the_date <= date '2006-12-30
)
select *
from date_list;