Why does DATEDIFF return -1 - sql-server

I'm trying to understand how the DATEDIFF function works in SQL Server. The following T-SQL returns -1:
SELECT DATEDIFF(MONTH, 2015-10-25, 2015-12-27)
However, ending_date is after start_date so I would expect a positive number, not a negative number. Further, I would expect that the difference between Oct 25 and Dec 27 to be 2 months. Instead, SQL Server is returning -1 month. Can someone explain what's going on?

You are missing apostrophes:
SELECT 2015-10-25 AS First,
2015-12-27 AS Second,
CAST(2015-10-25 As DateTime) AS [First as datetime],
CAST(2015-12-27 As DateTime) AS [Second as datetime],
DATEDIFF(MONTH, 2015-10-25, 2015-12-27) AS WrongResult,
DATEDIFF(MONTH, '2015-10-25', '2015-12-27') AS CorrectResult
Results:
First Second First as datetime Second as datetime WrongResult CorrectResult
1980 1976 04.06.1905 00:00:00 31.05.1905 00:00:00 -1 2
SQL Server looks at 2015-10-25 as an int - the result of the mathematical expression (1980).
The usage of int in datetime functions cause SQL Server to implicitly convert the int value to a DateTime value.
The int value represents the number of days since '1900-01-01' - and since the second argument results in a smaller int value, you get a negative number.
As Jeroen Mostert wrote in his comment - A gotcha for the books.

You are not quoting your dates, this means your expression evaluates to:
SELECT DATEDIFF(MONTH, 1980, 1976)
Which after implicit conversion to datetime evaluates to:
SELECT DATEDIFF(MONTH, '1905-06-04 00:00:00.000', '1905-05-31 00:00:00.000')
So now the startdate is after the enddate.

Because you're using numbers (integers specifically), not strings. 2015-10-25 = 1980 which, when you convert to a date is '1905-06-04'. On the other hand 2015-12-27 = 1976, which as a date is '1905-05-31'. As '1905-05-31' is one month before '1905-06-04' you get the result -1.
Use literal strings, and yyyyMMdd dates:
SELECT DATEDIFF(MONTH, '20151025', '20151227');

Put the date values within single quotes.
Query
SELECT DATEDIFF(MONTH, '2015-10-25', '2015-12-27');
This query returns 2 as the output.

Related

The datediff function resulted in an overflow message

