Problems with MSSQL IF statement - sql-server

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!)

Related

Check if time range is between another time range

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.

Incorrect syntax near case in trigger

I have a Reservations table with the following columns
Reservation_ID
Res_TotalAmount - money
Res_StartDate - datetime
IsDeleted - bit column with default value - false
So when a user tries to delete his reservation I've created a trigger that instead of delete - he just updates the value of column IsDelete to true;
So far so good - but this tourist may owe some compensation to the firm, for example when he has cancelled his reservation 30 days to 20 days from the start_date of the reservation - he owes 30% of the Res_TotalAmount and so on
And here is my trigger
Create Trigger tr_TotalAMountUpdateAfterInsert on RESERVATIONS after Delete
As
Begin
Declare #period int
Declare #oldResAmount int
Declare #newAmount money
Declare #resID int
Select #resID = Reservation_ID from deleted
select #oldResAmount = Res_TotalAmount from deleted
Select #period= datediff (day,Res_StartDate,GETDATE()) from deleted
case
#period is between 30 and 20 then #newAmount=30%*#oldResAmount
#period is between 20 and 10 then #newAmount=50%*#oldResAmount
end
exec sp_NewReservationTotalAmount #newAmount #resID
End
GO
As I have to use both triggers and stored procedure you see that I call at the end of the trigger one stored procedure which just updates Res_TotalAmount column
Create proc sp_NewReservationTotalAmount(#newTotalAmount money, #resID)
As
declare #resID int
Update RESERVATIONS set Res_TotalAmount=#newTotalAmount where Reservation_ID=resID
GO
So my first problem is that it gives me incorrect syntax near case
And my second - I would appreciate suggestions how to make both the trigger and stored procedure better.
Your fundamental flaw is that you seem to expect the trigger to be fired once per row - this is NOT the case in SQL Server. Instead, the trigger fires once per statement, and the pseudo table Deleted might contain multiple rows.
Given that that table might contain multiple rows - which one do you expect will be selected here??
Select #resID = Reservation_ID from deleted
select #oldResAmount = Res_TotalAmount from deleted
Select #period= datediff (day,Res_StartDate,GETDATE()) from deleted
It's undefined - you might get the values from arbitrary rows in Deleted.
You need to rewrite your entire trigger with the knowledge the Deleted WILL contain multiple rows! You need to work with set-based operations - don't expect just a single row in Deleted !
Also: the CASE statement in T-SQL is just intended to return an atomic value - it's not a flow control statement like in other languages, and it cannot be used to execute code. So your CASE statement in your trigger is totally "lost" - it needs to be used in an assignment or something like that ....
1) Here is the correct syntax for the CASE statement. Note that:
I changed the order of your comparisons with the CASE statement; the smaller value has to come first.
I have included an "ELSE" case so you don't wind up with an undefined value when #period is not within your given ranges
SELECT #newAmount =
CASE
WHEN #period between 10 and 20 then 0.5 * #oldResAmount
WHEN #period between 20 and 30 THEN 0.3 * #oldResAmount
ELSE #oldResAmount
END
2) You are going to have an issue with this trigger if ever a delete statement affects more than one row. Your statements like "SELECT #resID = Reservation_ID from deleted;" will simply assign one value from the deleted table at random.
EDIT
Here is an example of a set-based approach to your problem that will still work when multiple rows are "deleted" within the transaction (example code only; not tested):
Create Trigger tr_TotalAMountUpdateAfterInsert on RESERVATIONS after Delete
As
Begin
UPDATE RESERVATIONS
SET Res_TotalAmount =
d.Res_TotalAmount * dbo.ufn_GetCancellationFactor(d.Res_StartDate)
FROM RESERVATIONS r
INNER JOIN deleted d ON r.Reservation_ID = d.Reservation_ID
End
GO
CREATE FUNCTION dbo.ufn_GetCancellationFactor (#scheduledDate DATETIME)
RETURNS FLOAT AS
BEGIN
DECLARE #cancellationFactor FLOAT;
DECLARE #period INT = DATEDIFF (DAY, #scheduledDate, GETDATE());
SELECT #cancellationFactor =
CASE
WHEN #period <= 10 THEN 1.0 -- they owe the full amount (100%)
WHEN #period BETWEEN 11 AND 20 THEN 0.5 -- they owe 50%
WHEN #period BETWEEN 21 AND 30 THEN 0.3 -- they owe 30%
ELSE 0 -- they owe nothing
END
RETURN #cancellationFactor;
END;
GO
About the case:
The syntax is wrong. Even if it worked (see below) you'd be missing the WHENs:
case
WHEN #period is between 30 and 20 then #newAmount=30%*#oldResAmount
WHEN #period is between 20 and 10 then #newAmount=50%*#oldResAmount
end
Yet, the case statement can not be used this way. In the context where you want it, you need to use if. It's not like the switch statement in C++/C# for example. You can only use it in queries like
SELECT
case
WHEN #period is between 30 and 20 then value1
WHEN #period is between 20 and 10 then value2
end
Having said the above: I didn't actually read all your code. But now that I've read some of it, it is really important to understand how triggers work in SQL Server, as mark_s says.

Date Exists between Range of Dates not working

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?

Subtract two time values in SQL Server

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)

Operations with time lapses

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.

Resources