Get result of last six months using DATEDIFF - sql-server

I'm trying to create a query in SQL that retrieves rows based on their date.
I want to get the result of the last 6 months using DATEDIFF() function (and not another function ) but my query still returns rows that are greater than GETUTCDATE().
The query that I use is:
SELECT * FROM CARS
WHERE DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) < 180
Why am i still getting results that are greater than GETUTCDATE() ?

First, you may think you want to use datediff, but the fact that you are using it (or any other function, for that matter) on a column makes it impossible for SQL Server to use any indexes defined with this column - and that might be a real performance penalty for that.
Second, the reason you get records for future dates is that if the first date is later than the second date, the DateDiff function will return a negative number. All negative numbers I know of are smaller than 180.
A better query would be this:
SELECT *
FROM CARS
WHERE c.ExpiredWarranty <= GETUTCDATE()
-- If you want 6 months, don't bother with days...
AND c.ExpiredWarranty > DATEADD(MONTH, -6, GETUTCDATE())

DATEDIFF returns a positive number whenever the third argument is greater than the second. In your case, you want records whose warranties have expired within 6 months. On one extreme, this is 180 days, and the other extreme, this is 0 days. For warranties expiring in the future, your current call to DATEDIFF would return a negative number.
To fix this, just restrict the DATEDIFF output to between 0 and 180 days, and don't allow negative diffs:
SELECT *
FROM CARS
WHERE DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) BETWEEN 0 AND 180;

Because if ExpiredWarranty > GETUTCDATE() then DATEDIFF between them returns a negative number which is definitely less then 180.
Try:
SELECT *
FROM CARS
WHERE DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) < 180
AND DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) >= 0;
Or:
SELECT *
FROM CARS
WHERE DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) BETWEEN 0 AND 180;

Try this:
WHERE DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) >= 0 AND DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) < 180
Or:
WHERE DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) BETWEEN 0 AND 180;

Related

Convert number of hours to days and hours in SQL Server (NOT T-SQL)

