Datediff, how to not include start date that's in the future - sql

My current query is
case when datediff(day, isnull(installment_duedate, due_date), getdate()) < 30
then amount_due else 0 end as [unpaid under 30 days]
I have another 3 case that does between 30-60 days, between 60-90s and greater then 90 days.
My current problem is, my installment_duedate has future dates in it. If someone has an installment due date it would be something like 12-02-2012, 03-02-2013, 06-02-2013, 09-02-2013. If it's in the future I would like it to return as 0 for now but currently the amount is showing up in my under 30 days.
Any pointers would be greatly appreciated!

You need to change your case to:
CASE WHEN DATEDIFF(DAY, ISNULL(installment_duedate, due_date), GETDATE()) BETWEEN 0 AND 30
THEN amount_due ELSE 0 END AS [unpaid under 30 days]

In this line , when installment_duedate is past date , DATEDIFF returns negative value which is less than 30 and hence < 30 condition gets satisfied
datediff(day, isnull(installment_duedate, due_date), getdate()) < 30
Format
DATEDIFF(datepart,startdate,enddate)
for solution to it , see Lamak's answer below

Related

Finding number of days between two dates SQL Server 2012

Sorry for the misunderstanding.. let me try to clear this up
I'll keep this short and thanks for the help!
I have an age case I want to return the number of days between current date and the import date here is my original query I am wanting to change.
Thanks again
age = case
when datediff(day, ( t.importdate), getdate()) > 365 then J - 366+
when datediff(day, ( t.importdate), getdate()) > 270 then I - 271 to 365
when datediff(day, ( t.importdate), getdate()) > 240 then H - 241 to 270
This query will group the datediff and it goes all the way down to A before it ends, this is meant just show the person the categories the datediff would fall into.
What I want to accomplish is getting rid of the grouping function but still run a case that will return the datediff to just numbers...
ex.. instead of > 365 then J - 366+ It would just return say 18, which would be the days in between that specific instance..I am running hundreds of user accounts all with different import dates I want to compare with current date..
I hope this is clear!
I suspect that you are looking to return ages in groups. If so, this may be what you are looking for:
select age = (case when datediff(day, t.importdate, getdate()) > 365
then 'J - 366+'
when datediff(day, t.importdate, getdate()) > 270
then 'I - 271 to 365'
when datediff(day, t.importdate, getdate()) > 240
then 'H - 241 to 270'
else 'G - <270
end)

SQL Count sickness days

