SQL Server : DATEDIFF exclude certain time within the two dates - sql

I'm trying to get the difference between two dates in minutes, is it possible for me to exclude lunch times from this calculation aka 13:30-14:00 purely within my SQL statement. What I'm currently doing is below:
Example row
SetupStart StartTime SetupTime
----------------------------------------------------------------
2017-01-23 12:56:42.000 2017-01-23 14:41:06.000 105
My current statement:
DATEDIFF(MINUTE, UserData.Orders.SetupStart, UserData.Orders.StartTime) AS[SetupTime]
EDIT:--------------
Here is my current Select statement
SELECT DISTINCT
TOP (100) PERCENT UserData.Resources.Name AS Resource, UserData.Orders.OrderNo, UserData.Orders.StringAttribute4 AS Customer, UserData.Orders.Product,
CEILING(UserData.Orders.OpNo10Quantity) AS OpNo10Quantity, UserData.Orders.NumericalAttribute12 AS Speed, UserData.Orders.SetupStart, UserData.Orders.StartTime,
UserData.Orders.EndTime, CASE ToggleAttribute1 WHEN 1 THEN 'Yes' ELSE 'No' END AS Packed, UserData.Orders.StringAttribute5 AS OrderInstructions, UserData.Orders.NextResource,
UserData.Orders.DatasetId, UserData.Orders_Dataset.name AS ScheduleName, UserData.Orders.ShowOnReport, dbo.tblPreactorExportFullv10.Length AS L,
dbo.tblPreactorExportFullv10.Thickness AS T, dbo.tblPreactorExportFullv10.fWidth AS W,
DATEDIFF(MINUTE, UserData.Orders.SetupStart, UserData.Orders.StartTime) AS [SAM SetupTime]
FROM UserData.Orders INNER JOIN
UserData.Resources ON UserData.Orders.Resource = UserData.Resources.ResourcesId AND UserData.Orders.Resource = UserData.Resources.ResourcesId INNER JOIN
UserData.Orders_Dataset ON UserData.Orders.DatasetId = UserData.Orders_Dataset.DatasetId AND UserData.Orders.DatasetId = UserData.Orders_Dataset.DatasetId INNER JOIN
dbo.tblPreactorExportFullv10 ON UserData.Orders.PartNo = dbo.tblPreactorExportFullv10.ProductCode
WHERE (UserData.Orders.DatasetId = 15) AND (UserData.Resources.Name = 'Moulder 6') AND (UserData.Orders.ShowOnReport = 1)
AND (UserData.Orders.OperationProgress <> 5)
ORDER BY UserData.Orders.SetupStart

I'm sure Gordon's answer will be faster, but another option is as follows:
(Edit: Just for fun, ran this on 20,000 records and returned in 238 milliseconds)
Declare #YourTable table (SetupStart datetime, StartTime datetime)
Insert Into #YourTable values
('2017-01-23 12:56:42.000','2017-01-23 14:41:06.000'),
('2017-01-23 15:00:00.000','2017-01-23 18:30:00.000'), -- No Lunch
('2017-01-23 23:51:00.000','2017-01-23 23:53:46.000'), -- Anomoly mentioned
('2017-01-23 13:15:00.000','2017-01-23 13:45:00.000') -- Started After Lunch
Select A.*
,B.*
From ( ... your complex query here ... ) A
Cross Apply (
Select SetupTime = count(*)
From (Select Top (DateDiff(MINUTE,A.SetupStart,A.StartTime)) T=cast(DateAdd(MINUTE,Row_Number() Over (Order By Number)-1,A.SetupStart) as time)
From master..spt_values ) S
Where (cast(A.SetupStart as time)<'13:30' and cast(A.StartTime as time)>'14:00' and T not between '13:30' and '14:00')
or (cast(A.SetupStart as time) > '13:30' )
or (cast(A.StartTime as time) < '14:00' )
) B
Returns
SetupStart StartTime SetupTime
2017-01-23 12:56:42.000 2017-01-23 14:41:06.000 75
2017-01-23 15:00:00.000 2017-01-23 18:30:00.000 210
2017-01-23 23:51:00.000 2017-01-23 23:53:46.000 2
2017-01-23 13:15:00.000 2017-01-23 13:45:00.000 30