I have a number of hours which I need to display in the format of days and hours.
This number is derived from a DATEDIFF instruction.
For numbers less than 24, I wish to display only hours - ie, 21 hours.
For larger numbers, I wish to display days and hours - ie, 3 days, 14 hours
I do not need to display any smaller unit than hours, and values should be rounded down to the preceding hour, so 1 hour and 59 minutes will be 1 hour.
I cannot use a stored procedure - this must run as a single select statement.
I am aware that I can calculate the value by using modulo, so assuming 71 hours:
select concat((71 - (71 % 24)) / 24, ' days, ', 71 % 24, ' hours')
This however is somewhat messy, and as the statement must be a single select, I will have to calculate the DATEDIFF 3 times as below.
SELECT CONCAT (
(DATEDIFF(HOUR, StartDate, EndDate) -
(DATEDIFF(HOUR, StartDate, EndDate) % 24)) / 24,
' days, ',
DATEDIFF(HOUR, StartDate, EndDate) % 24,
' hours')
FROM RecordsTable
Is it possible to either format a number of hours as days and hours directly using an inbuilt SQL command, or failing that, select (datediff(hour, StartDate, EndDate) into a variable which I can reuse in the single select?
EDIT - As suggested, the solution was to use a CTE as follows:
WITH totalhours (htotal) AS
(
SELECT
DATEDIFF(HOUR, StartDate, EndDate) AS htotal
FROM
RecordsTable
)
SELECT
CONCAT ((htotal - (htotal % 24)) / 24,
' days, ',
htotal % 24,
' hours')
FROM
RecordsTable;
Use a CTE to generate your total once, and reference that total in your select against the CTE. Or use a subquery to generate the total once and then select from the subquery to get the desired results.
The fundamental issue is you need to materialize the total once to be able to reference it; forcing the engine to materialize a value is generally done via a CTE or subquery.
You can do a lot with datetime objects and format strings or datepart. For example,
declare #n int = 105;
select format(dateadd(day, -1, dateadd(hour, #n, '1753-1-1')), 'd h');
-- 4 9
Taking the minimum datetime value (1753-01-01), adding the requisite number of hours, subtracting one day (because on the first day you want days = 0), and then formatting.
You could improve the formatting like this:
select format(dateadd(day, -1, dateadd(hour, #n, '1753-1-1')), 'd \da\y(\s), h \hour(\s)');
-- 4 day(s), 9 hour(s)
Of course this will only work up to 31 days, because then you'll be out of the month of January in 1753 and into February. If that's the case, revert to datepart. This is uglier, but will work for larger values
select
datepart(day, (dateadd(day, -1, dateadd(hour, #n, '1753-1-1')))),
datepart(hour, (dateadd(day, -1, dateadd(hour, #n, '1753-1-1'))));

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.

How can I get a count every 60 days using datepart()?

The following query (on MSSQL) gives a correct answer to the question : how many IPs were collected on a monthly basis.
I would like to know how can I get the count per every 60 days?
select MIN(rowdate) min_rowdate,
MAX(rowdate) max_rowdate,
count(distinct IP),
DATEPART(MONTH, rowdate) month_
from t_tbl tl (nolock)
where rowdate between '2015-01-01 00:00:00' and '2015-12-31 23:59:59'
group by DATEPART(MONTH, rowdate)
You can get it using DATEDIFF and some simple maths:
select MIN(rowdate) min_rowdate, MAX(rowdate) max_rowdate, count(distinct IP),
DATEDIFF(day, 0, rowdate) / 60 as day60
from t_tbl tl (nolock)
where rowdate >= '20150101' and rowdate < '20160101'
group by DATEDIFF(day, 0, rowdate) / 60
In this instance, it's using 1900-01-01 (what 0 gets converted to) as the start of the first 60 day period and all subsequent periods follow on from there.
If you want to use a different "fixed point" for the reporting periods, you'd put that in in place of 0 as the second parameter to DATEDIFF.
(I've also corrected your WHERE clause so that it doesn't exclude any events that happened during the last second of 2015, i.e. with a non-zero milliseconds value)

SQL Server : DATEDIFF not accurate

I'm trying to retrieve the month difference of two dates but it seems like I can't find a way to get the accurate months.
Here are the queries I tried so far :
SELECT DATEDIFF(month,convert(datetime, '11/05/2015'), convert(datetime, '12/06/2015')) - 1
This will result to 0 which is wrong and when I used another date :
SELECT DATEDIFF(month,convert(datetime, '12/31/2015'), convert(datetime, '01/01/2016')) - 1
This would yield to 0 which is correct.
Leap year must also be considered.
The TSQL is return the correct results as you have written them. As you have it, it is taking the difference of the months between the two dates specified.
SELECT DATEDIFF(month, convert(datetime, '11/05/2015'), convert(datetime, '12/06/2015'))
The difference, in months, between November and December is "1".
However, if you are wanting the difference in terms of every 30 days is 1 month, then you would need to rewrite your query:
declare #daysPerMonth int = 30
SELECT (DATEDIFF(day, convert(datetime, '11/05/2015'), convert(datetime, '12/06/2015')) / #daysPerMonth)
This works if you define the number of days in a month as 30.
Here is a way to visualize what DATEDIFF(month, ...) is doing. Think of a monthly calendar hanging on the wall. For the purposes of this particular SQL function "number" of months between two dates is the number of pages you have to flip to get from one date to the next. It doesn't matter how many days are in each month or whether it's a leap year.
You aren't the first person to be confused about the behavior of the built-in function. If you need to count months by a different method then you'll need to describe the specifics of what you want to accomplish. It's very likely that a solution will be easy to create once you define the problem that needs to be addressed.
In T-SQL you can use CASE:
SELECT DATEDIFF(month, convert(datetime, '11/05/2015'),
convert(datetime, '12/06/2015')) -
CASE
WHEN (MONTH('11/05/2015') > MONTH('12/06/2015')
OR MONTH('11/05/2015') = MONTH('12/06/2015')
AND DAY('11/05/2015') > DAY('12/06/2015'))
THEN 1 ELSE 0
END
Which returns 1 which is correct and:
SELECT DATEDIFF(month, convert(datetime, '12/31/2015'),
convert(datetime, '01/01/2016')) -
CASE
WHEN (MONTH('12/31/2015') > MONTH('01/01/2016')
OR MONTH('12/31/2015') = MONTH('01/01/2016')
AND DAY('12/31/2015') > DAY('01/01/2016'))
THEN 1 ELSE 0
END
Which returns 0 which is correct.
declare
#start date = '20220105',
#end date = '20220406'
select 'Date_Diff_In_3 months' =
case
when datediff(month, #start, #end) < 3
or (datediff(month, #start, #end) = 3 and day(#start) >= day(#end))
then 'yes'
else 'no'
end

Creating a computed column in MSSQL

I'm having trouble creating a computed column in MSSQL. I'm using 2012, but ideally this would work with anything 2005 and on.
I need to set a column to be equal to 1% of another column for each month since the start date.
Here's what I have so far. I've tried entering this into the formula property of the computed column, but MSSQL says "There is an error with your forumla".
CASE
WHEN DATEDIFF(d, LienStartDate, GETDATE()) - (DATEDIFF(yyyy, LienStartDate, GETDATE()) * 365) >= 0
THEN Amount * (DATEDIFF(m, LienSTartDate, GETDATE()) * 0.01)
WHEN DATEDIFF(d, LienStartDate, GETDATE()) - (DATEDIFF(yyyy, LienStartDate, GETDATE()) * 365) < 0
THEN Amount * ((DATEDIFF(m, LienStartDate, GETDATE()) - 1) * 0.01) END
When I run it against the following data as a select I get back the correct results. (12, 11, 12)
LienStartDate | Amount
2014/01/20 100
2014/01/25 100
2014/01/23 100

Resources