I have a SQL Server table which contains the list of all staff and their sickness.
I need to be able to calculate how many days they have had sick in the current quarter
The issue is, some people may have been sick for a year so, E.G the FROMDATE could be 2013-12-31 and the UNTILDATE could be 2014-12-31 (1 year sickness leave). However it should only count the days from that sickness that occur in the current quarter. So it should be around 90 days of sickness rather than count the entire year.
Current SQL
select SUM(a.WORKDAYS) as Total
from ABSENCE a
where a.FROMDATE < GETDATE() and
a.UNTILDATE > DATEADD(MONTH, -3, GETDATE())
and
a.ABS_REASON='SICK'
So at the moment, it takes from any fromdate which is correct as I need to account for people who were already sick before the quarter started but still sick going into the current quarter but should only count the number of days from when the quarter started until the end of the quarter.
Any help would be greatly appreciated.
With a table of dates, you could easily find the count of dates where the date is between your two dates of interest, and where there exists a leave period that surrounds it. You could also filter your dates to exclude non-business days and public holidays.
There are lots of ways to generate such a table of dates, and plenty described both on stackoverflow and dba.stackexchange.
Not sure about your columns.you should only provide sql that gives records between 2013-12-31 and 2014-12-31 and then ask your problem .
Try this,
select SUM(Case when datepart(MM, a.FROMDATE) IN (10,11,12) Then a.WORKDAYS Else End)
as Total
from ABSENCE a
where a.FROMDATE >= '2013-12-31' and
a.UNTILDATE <= '2014-12-31'
and
a.ABS_REASON='SICK'
SELECT SUM(a.WORKDAYS) as Total
FROM ABSENCE a
WHERE (a.FROMDATE >= DATEADD(MONTH, -3, GETDATE()) OR a.UNTILDATE >= DATEADD(MONTH, -3, GETDATE()))
AND a.ABS_REASON = 'SICK'
Quarter Specific
SELECT SUM(a.WORKDAYS) as Total
FROM ABSENCE a
WHERE (a.FROMDATE >= DATEADD(quarter, -1, GETDATE()) OR a.UNTILDATE >= DATEADD(quarter, -1, GETDATE()))
AND a.ABS_REASON = 'SICK'
Having a Calendar table with the list of all possible dates is handy, but in this case we can do without it.
I'll generalize your question a bit. Instead of looking just at the current quarter let's have two parameters that define the range of dates that you are interested in:
DECLARE #ParamStartDate date;
DECLARE #ParamEndDate date;
At first we need to get all rows from Absence that have a range from FromDate to UntilDate that intersects with the given period.
SELECT
...
FROM
Absence
WHERE
ABS_REASON='SICK'
-- all absence periods, which overlap with the given period
AND FromDate <= #ParamEndDate
AND UntilDate >= #ParamStartDate
Two periods A and B overlap when (StartA <= EndB) and (EndA >= StartB).
Then we need to calculate how many days are in the intersection of the two periods.
The intersection period can't be larger than the given range of dates (#ParamStartDate to #ParamEndDate).
The intersection period can't be larger than the duration of the sickness (FromDate to UntilDate).
So, the beginning of the intersection is the latest of FromDate and #ParamStartDate, i.e. MAX(FromDate, #ParamStartDate)
The ending of the intersection is the earliest of UntilDate and #ParamEndDate, i.e. MIN(UntilDate, #ParamEndDate)
Finally, the duration of the intersection in days is
DATEDIFF(day, MAX(FromDate, #ParamStartDate), MIN(UntilDate, #ParamEndDate))
But, only if it is positive. If it is negative, it means that sickness period ended before the given quarter started (or sickness started after the given quarter ended).
There is no built-in MIN, MAX functions that take two parameters as I need, so I use CROSS APPLY to calculate them. Also, I calculate the number of days in the given quarter, just for completeness. The final query looks like this:
SELECT
1+DATEDIFF(day, #ParamStartDate, #ParamEndDate) AS QuarterDays
,CASE WHEN 1+DATEDIFF(day, CTE_MaxStartDate.AbsenceStartDate, CTE_MinEndDate.AbsenceEndDate) > 0
THEN 1+DATEDIFF(day, CTE_MaxStartDate.AbsenceStartDate, CTE_MinEndDate.AbsenceEndDate)
ELSE 0 END AS AbsenceDays
FROM
Absence
CROSS APPLY
(
SELECT CASE WHEN UntilDate < #ParamEndDate THEN UntilDate ELSE #ParamEndDate END AS AbsenceEndDate
) AS CTE_MinEndDate
CROSS APPLY
(
SELECT CASE WHEN FromDate > #ParamStartDate THEN FromDate ELSE #ParamStartDate END AS AbsenceStartDate
) AS CTE_MaxStartDate
WHERE
ABS_REASON='SICK'
-- all absence periods, which overlap with the given period
AND FromDate <= #ParamEndDate
AND UntilDate >= #ParamStartDate
I add 1 to DATEDIFF to get a duration of one day if start and end dates of the period are the same.

Subtracting two dates using PostgreSQL

I am trying to subtract 2 dates from each other but it seems that it is not subtracting properly and i am not sure what i am doing wrong here. I am using case statement to flag as 1 if the difference between the dates are less than 90 days else flag it as 0. But it is always flagging as 1 even if the difference between the dates are greater than 90 days. I am PostgreSQL here and here is my case statement:
CASE WHEN EXTRACT(DAY FROM CAST(SVS_DT AS DATE) - CAST(DSCH_TS AS DATE)) <90
THEN 1 ELSE 0 END AS FU90
example of the dates are here:
SVS_DT DSCH_TS
2013-03-22 00:00:00 2010-05-06 00:00:00
it is suppose to flag as 0 in this case but it is flagging as 1 because the difference between these 2 dates are greater than 90 days.
extract of a day returns the day element of a date. Since days are always between 1 and 31, the maximum difference is 30, and cannot be larger than 90.
Subtracting dates returns the difference in days, as an integer. So you need only drop the extract calls:
CASE WHEN (CAST(SVS_DT AS DATE) - CAST(DSCH_TS AS DATE)) < 90 THEN 1
ELSE 0
END AS FU90
you can use below one:
CASE WHEN (EXTRACT(EPOCH FROM (DATE_COLUMN_2 - DATE_COLUMN_1)) < (90*24*60*60)
THEN 1 ELSE 0 END AS FU90
here it returns the deference in seconds so you need to convert 90 days into seconds
A slightly shorter CAST version.
SELECT CASE WHEN SVS_DT::DATE - DSCH_TS::DATE < 90
THEN 1
ELSE 0
END
AS FU90
We can also do it without using any EXTRACT or CAST keyword like:
SELECT CASE WHEN (DATE(SVS_DT)- DATE(DSCH_TS)) < 90
THEN 1
ELSE 0
END AS FU90

Complicated sql date logic: How can I schedule my reports?

I have a table that looks something like this (simplified):
*ScheduledReports*
ReportID
StartDate
Frequency
Interval (1=months, 2=days)
So if I would want to run a report every 3 days, frequency would be 3 and interval would be 2.
I'm trying to write a stored procedure that we can run once a day, that will run all reports from the table that are scheduled for today (or should have been run since the last time we ran the stored procedure).
The stored procedure also has access to the lastRunTime (the last time this stored procedure was run).
This is what my query looks like so far:
-- get monthly reports that should be run
SELECT reportID
FROM ScheduledReports
WHERE intervalType = 1 AND
dateadd(m, (
frequency * ceiling((
--sql server calculates datediff of months based on the actual int of the month
--not if it's a true month later, so the following is necessary to check for
--a dayOfMonth difference
CASE
WHEN startDate > #lastRunTime
THEN 0
WHEN day(startDate) > day(#lastRunTime)
THEN datediff(m, startDate, #lastRunTime) - 1
ELSE datediff(m, startDate, #lastRunTime)
END
) / (frequency*1.0))
), startDate) BETWEEN #lastRunTime AND getDate()
UNION ALL
-- get weekly reports that should be run
SELECT reportID
FROM ScheduledReports
WHERE intervalType = 2 AND
dateadd(d, (
frequency * ceiling((
CASE
WHEN startDate > #lastRunTime
THEN 0
ELSE datediff(d, startDate, #lastRunTime)
END
) / (frequency*1.0)
)), startDate) BETWEEN #lastRunTime AND getDate()
There is something off with the logic though. What is wrong with my logic? How can I accomplish this?
The stored procedure also has access to the lastRunTime (the last time this stored procedure was run).
Don't you need to know the last time each report was produced? And not the last time this sproc was run? Each time you run the sproc, each report may or may not be produced. In order to know if the interval for each report has passed for each report (3 days, 1 month, etc) you need to know when THAT report was last produced.
What if you add a LastReportRun date column to your table. Then don't test todays date against #lastRunTime, but rather against LastReportRun.
Report Run Today; Today = 2012/04/15
ID StartDate Freqcy Interval LastReportRun
-- ---------- ------ -------- ----------------
1 2000/01/01 1 Days 2012/04/14 1 day ago; print it
2 2000/01/01 14 Days 2012/04/09 6 days ago; don`t print it
3 2000/01/01 3 Months 2012/01/13 > 3 mos ago; print it
4 2000/01/01 3 Months 2012/01/17 < 3 mos ago; don`t print it
We managed to fix the bug - we were supposed to be adding 1 in the case, not subtracting, because we want to get the next run date, not the previous. Changed the month case to:
CASE
WHEN startDate > #lastRunTime
THEN 0
WHEN day(startDate) > day(#lastRunTime)
THEN datediff(m, startDate, #lastRunTime)
ELSE datediff(m, startDate, #lastRunTime) + 1
END
and the day case to:
CASE
WHEN startDate > #lastRunTime
THEN 0
ELSE datediff(d, startDate, #lastRunTime) + 1
END
And now it is working perfectly :)

how to get the number on months between two dates in sql server 2005

I have a column in my sql server 2005 table that should hold the number of months an employee has been in service.
Since I also have the date the employee was engaged, I want the "months_In_Service" column to be a computed column.
Now if I use DATEDIFF(month,[DateEngaged],GETDATE()) as the formula for the months in service computed column, the results are correct some times and other times incorrect.
What would be the better reliable way to get the number of months between the DateEngaged value and the current date? Which formula should i use in my computed column?
Something like (might need to swap the 1 and 0, untested)
datediff(month,[DateEngaged],getdate()) +
CASE WHEN DATEPART(day, [DateEngaged]) < DATEPART(day, getdate()) THEN 1 ELSE 0 END
DATEDIFF measure month boundaries eg 00:00 time on 1st of each month, not day-of-month anniversaries
Edit: after seeing OP's comment, you have to subtract 1 if the start day > end day
DATEDIFF (month, DateEngaged, getdate()) -
CASE
WHEN DATEPART(day, DateEngaged) > DATEPART(day, getdate()) THEN 1 ELSE 0
END
So for 20 Dec to 13 Jan, DATEDIFF gives 1 and then 20 > 13 so subtract 1 = zero months.
Same approach as gbn, but with less keystrokes :-)
SELECT
DATEDIFF(MONTH, DateEngaged, GETDATE()) +
CASE
WHEN DAY(DateEngaged) < DAY(GETDATE())
THEN 1
ELSE 0
END
Maybe you want something like:
(year(getdate())-year([DateEngaged]))*12+(month(getdate())-month([DateEngaged]))
If You presume that month is meaning for 30 days You can also round vale
round((datediff(day,[DateEngaged],getdate()))/30.00,0)