declare
#ldt_from time = '13:00'
,#ldt_to time = '14:00';
-- dummy data prepare
;with [my_setups] as
(
select [SetupStart] = cast('2017-01-23 13:56:42.000' as datetime), [StartTime] = cast('2017-01-23 14:41:06.000' as datetime)
union all
select [SetupStart] = cast('2017-01-23 12:45:00.000' as datetime), [StartTime] = cast('2017-01-23 12:46:00.000' as datetime)
)
-- end dummy data prepare
select [minutes] =
datediff(mi, [SetupStart], [StartTime])
- datediff(day, [SetupStart], [StartTime]) * (datediff(mi, #ldt_from, #ldt_to))
+ case when cast([SetupStart] as time) between #ldt_from and #ldt_to or cast([StartTime] as time) between #ldt_from and #ldt_to then
datediff
(
mi
,case when cast([SetupStart] as time) > #ldt_from then cast([SetupStart] as time) else #ldt_from end
,case when cast([StartTime] as time) < #ldt_to then cast([StartTime] as time) else #ldt_to end
)
else 0 end
from
[my_setups]

This is a pain in SQL Server, but you can do it:
(CASE WHEN CAST(o.SetupStart as time) > '14:00:00' OR
CAST(o.StartTime as time) < '13:30:00'
THEN DATEDIFF(MINUTE, o.SetupStart, o.StartTime)
WHEN CAST(o.SetupStart as time) >= '13:30:00' AND
CAST(o.StartTime as time) <= '14:00:00'
THEN 0
WHEN CAST(o.SetupStart as time) >= '13:30:00'
THEN DATEDIFF(MINUTE,
CAST(CAST(o.SetupStart as DATE) as DATETIME) + CAST('14:00:00' as TIME),
o.StartTime
)
WHEN CAST(o.StartTime as Time) <= '14:00:00'
THEN DATEDIFF(MINUTE,
o.SetStart,
CAST(CAST(o.StartTime as DATE) as DATETIME) + CAST('13:30:00' as TIME)
)
ELSE DATEDIFF(MINUTE, o.SetupStart, o.StartTime) - 30
END) AS [SetupTime]
This handles the following cases:
Times are both before or after lunch.
Times are both during lunch.
SetupStart is during lunch.
StartTime is during lunch.
SetupStart is before lunch and StartTime is after lunch

The easiest way is what #Shakeer Hussain recommended
DATEDIFF(MINUTE, UserData.Orders.SetupStart, UserData.Orders.StartTime) AS[SetupTime]
BECOMES
DATEDIFF(MINUTE, UserData.Orders.SetupStart, DATEADD(MINUTE,-30,(UserData.Orders.StartTime))) AS[SetupTime]
Additionally, you could do two DATEDIFF functions and add them together:
DATEDIFF(MINUTE, UserData.Orders.SetupStart, UserData.Orders.LunchStartTime)
+ DATEDIFF(MINUTE, UserData.Orders.LunchEndTime, UserData.Orders.StartTime) AS[SetupTime]
If these data points don't exist, you can create a temp table.

Related

Time between two dates excluding night and weekends?

How do I calculate the time between two dates, excluding times during evening/night (out of business hours) from 6 pm - 8 am and weekends in MS SQL?
Example:
Column 1: Time1: 2019-11-28 16:30:00
Column 2: Time2: 2019-11-29 09:00:00
Calculated Difference: 1.5 h + 1 h = 2.5 h
Try this:
declare #Column1 as smalldatetime, #Column2 as smalldatetime, #Column as smalldatetime
set #Column1 = '2019-11-28 16:30:00'
set #Column2 = '2019-11-29 09:00:00'
set #Column = #Column2
select
case
when DATEPART(DW, #Column) in (1, 7) then 0
when #Column <= dateadd(hour,18,cast(cast(#Column as date) as smalldatetime)) and #Column > dateadd(hour,9,cast(cast(#Column as date) as smalldatetime))
then datediff(minute, #Column, dateadd(hour,18,cast(cast(#Column as date) as smalldatetime)))
when #Column >= dateadd(hour,8,cast(cast(#Column as date) as smalldatetime)) and #Column < dateadd(hour,18,cast(cast(#Column as date) as smalldatetime))
then datediff(minute, dateadd(hour,8,cast(cast(#Column as date) as smalldatetime)), #Column)
end
Here is an option which uses an ad-hoc tally table
The CROSS APPLY will return the business seconds, then it becomes a small matter to to format. Note: You will have to make allowances for items over 24 hours.
Example
Declare #YourTable table (ID int,DT1 datetime,DT2 datetime)
Insert Into #YourTable values
(1,'2019-11-28 16:30:00','2019-11-29 09:00:00')
Select A.*
,Elapsed=format(dateadd(SECOND,Seconds,0),'HH:mm')
From #YourTable A
Cross Apply (
Select Seconds=sum(1)
From (Select Top (DateDiff(SECOND,DT1,DT2)+1) D=DateAdd(SECOND,-1+Row_Number() Over (Order By (Select Null)),DT1) From master..spt_values n1,master..spt_values n2) A
Where DateName(WEEKDAY,D) not in ('Saturday','Sunday')
and convert(time,D) > '08:00'
and convert(time,D) < '18:00'
) B
Returns
ID DT1 DT2 Elapsed
1 2019-11-28 16:30:00.000 2019-11-29 09:00:00.000 02:30

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

SQL calculate points between a time range

I’ve a table which contains different time ranges:
Id Start Time End Time Points
1 0:00 3:00 10
2 3:01 6:00 20
3 6:01 23:59 30
Now I need to calculate the points achieved between two date ranges with respect to time specified.
Start date = 11/9/2016 18:17:00 and
End date = 11/10/2016 01:20:00
I need to calculate the sum of points gained between these two dates.
The time of start date that is 18:17 falls under Id 3, whose point is 30. So the calculation will be,
18:17 to 23:59 -> 6 hrs -> 6 * 30 = 180 points
The end time 01:20 falls under Id 1
0:00 to 1:20 -> 2 hrs
(if minute is greater than zero, it is rounded to next hour, ie; 2) -> 2 * 10 = 20 points
So the total points gained will be 200 points.
Taking the time difference, does not help me, if the start and end date difference is greater than one day.
Table Structure:
Id - int,
StartTime - time(7),
EndTime - time(7),
Points - int
How to write a query for this using SQL?
This question was good.
You can as the below:
DECLARE #Tbl TABLE (Id INT, StartTime TIME, EndTime TIME, Points INT)
INSERT INTO #Tbl
VALUES
(1, '0:00', '3:00' , 10),
(2, '3:01', '6:00' , 20),
(3, '6:01', '23:59', 30)
DECLARE #StartDate DATETIME = '2016.11.09 18:17:00'
DECLARE #EndDate DATETIME = '2016.11.10 01:20:00'
;WITH CTE
AS
(
SELECT 1 AS RowId, #StartDate CurrentDate, 0 Point, #StartDate DateVal UNION ALL
SELECT
A.RowId ,
IIF((A.CurrentDate + A.EndTime) > #EndDate, #EndDate, DATEADD(MINUTE, 1, (A.CurrentDate + A.EndTime))) AS CurrentDate,
A.Points,
IIF((A.CurrentDate + A.EndTime) > #EndDate, #EndDate, (A.CurrentDate + A.EndTime)) DateVal
FROM
(
SELECT
C.RowId + 1 AS RowId,
CAST(CAST(CurrentDate AS DATE) AS DATETIME) CurrentDate,
CAST((SELECT T.EndTime FROM #Tbl T WHERE CAST(CurrentDate AS TIME) BETWEEN T.StartTime AND T.EndTime) AS DATETIME) AS EndTime,
(SELECT T.Points FROM #Tbl T WHERE CAST(CurrentDate AS TIME) BETWEEN T.StartTime AND T.EndTime) AS Points,
C.CurrentDate AS TempDate
FROM CTE C
) A
WHERE
A.TempDate <> IIF((A.CurrentDate + A.EndTime) > #EndDate, #EndDate, DATEADD(MINUTE, 1, (A.CurrentDate + A.EndTime)))
), CTE2
AS
(
SELECT
C.RowId ,
C.CurrentDate ,
C.Point ,
C.DateVal,
DATEDIFF(MINUTE, LAG(C.DateVal) OVER (ORDER BY C.RowId), C.DateVal) MinuteOfDateDiff
FROM
CTE C
)
SELECT
SUM(CEILING(C.MinuteOfDateDiff * 1.0 / 60.0) * C.Point)
FROM
CTE2 C
Result: 200

Need to calculate sum of overtime

I have a table TEST
AccountName AccountIndex AccountID StartTime EndTime checkouttime
ABC 3 7 07:00:00.00 16:00:00.00 2016-07-22 17:03:00
ABC 3 7 07:00:00.00 16:00:00.00 2016-07-23 16:00:00
ABC 3 7 07:00:00.00 16:00:00.00 2016-07-25 17:04:00
I have to calculate the sum of overtime.
I am trying this
select name,accountid,case when (cast(CheckOutTime as time) < EndTime) then '-' else '' end +
convert(varchar(8),
dateadd(minute,
abs(
DATEDIFF(minute,
cast(CheckOutTime as time)
, EndTime)
)
,0)
,108) as Overtime
from test
And i am getting the o/p as
name accountid Overtime
ABC 7 01:03:00
ABC 7 00:00:00
ABC 7 01:04:00
I want to have the o/p like
name accountid Overtime
ABC 7 02:07:00
sum of overtime how to achieve that
select accountid,name,cast((totalseconds/3600) as varchar) + ':' + cast(((totalseconds%3600)/60) as varchar) as overtime
from
(
select accountid,name,
sum(Datediff(s,Endtime,cast(checkouttime as time))) as totalseconds
group by accountid,name
) t
Use the above query to calculate overtime
Declare #YourTable table (AccountName varchar(50),AccountIndex int,AccountID int,StartTime varchar(25),EndTime varchar(25), checkouttime datetime )
Insert into #YourTable values
('ABC',3,7,'07:00:00.00','16:00:00.00','2016-07-22 17:03:00'),
('ABC',3,7,'07:00:00.00','16:00:00.00','2016-07-23 16:00:00'),
('ABC',3,7,'07:00:00.00','16:00:00.00','2016-07-25 17:04:00')
Select AccountName
,AccountID
,OverTime = cast(DateAdd(MINUTE,sum(DateDiff(MINUTE,cast(EndTime as time),case when cast(EndTime as time)>cast(checkouttime as time) then cast(EndTime as time) else cast(checkouttime as time) end)),'00:00:00') as time)
From #YourTable
Group By AccountName,AccountID
Returns
AccountName AccountID OverTime
ABC 7 02:07:00.0000000
For a more readable one, you could use a CTE.
;with cteBase as (
Select AccountName
,AccountID
,EndTime =cast(EndTime as time)
,checkouttime=cast(checkouttime as time)
From #YourTable
)
Select AccountName
,AccountID
,OverTime = cast(DateAdd(MINUTE,sum(DateDiff(MINUTE,EndTime,case when EndTime>checkouttime then EndTime else checkouttime end)),'00:00:00') as time)
From cteBase
Group By AccountName,AccountID
SUM does not work against time data type.
you can group the data by name, accountID and use
SUM(DATEDIFF(ss, '00:00:00.000', [your overtime value]))
to find the total number of seconds, and finally convert the number of seconds back to time with DATEADD() function.
UPDATE:
select
acc_id,
name,
CONVERT(VARCHAR(20), DATEADD(ss, SUM(DATEDIFF(ss, '00:00:00.000', [your overtime value])), '00:00:00'), 114) as overtime
from [your table]
group by acc_id, name
If you expect the overtime may be more than 24 hours for one acc_id, then you can add another column to show the number of days (number of seconds / (3600 * 24))
It will also fulfill your requirement so please try with this as well: (It only works on 2012)
SELECT AccountName, AccountID,
CONCAT(t.tot/60, ':', t.tot%60,':00') orverTime
FROM (
SELECT AccountName, AccountID,
SUM(DATEDIFF(mi, CONCAT(SUBSTRING(
CAST(checkouttime AS VARCHAR(50)), 1, 11), ' ', EndTime),
checkouttime)) tot
FROM #tmp
GROUP BY AccountName, AccountID ) t