Calculating Next Due Date - sql-server

I'm new to T-SQL and have come upon a piece of code and was wondering if someone could help me understand what is happening. The point of the code is to calculate the date the next payment.
CASE
WHEN MONTH(table.field-10) < MONTH(GETDATE())
THEN CONVERT(DATE,((MONTH(GETDATE()) - MONTH(table.field-10)) *30) + (table.field))
WHEN MONTH(table.field-10) = MONTH(GETDATE())
THEN CONVERT(DATE,FA_NEXTDUE)
WHEN MONTH(table.field-10) > MONTH(GETDATE())
THEN CONVERT(DATE,(table.field) - ((MONTH(table.field-10) - MONTH(GETDATE())) *30))
END AS CurrentDueDate

This appears quite poorly written as it uses a "month" function that does not take the year into account. It appears to trying to determine if ten days before the date in "field" is in the same month as the current date. However, due to this not taking the year into account the output is a bit erratic.
A better way would be to use the datediff function.
CASE WHEN DATEDIFF(month, getdate(), (dateadd(day, -10, table.field))) < 0 then dateadd(month, DATEDIFF(month, getdate(), (dateadd(day, -10, table.field))), table.field)
CASE WHEN DATEDIFF(month, getdate(), (dateadd(day, -10, table.field))) = 0 then CONVERT(Date, fa_nextdue)
CASE WHEN DATEDIFF(month, getdate(), (dateadd(day, -10, table.field))) > 0 then dateadd(month, DATEDIFF(month, getdate(), (dateadd(day, -10, table.field))), table.field)
Even with this correction, I am skeptical that the output is what the business would require. You might want to dig into the requirements a little to see what exactly should be returned here.
FYI table.field-10 = ten days before the "field" date.

Related

Using Parameters to generate date range

So I have multiple reports I am trying to merge into one report. The big issue is that one report is run every two weeks and the other is run once a month.
The date range for the report was created using this sql
SELECT
CASE
WHEN DAY(GETDATE()) <= 15 THEN
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) - 1, 0) + 15
ELSE
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) , 0)
END AS LO_DATE
So I tried adding a parameter that I could use that would basically say, hey if this is checked, then always run it for the whole month not the last two weeks. That sql looks like this.
IF (#RUN_FOR_MONTH = 'true')
SELECT
CASE
WHEN DAY(GETDATE()) <= 15 THEN
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) - 1, 0) + 15
ELSE
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) , 0)
END AS LO_DATE
ELSE
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) , 0) AS LO_DATE
However I keep getting this error:
The report parameter ‘LO_DATE’ has a DefaultValue or a ValidValue that depends on the report parameter “RUN_FOR_MONTH”. Forward dependencies are not valid.
I am new to using SQL Server Report Builder, so if you need more information, please ask and I'll provide it.
REQUESTED CHANGE - I still get the same error
SELECT
CASE
WHEN #RUN_FOR_MONTH = 'true' THEN
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) , 0)
WHEN DAY(GETDATE()) <= 15 THEN
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) - 1, 0) + 15
ELSE
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) , 0)
END AS LO_DATE
Parameters in SSRS are derived sequentially. If you have a parameter that is based off another's value, you must list those parameters in in the correct sequential order. Take the below
In this example, #DateFrom would be evaluated first, and then #DateTo; thus #DateFrom cannot depend on #DateTo (however the reverse is fine).
If, for example, the value of #DateFrom was DateAdd("d", -2, #DateTo) you would receive the error you have above. You would need to select #DateTo and click the Up Arrow icon (Or select #DateFrom and the down arrow)

Counting datediff from 2 date

