Let me explain the situation
I have two tables:
-One is to store the equipment faults, with their own Start Date and End Date.
-Other to store the breaks, each break have a Start Date and End Date.
For example the fault begin at 9:10 and finish at 9:16. The break begin at 9:14 and finish at 9:18, The fault time is 4 minutes And 2 minutes of the fault don't count because ocurred on a break. I need get the "2 minutes" value to include it in a report
To calculate it, I use a Scalar-valued Function:
DECLARE #Time int;
SET #Time = 0;
IF (#BreakStartDate > #FaultStartDate) AND (#BreakEndDate < #FaultEndDate)
SET #Time = DATEDIFF(SECOND, #BreakStartDate, #BreakEndDate);
ELSE IF (#FaultStartDate > #BreakStartDate) AND (#FaultEndDate < #BreakEndDate)
SET #Time = DATEDIFF(SECOND, #FaultStartDate, #FaultEndDate);
ELSE IF (#FaultStartDate < #BreakStartDate) AND ((#FaultEndDate > #BreakStartDate) AND (#FaultEndDate < #BreakEndDate))
SET #Time = DATEDIFF(SECOND, #BreakStartDate, #FaultEndDate);
ELSE IF (#FaultEndDate > #BreakEndDate) AND ((#FaultStartDate > #BreakStartDate) AND (#FaultStartDate < #BreakEndDate))
SET #Time = DATEDIFF(SECOND, #FaultStartDate, #BreakEndDate);
RETURN #Time
I need to validate all scenarios, if the fault start first and finish on the break, etc...
My question is, exist a function that do this automatically?
or a more elegant solution?
I was doing something similar with a time entry tool and needed to detect overlap between entered times. If all you really need is "I have a fault. There will be 1 Break during the Fault period. Give me the amount of time during which they're co-incident", then your function is on the right track.
When I did mine, I found exploring test cases visually was better than a truth table, and both were better than prose or code.
Case 1: Case 2:
Fault: |-------| Fault: |----------|
Break: |-------| Break: |----------|
Case 3: Case 4:
Fault: |----------| Fault: |----------|
Break: |----------| Break: |------|
...etc.
As for doing this elegantly, I'd point out that if there is any overlap between the two lapses, you always wind up wanting the larger start date. Like so:
Start Dates:
Fault: |---- ... |---- ... |----
Break: |---- ... |---- ... |----
Verdict: break either fault
...and similar for the smallest end date. Because our zone of interest is when both have started and neither have finished. So your logic can become:
DECLARE #Time int, #biggestStart datetime, #smallestEnd datetime;
SET #Time = 0;
IF (#BreakStartDate < #FaultEndDate) AND (#BreakEndDate > #FaultStartDate)
BEGIN
-- We have overlap.
-- Poor man's Math.Max and Math.Min:
SET #biggestStart = CASE WHEN #FaultStartDate > #BreakStartDate THEN #FaultStartDate ELSE #BreakEndDate END;
SET #smallestEnd = CASE WHEN #FaultEndDate < #BreakEndDate THEN #FaultEndDate ELSE #BreakEndDate END;
SET #Time = DATEDIFF(SECOND, #biggestStart, #smallestEnd);
END
RETURN #Time
If I had to do what you stated, this is probably what I'd go with.
That said, in analyzing your question, I'm wondering if you don't have to worry about multiple breaks per fault, if you are trying to do this for all faults in a result set, if you're aggregating "time per equipment per week" or something. If so, you want another approach. But that's another answer.
AFAIK there is no built in function for this. However, if you convert all dates to seconds since some random start date, I think you can calculate the time of the fault as (in pseudo code):
max(0; b1 - f1) - max(0; b2 - f2)
where b1, f1, b2 and f2 are given in the code below where I have put the SQL to calculate the above expression (untested):
DECLARE #startDate DATETIME
SET #startDate = '2010-01-01' -- Some date that must be smaller than all fault dates and break dates
DECLARE #breakStartDate DATETIME
DECLARE #breakEndDate DATETIME
DECLARE #faultStartDate DATETIME
DECLARE #faultEndDate DATETIME
DECLARE #b1 INT
DECLARE #b2 INT
DECLARE #f1 INT
DECLARE #f2 INT
-- Set values for break and fault dates here
SET #breakStartDate = ....
SET #breakEndDate = ....
SET #faultStartDate = ....
SET #faultEndDate = ....
SET #b1 = DATEDIFF(SECOND, #startDate, #breakStartDate)
SET #b2 = DATEDIFF(SECOND, #startDate, #breakEndDate)
SET #f1 = DATEDIFF(SECOND, #startDate, #faultStartDate)
SET #f2 = DATEDIFF(SECOND, #startDate, #faultEndDate)
DECLARE #time INT
SET #time =
CASE
WHEN #b1 - #f1 < 0 THEN 0
ELSE #b1 - #f1
END
+
CASE
WHEN #b2 - #f2 < 0 THEN 0
ELSE #b2 - #f2
END
SELECT #time
I think it is more elegant and less error prone but it's a matter of taste. It may be harder to read.
Related
Im stuck in solving something. How can I check if a time range is between another time range?
For example how can I check if 11:00AM - 12:00PM is between 9:30AM - 2PM?
I need this in developing timekeeping system to check if the filed Sick Leave of an employee covers his scheduled break time.
Im using SQL Server 2008
declare #timefrom datetime
declare #timeto datetime
declare #timefromBreak datetime
declare #timetoBreak datetime
set #timefrom = cast('01/21/2015 11:30:00' as datetime)
set #timeto = cast('01/21/2015 14:00:00' as datetime)
set #timefromBreak = cast('01/21/2015 11:00:00' as datetime)
set #timetoBreak = cast('01/21/2015 12:00:00' as datetime)
select
case when ((#timefromBreak between #timefrom and #timeto) AND (#timetoBreak between #timefrom and #timeto)) then
(datediff(hour, #timefrom,#timeto) - datediff(hour,#timefromBreak,#timetoBreak))
else datediff(hour, #timefrom,#timeto)
end as TotalLeaveHours
here is my example.. im trying to get the total leave hours an employee filed for his Leave.
The logic is that i will get the total leave hours not including the break time (if and only if the leave time range covers the break time.)
So if an employee filed a leave for 11:30PM to 2:00PM and his scheduled break time is 11:00Am to 12:00PM. The total leave hours should only be 2Hours.
11:30AM to 12:00PM should not be counted for the total leave hours since his scheduled time is 11:00AM to 12:00PM..
This computes the time difference (in minutes at the moment1) and tries to take care of all possible overlaps:
declare #timefrom datetime
declare #timeto datetime
declare #timefromBreak datetime
declare #timetoBreak datetime
select #timefrom = '2015-01-21T11:30:00',#timeto = '2015-01-21T14:00:00',
#timefromBreak = '2015-01-21T11:00:00',#timetoBreak = '2015-01-21T12:00:00'
select DATEDIFF(minute,#timefrom,#timeto) -
CASE WHEN #timeFrom < #timeToBreak AND #timefromBreak < #timeTo THEN
DATEDIFF(minute,
CASE WHEN #timeFrom > #timeFromBreak THEN #timeFrom ELSE #timeFromBreak END,
CASE WHEN #timeTo < #timeToBreak THEN #timeTo ELSE #timeToBreak END
) ELSE 0 END
Hopefully, you can see the basic logic - we always compute the time difference - and then we check whether an overlap exists. Only if an overlap exists do we need to adjust the total time, and we do that using a couple of CASE expressions to account for when the break time and the leave time partially overlap or entirely overlap.
(If SQL Server had MIN and MAX functions that worked against multiple arguments instead of multiple rows, then the later CASE expressions would have just been MAX(#timeFrom,#timeFromBreak) and MIN(#timeTo,#timeToBreak))
1Since in some of your examples you seem to be working with e.g. 2.5 hours, I though it would be safer to work in minutes and let you perform any final adjustment/rounding down/up as a final step, separate from these calculations
4 is the total leave hours..
Sorry for making confusions..I need to get the total number of hours..I tried it and i think there will be no conflict if the leave time range will cover the entire break time..how ever if only part of the break time will be covered, the first condition will be false..
if the employee's break schedule is 11:00AM to 12:00PM, then the leave he filed is from 11:30AM to 2:00Pm the Total Leave Hours should return only 3 hours, instead of 2.5hours.
in my testing,
declare #timefrom datetime
declare #timeto datetime
declare #timefromBreak datetime
declare #timetoBreak datetime
set #timefrom = cast('01/21/2015 11:30:00' as datetime)
set #timeto = cast('01/21/2015 14:00:00' as datetime)
set #timefromBreak = cast('01/21/2015 11:00:00' as datetime)
set #timetoBreak = cast('01/21/2015 12:00:00' as datetime)
select
case when ((#timefromBreak between #timefrom and #timeto) AND (#timetoBreak between #timefrom and #timeto)) then
(datediff(hour, #timefrom,#timeto) - datediff(hour,#timefromBreak,#timetoBreak))
else datediff(hour, #timefrom,#timeto)
end as TotalLeaveHours
this returns 3 hours because it doesn't meet the first condition in Select Case statement.
I'm having trouble converting a query from MySQL to MSSQL. Most of my errors are coming from an if statement that's supposed to see how many manhours we have left on a given day. It checks to see if the person hasn't worked yet or is currently working and adds either the amount of time they're scheduled for or the amount of time they have left to work.
Round(Sum(IF(mon_endtime > Curtime(),IF(mon_starttime > Curtime(),mon_duration, (mon_endtime - Curtime()) / 10000), 0)),1) AS hours
Where mon_ information is stored in the employee table. I'm aware that MSSQL doesn't have curtime() and I have a variable in place to hold it, but I'm having problems getting the correct amount with the following code:
declare #cur_time time;
declare #starttime time;
declare #endtime time;
set #cur_time = CONVERT(time, CURRENT_TIMESTAMP);
select #starttime = tue_starttime from employee;
select #endtime = tue_endtime from employee;
if (#endtime > #cur_time)
begin
if (#starttime > #cur_time)
begin
select sum(tue_duration) as hours from employee
end
else begin
select sum(datediff(hh,#cur_time,tue_endtime)) as hours from employee
end
end
else begin
select 0 as hours from employee
end
Any help would be greatly appreciated
Try using this CASE statement instead:
ROUND(
SUM(
CASE WHEN (mon_endtime > Curtime())
THEN
CASE WHEN (mon_starttime > Curtime())
THEN mon_duration
ELSE ((mon_endtime - Curtime()) / 10000)
END
ELSE 0
END)
,1) AS Hours
(formatted so I could understand the statement a little better!)
ALTER PROCEDURE [dbo].[spInsert]
(#PlanName Varchar(50)=null
,#StartDate Datetime
,#EndDate Datetime
,#ModifiedBy Varchar(100)=null
,#ReturnValue Int Out)
As
BEGIN
IF NOT EXISTS(SELECT PlanName FROM dbo.tblPlan WHERE PlanName=#PlanName)
BEGIN
IF((SELECT COUNT(*) FROM tblPlan WHERE StartDate <= #StartDate AND EndDate <=
#EndDate)<0)
BEGIN
INSERT INTO dbo.tblPlan VALUES(3,#PlanName,#StartDate,#EndDate,#ModifiedBy,GETDATE(),
(SELECT DATEDIFF(DD,#StartDate,#EndDate)))
SET #ReturnValue=1;
END
ELSE
SET #ReturnValue=-2;
END
ELSE
SET #ReturnValue=-1;
END
I am trying to achieve the below thing.I want to check user supplied startDate and Enddate is in between the existing table startdate and enddate. if any of the date of user supplied date range is in between the tables start date and end date,it should return -2,if the record does not exists it should insert the details..
I Could not achieve this logic.where i went wrong ..please suggest me any solution to this.
EDIT:First Consditon check whether planName is exists or not,If not exists then want to check Start and End Date already existed or Not(Including start and End)
I tried two ways as suggested mentioned in the replies.
eg:If existing start and end range is Start-2013-10-09,End-2013-10-15,If am going to insert another plan then the start and end date of this plan should not fall between 9th-15th october and start and End date should not be 9th or 15 th both.
ONE:IF((SELECT COUNT(*) FROM tblPlan WHERE StartDate <= #StartDate AND EndDate <=
#EndDate)=0)
Result: It does not insert any data, even it is out of previous date. or with in the
range
SECOND:IF((SELECT COUNT(*) FROM tblPlan WHERE StartDate>=#StartDate AND
EndDate<=#EndDate)=0)
RESULT: It insert the date with out Considering the above condition.
I think you need to change your if from
IF((SELECT COUNT(*) FROM tblPlan WHERE StartDate <= #StartDate AND EndDate <= #EndDate)<0)
to
IF((SELECT COUNT(*) FROM tblPlan WHERE StartDate <= #StartDate AND EndDate >= #EndDate)=0)
Which should ensure that #StartDate and #EndDate is between StartDate and EndDate and test for =0
COUNT(*) can never be smaller than zero like your code suggests.
It's either a positive integer (which is greater than zero), or null, which will also return false on any arithmetic condition.
try the below :
ALTER PROCEDURE [dbo].[spInsert]
(#PlanName Varchar(50)=null
,#StartDate Datetime
,#EndDate Datetime
,#ModifiedBy Varchar(100)=null
,#ReturnValue Int Out)
As
BEGIN
IF NOT EXISTS(SELECT PlanName FROM dbo.tblPlan WHERE PlanName=#PlanName)
BEGIN
IF((SELECT COUNT(*) FROM tblPlan WHERE StartDate >= #StartDate AND EndDate <=
#EndDate)=0)
BEGIN INSERT INTO dbo.tblPlan
VALUES(3,#PlanName,#StartDate,#EndDate,#ModifiedBy,GETDATE(),
(SELECT DATEDIFF(DD,#StartDate,#EndDate)))
SET #ReturnValue=1;
END
ELSE
SET #ReturnValue=-2;
END
ELSE
SET #ReturnValue=-1;
END
As count always return positive value or zero. so your first condition always being false.
*UPDATE:*
you want to say if Sdate is '12-12-2013' and edate is '15-12-2013' then you don't want to considers these dates in check if so then try replace the query with below:
IF((SELECT COUNT(*) FROM tblPlan WHERE StartDate > #StartDate AND EndDate <
#EndDate)=0)
If you're wanting to check whether the period defined by #StartDate,#EndDate overlaps with any period defined by the values in the StartDate,EndDate columns for a particular row, then the actual comparison that you want to perform is:
StartDate < #EndDate AND EndDate < #StartDate
(With appropriate adjustments of < to <= depending on whether you want to consider two periods such that one starts at the exact time that the other finishes as overlapping or not)
The logic is - Two periods overlap if both of the following conditions are true:
period 1 starts before period 2 ends
period 2 starts before period 1 ends
Other notes -
A COUNT will never be <0 so that part of the logic is incorrect. If you merely want to determine whether any rows exists, use EXISTS (as you already have once) - don't force a COUNT if you don't actually need to know how many rows match your criteria.
I also find it slightly suspect that your first query considers PlanName but your second doesn't. Are you sure that this is correct?
How to subtract two time values in SQL Server 2008. I am using time variables in a stored procedure.
Please help.
You can use DATEDIFF():
SELECT DATEDIFF(Day, startDate, endDate)
FROM table
SELECT DATEDIFF(Second, date, GETDATE())
FROM table
DECLARE #END TIME = '16:00:00.0000000' ,
#START TIME = '01:00:00.0000000'
SELECT convert(TIME,dateadd(ms,DateDiff(ss, #START, #END )*1000,0),114)
following expression works for me
declare #starttime Time, #endtime Time
set #starttime='18:45'
set #endtime='22:45'
select DATEDIFF(HH,#starttime, #endtime)
output: 4
Even You are using offset value or normal dates this code will give you appropriate answers.
BEGIN TRY
DECLARE #OffSetVal1 VARCHAR(39) = '2019-12-02 09:15:29 +14:00'
, #OffSetVal2 VARCHAR(39) = '2019-12-02 09:15:29 +12:30'
, #minutes INT = 0
, #OffSetDiff VARCHAR(19) = ''
, #HourDiff INT = 0
SET #HourDiff = DATEDIFF(HOUR,#OffSetVal1,#OffSetVal2) -- To Check digits of hours.
SET #minutes = DATEDIFF(MINUTE,#OffSetVal1,#OffSetVal2) -- To Convert minutes to hours.
SET #OffSetDiff = #minutes / 60 + (#minutes % 60) / 100.0 -- To Check '+' Or '-' And To Get Value.
SELECT CASE WHEN CAST(#OffSetDiff AS FLOAT) <= 0
THEN (CASE WHEN #HourDiff < 10
THEN FORMAT(CAST(#OffSetDiff AS FLOAT),'0#.00')
ELSE FORMAT(CAST(#OffSetDiff AS FLOAT),'0#.00')
END)
ELSE (CASE WHEN #HourDiff < 10
THEN '+'+FORMAT(CAST(#OffSetDiff AS FLOAT),'0#.00')
ELSE '+'+FORMAT(CAST(#OffSetDiff AS FLOAT),'0#.00')
END)
END
END TRY
BEGIN CATCH
PRINT N'It seems you provided an invalid DateTimeOffSet Parameter. '
END CATCH
ANS :- +01.30 (# The offset differences with hour and minute and if you don't want that '+' simply remove from code & then run the code)
I have a database that displays time as an integer. However I am wanting to output this into a report with the correct format. This is the format that I would like to change:
eg.
183000 would become 18:30
500 would become 00:05
160000 would become 16:00
and so on.
I have had a look and CAST and CONVERT but not successfully managed to get this the time in the correct format.
Assuming your input will always be an int, you can parse it with something like:
DECLARE #stringTime varchar(6)
SET #stringTime = RIGHT('000000' + CAST(intTime AS VARCHAR), 6)
SELECT CAST(LEFT(#stringTime, 2) + ':' + RIGHT(LEFT(#stringTime, 4), 2) AS TIME) as TimeValue
I'd DEFINITELY look to change this field to an actual time or datetime field, as this level of conversion is not advised, especially for a heavily used database. There's just really got to be a better way to store your data.
Using an int value this way allows for a lot of bad data, without adding a lot of additional checks and/or constraints on your inputs (i.e.: 260000, 127900, etc.)
looks like you need to divide by 100 to get the seconds, divide by 10000 to get the minutes, and divide by 1000000 to get the hours, then format those values as a string, inserting a colon between hours and minutes, like
hh:mm
First cast to a varchar field and convert all times to 4 digits adding leading zeros if need be (500 would become 0500)and then break up the field with concantenation Left(myfield,2) + ':' + right(myfield,2)
This is something stupid to do every time you run a report, it is wasteful of server resources. If possible change the field to varchar and runthe code once. If not possible, can you add a formatted field and have a trigger do the formatiing on insertion (you'll still need to update the field the first time? Possibly a constraint would do instead of a trigger, but that would depend on the database.
I'm assuming that you are on SQL Server based on use of CONVERT and your previous questions.
You could use DATEADD for this too.
WITH Times AS
(
SELECT 183000 AS T
UNION ALL
SELECT 500
UNION ALL
SELECT 160000
)
SELECT CAST(DATEADD(SECOND, T%100 + (60*(T%10000 / 100)) + 3600*(T/10000),0)
AS time /*Or datetime if < SQL Server 2008*/)
FROM Times
declare #i int = 235959
select cast(stuff(stuff(
case when len(cast(#i as varchar(6))) = 6 then cast(#i as varchar(6))
else REPLICATE('0',6 - LEN(cast(#i as varchar(6))))+cast(#i as varchar(6))
end
,3,0,':'), 6, 0,':') as datetime)
set #i = 500
select cast(stuff(stuff(
case when len(cast(#i as varchar(6))) = 6 then cast(#i as varchar(6))
else REPLICATE('0',6 - LEN(cast(#i as varchar(6))))+cast(#i as varchar(6))
end
,3,0,':'), 6, 0,':') as datetime)
DECLARE #intTime int
SET #intTime = 50000
SELECT CAST(STUFF(STUFF(CONCAT(REPLICATE('0', 6 - LEN(#intTime)),#intTime),3,0,':'),6,0,':') AS TIME(0))
ALTER FUNCTION [dbo].[MinutesToDuration]
(
#minutes int
)
RETURNS nvarchar(30)
AS
BEGIN
declare #hours nvarchar(20)
DECLARE #XX NVARCHAR(10)
DECLARE #HH VARCHAR(2)
DECLARE #TT VARCHAR(2)
DECLARE #BL NVARCHAR(2)
DECLARE #TM VARCHAR(5)
SET #XX=#minutes
IF #XX<60
BEGIN
IF #XX<10
BEGIN
SET #HH='00'
SET #TT='0'+#XX
END
ELSE
BEGIN
SET #HH='00'
SET #TT=#XX
END
END
ELSE
BEGIN
IF #XX%60=0
BEGIN
SET #HH=#XX/60
SET #TT='00'
END
ELSE
BEGIN
SET #BL= #XX%60
IF #BL<10
BEGIN
SET #HH=#XX/60
SET #TT='0'+#BL
END
ELSE
BEGIN
SET #HH=#XX/60
SET #TT=#BL
END
END
END
SET #hours= #HH+':'+#TT
return #hours
END