doing something like this:
select *
from INVOICE_HEADING
where INVOICE_DATE >= '06 Dec 2018 00:00:00'
INVOICE_DATE <= '16 Dec 2018 00:00:00'
and I get this message:
"The datediff function resulted in an overflow. The number of dateparts separating two date/time instances is too large. Try to use datediff with a less precise datepart."
How can I write it in a different way (or how can I use datediff here?) to get the result?
The function DATEDIFF returns a signed integer, which can hold values from -2.147.483.648 to 2.147.483.647. If the dates you are applying the function to and the unit you are using (month, day, second, etc.) generate a difference outside these bounds then an error is thrown.
There are a few workarounds:
Use DATEDIFF_BIG if you are using SQL Server 2016+.
Move to a "higher" unit (milliseconds -> seconds -> minutes -> hours and so on) until the value you get can be cast into a integer and make sure that all the values you might apply the function to in the future will still be inside the bounds of an integer. You can then drill down the unit to the one you need by multiplying and handling the value as BIGINT (for example).
It's common for this error to pop up when comparing dates that are not valid to the business or generated by default as 1900-01-01. You can filter these with a WHERE clause, supply a decent default value or convert to NULL. Can also avoid applying the DATEDIFF function with a CASE before it when dates aren't reasonable.
Examples:
DECLARE #OldDate DATE = '1900-01-01'
DECLARE #Now DATE = GETDATE()
SELECT DATEDIFF(SECOND, #OldDate, #Now) AS DateDiffResult
--Msg 535, Level 16, State 0, Line 5
--The datediff function resulted in an overflow. The number of dateparts separating two date/time instances is too large. Try to use datediff with a less precise datepart.
Change the unit from second to minute:
DECLARE #OldDate DATE = '1900-01-01'
DECLARE #Now DATE = GETDATE()
SELECT DATEDIFF(MINUTE, #OldDate, #Now) AS DateDiffResult
-- DateDiffResult: 62599680
Revert the minute to second with a "bigger" data type:
DECLARE #OldDate DATE = '1900-01-01'
DECLARE #Now DATE = GETDATE()
SELECT
CONVERT(BIGINT, DATEDIFF(MINUTE, #OldDate, #Now)) * 60 AS DateDiffResult
-- DateDiffResult: 3755980800

T-SQL DATEDIFF MONTH January

This may seem like a very simple/silly question but, if I use this code on January, will the output be December (as in January - 1 = December)?
SELECT DATENAME(MONTH, DATEDIFF(MONTH, -1, GETDATE()));
You're using both functions incorrectly. I recommend looking at the online help for more details, but here is an explanation of what you've done...
DATEDIFF()'s last two parameters are both DATETIMEs.
It finds the difference between those two dates.
In years, or months, or days, or hours, etc.
So, when you supply a -1 as the first date it is implicitly CAST to a DATETIME.
As it happens, 0 is 1970-Jan-01 # 00:00:00
Which means that -1 is 1969-Dec-31 # 00:00:00.
You did How many months are there between 1969-Dec-31 and now?
Which is current 1405 months
DATENAME()'s second parameter is also a DATETIME.
It returns the name of the month for that date
Or name of day, etc.
So, when you supplied 1405 as the DATETIME parameter, that also got implicitly converted.
That's 1405 days after 1st Jan 1970
Which is 6th Nov 1973
So DATENAME() returns 'November'
You possibly just want to take one month away from a specific date, and then get its name.
DATENAME( MONTH, DATEADD( MONTH, -1, getDate() ) )
As written, no. As of this current time, it will only return November. This is because you're using DATEDIFF instead of DATEADD.
You can see a bit of why it does this by selecting the DATEDIFF portion:
SELECT DATEDIFF(MONTH, -1, '2017-01-01')
1405
And Select DateName(Month, 1405) returns November.
But, this should be what you meant to use:
SELECT DATENAME(MONTH, DATEADD(MONTH, -1, GETDATE()));
And yes, using that will return December:
SELECT DATENAME(MONTH, DATEADD(MONTH, -1, '2017-01-01'));
December

Can any one please tell me logic behind select DATENAME(month,29*5)

select DATENAME(month,29*5)
Can any one please tell me logic behind the above query.
How it always returns correct month name when provided month number as integer.
Datetime values in Sql server are stored on 8 bytes.
The first 4 bytes represents the date and the last 4 byte represents the time.
On the date part, date is stored as the number of days since 1900-01-01.
On the time part, it's the number of clock ticks since midnight.
There are 300 clock ticks per second, so a tick is 3.33333 milliseconds.
That's also the reason why datetime is only accurate to .003 of a second.
This query will hopefully help to explain:
SELECT CAST(0 As datetime) As Date_0,
29*5 As NumberOfDays,
CAST(29*5 as datetime) As TheDate,
DATENAME(month,29*5) As TheMonthName
Results:
Date_0 NumberOfDays TheDate TheMonthName
----------------------- ------------ ----------------------- ------------
1900-01-01 00:00:00.000 145 1900-05-26 00:00:00.000 May
As for the last part of your question, 29 (28 would work as well) is the magic number here - 30 is too big (May would be returned for 4 and 5) and 27 is too small - (September would be returned for 9 and 10).
Basically i'ts just math - get the number correctly so that each time you double it with any number between 1 and 12 will give you a number of days that sums up to a day that belongs to the correct month.
You can test it yourself using this script:
DECLARE #MagicNumber int = 28
;With cte as
(
select 1 as num
union all
select num + 1
from cte
where num < 12
)
SELECT num, DATENAME(month, #MagicNumber * num ) As TheMonthName
from cte
Just change the value of #MagicNumber and see the results you get.
I think I will able to explain.
The default year-month-day for any date data type is 1900-01-01. If we consider above select query, it add 29*5 days into default date and gives the MONTHNAME.
Select DATENAME(month,29*5)
Now understand the DATENAME
DateName - Returns a character string that represents the specified datepart of the specified date. Its have different -2 argument and give the different-2 result as per datepart.
Argument 1 - Is the part of the date to return.
Argument 2 - Is a any date (Is an expression that can be resolved to a
time, date, smalldatetime, datetime, datetime2, or datetimeoffset
value.)
Here we given month as a first argument. Which means it return monthname.
The calculation of 29*5 gives 145 answer and if we simply cast into date it consider as a days and calculate as 1900-01-01 + 145 and gives the date 1900-05-26 00:00:00.000.
Means if we get the month of this will give the 5 - MAY as a answer.
Execute this query and check the answer for the above logic.
Select DATENAME(month,29*5), (29*5) , DATENAME(month, '12:10:30.123'), DATENAME(month, getdate())
select cast (145 as datetime)
DECLARE #t datetime = '12:10:30.123';
SELECT DATENAME(month, 29*5), 145/30.00;
Check for further.
MSDN Link
Convert Month Number to Month Name Function in SQL (check the #user275683 answer)
If you are simply want to show the month corresponding to month number then you should have to use like this.
declare #intMonth as int
set #intMonth = 5
Select DateName( month , DateAdd( month , #intMonth , -1 ))

To get the previous month's last date till last second

I want to get last month's last date (whether 30 or 31) and time till last second, whenever the query is executed.
Eg. 11/30/2015 11:59:59 PM
So I have a query like
SELECT DATEADD(ss, (60*60*24)-1, DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), -1))
It solves my problem. But what is the difference between the query written above and the one below, when I change the DATEDIFF part and replace 0 with 1?
SELECT DATEADD(ss, (60*60*24)-1, DATEADD(MONTH, DATEDIFF(MONTH, 1, GETDATE()), -1))
Will both of these queries gives the same result whenever they are run, or which should I consider as the permanent solution?
Do NOT do this; attempt to get the "last second" of the last day of the previous month
I make this bold statement on the assumption you are attempting to use BETWEEN and you are concerned with the accuracy of something like this:
select sum(value) from Atable
where [Adate] BETWEEN '20151201' AND '21051231 23:59:59'
But the complexity of arriving at the last point in time on the last day of any month is solved so easily by using the first day of the next month instead. All that you also need to do is drop the use of BETWEEN. Like this:
select sum(value) from Atable
where [Adate] >= '20151201' and [Adate] < '21060101'
LESS THAN "the first day of the this month"
That is how you solve your conundrum.
& by the way: The precision (accuracy) of smalldatetime, datetime and datetime2 all differ, all the more reason not to use BETWEEN.
see "Be careful about rounding errors." at http://sqlmag.com/t-sql/t-sql-best-practices-part-2
Specifically, do this:
DateLogged < SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0)
This will be 100% accurate for date, smalldatetime, datetime and datetime2 columns.
Here is another attempt to explain why LESS THAN [the_next_day_at_00:00:00+0000000] is accurate and using 22:59:59 is NOT accurate. Please take note of the sample data accuracy
SQL Fiddle
MS SQL Server 2014 Schema Setup:
Query 1:
DECLARE #Tbl TABLE
( [ID] int identity(1,1)
, [DT_a] datetime
, [DT_b] datetime
, [DT_c] datetime2
)
INSERT INTO #Tbl
([Dt_a], [Dt_b], [Dt_c])
VALUES
(
'20151231 23:59:59'
, '20151231 23:59:59.997'
, '20151231 23:59:59.9999999'
)
select
'where [DT_b] <= 20151231 23:59:59' as FilterString
, max([Dt_a]) as [Dt_a]
, max([Dt_b]) as [Dt_b]
, max([Dt_c]) as [Dt_c]
from #Tbl
where [DT_b] <= '20151231 23:59:59'
UNION ALL
select
'where [DT_b] < 20160101'
, max([Dt_a]) as [Dt_a]
, max([Dt_b]) as [Dt_b]
, max([Dt_c]) as [Dt_c]
from #Tbl
where [DT_b] < '20160101'
Results:
| FilterString | Dt_a | Dt_b | Dt_c |
|-----------------------------------|----------------------------|----------------------------|-----------------------------|
| where [DT_b] <= 20151231 23:59:59 | (null) | (null) | (null) |
| where [DT_b] < 20160101 | December, 31 2015 23:59:59 | December, 31 2015 23:59:59 | 2015-12-31 23:59:59.9999999 |
Data accuracy
smalldatetime: one minute
datetime: rounded to increments of .000, .003, or .007 seconds
datetime2: 100 nanoseconds
To avoid possible errors from rounding by time units DO NOT USE <= 23:59:59
Instead use LESS THAN [the_next_day]
AND, as a consequence AVOID USING BETWEEN for date ranges.
See this link for how DATEDIFF is used or should be used in SQL Server. The 2nd argument, the one which does not seem to make a difference in your case, is supposed to be the start date which is subtracted from the end date (getdate()) to get the difference and then converted to months. I would try and use this function the typical way and provide a proper start date.
Also below is an alternative way of getting the same result
SELECT DATEADD(ss, -1, '01/' + CONVERT(VARCHAR, DATEPART(MONTH, getdate())) + '/' + CONVERT(VARCHAR, DATEPART(YEAR, getdate())));
It is because of DATEDIFF( MONTH, 0, GETDATE()) Function
If you use an integer as the second argument, this is interpreted as the number of days since 1900-01-01 regardless of the Interval you are using in the datediff function.
For eg:
SELECT YEAR(0), MONTH(0), DAY(0);
year month day
1900 1 1
Now if I Increment 0 to 1 in year, month, day
SELECT YEAR(1), MONTH(1), DAY(1);
year month day
1900 1 2
Now if I Increment values to 365,
SELECT YEAR(365), MONTH(365), DAY(365);
year month day
1901 1 1
You can see the Year got incremented by 1.
there are many ways to find out the previous month's last date. Here is the one I am using.
SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE()),0))
Well it would be expected that substracting one millisecond to the first day of the current month you would get the last millisecond of the previous month but it doesn't work that whay, with datediff millisecond,-1 you still get the first day of the month you have to do datediff millisecond,-2 to reach 997 milliseconds, no way to get 999 nor 998.(without using text).
select dateadd(MILLISECOND,-2,dateadd(month, datediff(month, 0, getdate()), 0))
And you get 2020-01-31 23:59:59.997
To get last second of current month use:
SELECT DATEADD(MILLISECOND, -10, CAST (EOMONTH(GETDATE()) AS DATETIME))
and you get:
2021-12-30 23:59:59.000
Explanation: takes begin of next month (2021-12-31) and convert to datetime (2021-12-31 00:00:00.000) then takes 1 second to get (2021-12-30 23:59:59.000)

Why DATEDIFF in SQL Server returning error while interval is set to second

While running following query
select DATEDIFF(SECOND, 0, DATEADD(SECOND, -1, '2014-04-11 23:52'))
I am getting following error message, no matter whatever the date I provide to it.
The datediff function resulted in an overflow. The number of dateparts separating two date/time instances is too large. Try to use datediff with a less precise datepart.
Datediff takes these parameters: interval, starting_date, ending_date, so your SELECT is trying to find difference in seconds between server default for starting_date and your date.
When you specified 0 as starting_date, MS SQL replaced it with '1900-01-01 00:00'. The returned seconds where ~3606249060, but the DATEDIFF returns int, and the seconds returned where larger than datatype int could handle.
It works fine if you specify minute instead of second, because it'll return 60104151, which is int
You could use similar select to find difference in seconds between now and your defined date:
select DATEDIFF(SECOND, GETDATE(), DATEADD(SECOND,-1,'2014-04-11 23:52'))
If you put your hard-coded date as starting_date parameter, then you'll get -1 second difference (due to DATEADD you've used):
select DATEDIFF(SECOND, '2014-04-11 23:52', DATEADD(SECOND,-1,'2014-04-11 23:52'))
According to this DateDiff is not working because difference between 2 dates in Interval of Seconds is more than 68 years.
First argument in DateDiff as 0 means it is '1900-01-01 00:00:00.000', and difference with '2014-04-11 23:52' is more than 68 years.
Your current query results in a bigint, DATEDIFF can only return an integer. That is why you get overflow
3597523200 is the seconds between year 1900 and 2014
Try this instead:
SELECT 3597523200
+ DATEDIFF(S, '2014', DATEADD(SECOND,-1,'2014-04-11 23:52'))
This would be the same as:
SELECT CAST(DATEDIFF(S, '1900', '1950') AS BIGINT)
+ DATEDIFF(S, '1950', '2014')
+ DATEDIFF(S, '2014', DATEADD(SECOND,-1,'2014-04-11 23:52'))

Resources