I have 2 datetime fields, NEW_EMPLOYMENT_STARTDATE and NEW_EMPLOYMENT_ENDDATE
When I calculate the difference in months using datediff with these values for startdate and enddate:
NEW_EMPLOYMENT_STARTDATE = 2017-15-01
NEW_EMPLOYMENT_ENDDATE = 2018-14-01
With this query:
DATEDIFF(MONTH, NEW_EMPLOYMENT_STARTDATE, NEW_EMPLOYMENT_ENDDATE)
It returns 12 months, but when I have values like this:
NEW_EMPLOYMENT_STARTDATE = 2017-01-01
NEW_EMPLOYMENT_ENDDATE = 2017-31-12
It returns 11 months.
How can I get 12 months? I am using this query:
DATEDIFF(MONTH, NEW_EMPLOYMENT_STARTDATE, NEW_EMPLOYMENT_ENDDATE)-
CASE
WHEN DATEPART(DAY, NEW_EMPLOYMENT_ENDDATE) > DATEPART(DAY, NEW_EMPLOYMENT_STARTDATE)
THEN 0
ELSE 0 END AS MONTH_DIFF
It still returns 11 months.
EDIT:
According to my case, the value of the end date always less 1 day from start date, so i make a trick to check condition with case when like this:
CASE
WHEN DATEPART(DAY, NEW_EMPLOYMENT_ENDDATE) > DATEPART(DAY, NEW_EMPLOYMENT_STARTDATE)
THEN DATEDIFF(MONTH, NEW_EMPLOYMENT_STARTDATE, NEW_EMPLOYMENT_ENDDATE)+1
ELSE DATEDIFF(MONTH, NEW_EMPLOYMENT_STARTDATE, NEW_EMPLOYMENT_ENDDATE)
END AS DATEDIF
i add + 1 value to the end date so i ta can be round to next month, give feedback from my solution sir thanks
You're expectations are incorrect. When you do a DATEDIFF using MONTH, it does not consider the day portion of the dates. Consider that it is simply considering the difference in the month numbers only, regardless of the day specified.
This query:
SELECT DATEDIFF(MONTH, '20170101', '20171231') MonthsDiff
Is equivalent to this:
SELECT DATEPART(MONTH, '20171231') - DATEPART(MONTH, '20170101') MonthsDiff
The documentation for DATEDIFF states:
DATEDIFF ( datepart , startdate , enddate )
The first option is DATEPART:
datepart
Is the part of startdate and enddate that specifies the type of boundary crossed.
If you want something closer to what you expect, you can do a simple calculation based on performing the DATEDIFF in days, the dividing it by the approximate number of days in a month.
SELECT DATEDIFF(DAY, '20170101', '20171231') / ( 365 / 12 ) MonthsDiff
This will round the output to the closest month number, it all depends on how accurate you want to be. If you want months as a decimal for greater accuracy then run the below:
SELECT DATEDIFF(DAY, '20170101', '20171220') / ( 365.00 / 12 ) MonthsDiff
Note: This does not take into account leap years, for larger date ranges that might include leap years, which will make a minor difference to the accuracy.
datediff() does something very particular. It counts the number of "boundaries" between two date/time values. In your case, there are eleven boundaries -- one for each month in the year before December.
This behavior is not necessarily intuitive. If you add one day to each of your dates:
NEW_EMPLOYMENT_STARTDATE = '2017-01-02' (YYYY-MM-DD is standard format)
NEW_EMPLOYMENT_ENDDATE = '2018-01-01'
Then you will have 12 months.
If you want to round up, you can play with the dates. One method would be to normalize the first value to the beginning of the month and then add 15 days to the second value:
DATEDIFF(MONTH,
DATEADD(DAY, 1 - DAY(NEW_EMPLOYMENT_STARTDATE), NEW_EMPLOYMENT_STARTDATE)
DATEADD(DAY, 15 + 1 - DAY(NEW_EMPLOYMENT_STARTDATE), NEW_EMPLOYMENT_ENDDATE)
)
This would happen to work for the two examples you give.
Please use this select to achieve your desired result. You can use table columns instead of variables:
declare #new_employment_startdate datetime = convert (datetime, '2017-01-01', 121);
declare #new_employment_enddate datetime = convert (datetime, '2018-01-14', 121);
select
datediff(month, #new_employment_startdate, #new_employment_enddate)
+ case when
datediff(month, dateadd(ms, -3, dateadd(dd, datediff(dd, 0, #new_employment_startdate), 0)), #new_employment_startdate) = 1
and datediff(month,#new_employment_enddate , dateadd(dd, datediff(dd, 0, #new_employment_enddate) + 1, 0)) = 1
then 1
else 0
end;
Some explanations:
I check or start date is first month day AND end date is last month day. At this case I add +1 to standard datediff by month.
You can better understand my used datetime manipulations by using these example queries: https://gist.github.com/runnerlt/60636b029ab47845fdfd8924ed482e61
You need to add 1 more day in your End Date.
DATEDIFF(MONTH, NEW_EMPLOYMENT_STARTDATE, DATEADD(DD,1,NEW_EMPLOYMENT_ENDDATE))
You could match the output with MS Excel.

What is the difference between date handling in my queries?

I need the number of records of last month. I used to use this approach so far:
select
count(ID)
from
table
where
col1 = 'somerule'
and
DateTimeOfInsert >= '20150901'
and
DateTimeOfInsert <= '20150930'
Now I'm about to atomate this task and therefore I have to pull the start and end dates of last month automatically. So here it is:
select
count(ID)
from
table
where
col1 = 'somerule'
and
DATEPART(m, DateTimeOfInsert) = DATEPART(m, DATEADD(m, -1, getdate()))
and
DATEPART(yyyy, DateTimeOfInsert) = DATEPART(yyyy, DATEADD(m, -1, getdate()))
My only issue is that at this very moment the first query returns 1073 and the second one 1124. So the question is obvious: what is the difference between them? Both should inlude the start and end date. I can't spot it.
This condition:
DateTimeOfInsert >= '20150901' and DateTimeOfInsert <= '20150930'
retrieves record that are between 2015-09-01 00:00:00.000 and 2015-09-30 00:00:00.000.
If DateTimeOfInsert is DATETIME, then this will return different result from your other condition.
The best way for this kind of queries is not to use BETWEEN but rather use
>= and <. In your case, you want to get records for the last month, so you want to use >= start of last month and < start of this month:
DateTimeOfInsert >= DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) - 1, 0) -- Beginning of previous month
AND DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0) -- Beginning of this month
The above condition also makes your query sargable.
For some common date routines, see this article.
The difference is in time component of datetime.
This:
DateTimeOfInsert >= '20150901' and DateTimeOfInsert <= '20150930'
will not select date like 20150930 15:30.
But this:
DATEPART(m, DateTimeOfInsert) = DATEPART(m, DATEADD(m, -1, getdate()))
will select it because you are checking for months and year part only. That`s why the second select returns more rows.
Both queries will return the same if you just change the first statement so that it will consider time component of last day of month:
DateTimeOfInsert >= '20150901' and DateTimeOfInsert < '20151001'

Dates between 18 and 9 months ago but from beginning to end of relevant months

I have a query that I thought I had cracked to return all records where the start date is between 18 and 9 months ago but I want all of the records for the month 18 months ago from the 1st of the month and all of the records for the month 9 months ago to the end of the month as well as the records in between.
At the moment my WHERE clause contains:
WHERE startdate BETWEEN
DATEADD(DAY, - (DAY(DATEADD(MONTH, 1, GETDATE())) - 18), DATEADD(MONTH, - 18, GETDATE()))
AND DATEADD(DAY, - (DAY(DATEADD(MONTH, 1, GETDATE())) - 9), DATEADD(MONTH, - 9, GETDATE())
But it is pulling all records where start date is between 18 and 9 months ago to the exact date today and is not going back to the start of the month or completing the end month. Please could you advise where I have gone wrong. I am sure it is something really simple but I have gone bracket blind now I think.
You can use DateAdd and DateDiff to get the first day of the month, like shown in this answer.
From this it's fairly simple to do something like this:
WHERE StartDate >= DATEADD(month, -18, DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0))
AND StartDate < DATEADD(month, -8, DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0))
You can use new SQL EMONTH() end of month function
For the first of month, you can add 1 day to the previous month's end of month

Query to get the first day of the month not working with DATEADD

I've been writing queries to truncate the time from a given datetime for years now, based on answers like this one and this one (and many other places), that groups a couple functions like
SELECT DATEADD(day, 0, DATEDIFF(day, 0, getdate()))
and it always gives the right answer.
I figured I could translate the same logic to finding the first of the current month by using month instead of day, but it's giving me a weird date for the result: 1903-10-17, instead of 2015-05-01.
My parameters have always been in the wrong order.
It turns out the format for DATEADD I've been using all these years is wrong, and it's only been working because it's using the day datepart. Casting an int to a date increments the day:
SELECT CAST(0 AS datetime) = '1900-01-01 00:00:00.000'
SELECT CAST(1 AS datetime) = '1900-01-02 00:00:00.000'
SELECT CAST(2 AS datetime) = '1900-01-03 00:00:00.000'
I should be using DATEADD(d, DATEDIFF(d, 0, getdate()), 0) - the parameters are (datepart, number, date), as laid out here at MSDN.
Writing it as SELECT DATEADD(month, DATEDIFF(month, 0, getdate()), 0) gives the expected result of 2015-05-01.

Resources