SQL DATEDIFF coercion differences between databases on same SQL instance? - sql-server

I have a data coercion mystery. I am seeing two different behaviors for the same query, and I cannot understand why.
This is an extract of the relevant part of the query in question, with fixed values. The first value represents "today" in our query, and was set up with the same data type with an explicit CAST:
-- edited to change dates to ISO 8601 literal format to avoid ambiguity
SELECT DATEDIFF(dd,CAST('2014-03-24' AS SmallDateTime),'0001-01-01')
ISO 8601 date literal format citation: https://msdn.microsoft.com/en-us/library/ms187819.aspx
We have two different databases on the same SQL server instance.
One of them returns zero rows, as you would expect.
Server one returns an error about the date range of the '1/1/0001':
Msg 242, Level 16, State 3, Line 1
The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.
Server two returns a value that looks ballpark correct:
-735315
The problematic date is almost certainly '1/1/0001' and it fails as I expect for a default datetime, as it is below the minimum SQL datetime of Jan 1, 1753 (https://msdn.microsoft.com/en-us/library/ms187819.aspx).
According to the MSDN page for datediff (https://msdn.microsoft.com/en-US/library/ms189794(v=SQL.105).aspx), it can accept the following values:
startdate is an expression that can be resolved to a time, date, smalldatetime, datetime, datetime2, or datetimeoffset
Cast results for each of these are identical between servers, and are listed here:
SELECT CAST('0001-01-01' As time) -- works: 00:00:00.0000000
SELECT CAST('0001-01-01' As date) -- works: 0001-01-01
SELECT CAST('0001-01-01' As smalldatetime) -- error: The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value.
SELECT CAST('0001-01-01' As DateTime) -- error: The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.
SELECT CAST('0001-01-01' As datetime2) -- works: 0001-01-01 00:00:00.0000000
SELECT CAST('0001-01-01' As datetimeoffset) -- works: 0001-01-01 00:00:00.0000000 +00:00
The error explicitly calls out a failed DateTime conversion, so that appears to be the coercion of choice on database one.
It seems database two uses a different coercion that succeeds and does the datediff math correctly.
Because both of these databases are on the same SQL instance, I am ruling out instance settings.
Here are a few database settings we thought to check, and they appear identical between the two databases as well (checked in SQL Server Management Studio):
Database Collation (should be per server, but included for clarity):
(database) > Right Click > Properties > General > Maintenance > Collation
Database one: SQL_Latin1_General_CP1_CI_AS
Database two: SQL_Latin1_General_CP1_CI_AS
Date Correlation Optimization Enabled:
(database) > Right Click > Properties > Options > Misc. > Date Correlation Optimization Enabled
Database one: False
Database two: False
Two Digit Year Cutoff:
(database) > Right Click > Properties > Options > Containment > Two Digit Year Cutoff
Database one: 2049
Database two: 2049
User options date format
DBCC USEROPTIONS
Database one, dateformat: mdy
Database two, dateformat: mdy
(other settings appear identical)
I'm happy to provide other settings, or test query results, let me know what you'd like to see.
Why are the two databases behaving differently for this identical query? Why does it appear that the coercion chosen is different?
Edits:
Converted query to ISO date literals to avoid any ambiguity on formatting. Still seeing the same behavior.
Added DBCC USEROPTOINS check for dateformat, both mdy

https://dba.stackexchange.com/questions/96101/sql-datediff-coercion-differences-between-databases-on-same-sql-instance
As answered by Aaron Bertrand on the DBA stack exchange site, this was rooted in compatibility levels.
SELECT compatibility_level
FROM sys.databases WHERE name = 'FirstDatabase'
90
VS
SELECT compatibility_level
FROM sys.databases WHERE name = 'SecondDatabase'
110
Aaron has an excellent write up of why in this question:
https://dba.stackexchange.com/questions/44908/what-is-the-actual-behavior-of-compatibility-level-80
See Conversions involving new date/time types
The higher compatibility level likely means DateDiff uses a DateTime2 or other 'wider' data type and works. At 90 or below, it probably uses the old DateTime, and thus has conversion errors.
Thanks to Tab Alleman for the suggestion to cross post.

Related

SQL Server stored procedure passing date parameters

ALTER PROCEDURE [dbo].[AccountsData]
#Start_Date datetime,
#End_Date datetime
AS
BEGIN
SET NOCOUNT ON;
SELECT
a.Customer_AC_No, a.Customer_Name, a.Product_Code,
a.Product_Description, a.Sales_Person, c.HSID
FROM
(SELECT
Customer_AC_No, Customer_Name, Product_Code,
Product_Description, Sales_Person
FROM
View_Sales_Details
WHERE
([Week Ending] >=' #Start_Date') AND ([Sales Value] > 0)
GROUP BY
Customer_AC_No, Product_Code, Product_Description,
Customer_Name, Sales_Person) AS a
LEFT JOIN
(SELECT
Customer_AC_No, Product_Code
FROM
View_Sales_Details
WHERE
([Week Ending] >= '#End_Date') AND ([Sales Value] > 0)
GROUP BY
Customer_AC_No, Product_Code) AS b ON a.Customer_AC_No = b.Customer_AC_No
AND a.Product_Code = b.Product_Code
INNER JOIN
Hubspot.dbo.View_BPA_Cust_Data AS c ON a.Customer_AC_No = c.CustomerNo COLLATE Latin1_General_100_CI_AS
WHERE
b.Customer_AC_No IS NULL
ORDER BY
a.Customer_AC_No, a.Product_Code ASC
END
I am trying to pass the above date parameters to the SQL Server stored procedure above, but I keep getting this error
Msg 241, Level 16, State 1, Procedure AccountsData, Line 52
Conversion failed when converting date and/or time from character string.
Can some one please help. WeekEnding date is also in datetime format. Thanks
There are many formats supported by SQL Server for specifying a date&time as a string literal - see the MSDN Books Online on CAST and CONVERT. Most of those formats are dependent on what settings you have - therefore, these settings might work some times - and sometimes not.
The way to solve this is to use the (slightly adapted) ISO-8601 date format that is supported by SQL Server - this format works always - regardless of your SQL Server language and dateformat settings.
The ISO-8601 format is supported by SQL Server comes in two flavors:
YYYYMMDD for just dates (no time portion); note here: no dashes!, that's very important! YYYY-MM-DD is NOT independent of the dateformat settings in your SQL Server and will NOT work in all situations!
or:
YYYY-MM-DDTHH:MM:SS for dates and times - note here: this format has dashes (but they can be omitted), and a fixed T as delimiter between the date and time portion of your DATETIME.
This is valid for SQL Server 2000 and newer.
If you use SQL Server 2008 or newer and the DATE datatype (only DATE - not DATETIME!), then you can indeed also use the YYYY-MM-DD format and that will work, too, with any settings in your SQL Server.
Don't ask me why this whole topic is so tricky and somewhat confusing - that's just the way it is. But with the YYYYMMDD format, you should be fine for any version of SQL Server and for any language and dateformat setting in your SQL Server.
The recommendation for SQL Server 2008 and newer is to use DATE if you only need the date portion, and DATETIME2(n) when you need both date and time. You should try to start phasing out the DATETIME datatype if ever possible.
So in your case, either switch to using DATE as your parameter datatype (since you obviously don't use the time portion):
ALTER PROCEDURE [dbo].[AccountsData]
#Start_Date DATE,
#End_Date DATE
and then execute your stored procedure like this:
EXEC [dbo].[AccountsData] '2019-05-11', '2020-06-10'
or use this format to support DATETIME if you insist on keeping that:
EXEC [dbo].[AccountsData] '2019-05-11T00:00:00', '2020-06-10T00:00:00'
Your have an error in your select. You have change your query like this
DATEPART(wk, Ending)>=DATEPART(wk, #Start_Date) AND ([Sales Value] > 0)
DATEPART(wk, Ending)>=DATEPART(wk, #End_Date) AND ([Sales Value] > 0)

SQL getdate() - not the same in one statement

The getDate() statement always returns the same value anywhere in one statement.
However, in one SQL Server 2017, I'm seeing otherwise.
To set this up, create a table and put two rows into it:
CREATE TABLE Test
(
TestDate datetime2(0) NULL,
OtherValue varchar(5) NULL
)
INSERT INTO Test (OtherValue) VALUES ('x')
INSERT INTO Test (OtherValue) VALUES ('x')
Then run this query a number of times:
SELECT
CASE
WHEN GETDATE() < COALESCE(TestDate, GETDATE())
THEN 'less'
WHEN GETDATE() > COALESCE(TestDate, GETDATE())
THEN 'greater'
ELSE 'same'
END [Compare]
FROM
Test
Both rows always return matching results.
When I do this in SQL Server 2008 R2 (v10.50) and other SQL Server 2017 machines, the result is always 'same'.
However, on one of my SQL Server 2017 instances, it varies randomly between 'same', 'less' and 'greater':
Why is this happening? Is there a server setting that can cause this?
Edit:
Using SYSDATETIME in place of GETDATE works as expected on the 'bad' server, always returning 'same'.
Edit #2:
If I test GETDATE as above on a column defined as DATETIME (which is what GETDATE() generates), then it works as expected. So it seems to be related to converting between DATETIME and DATETIME2.
Interesting enough question.
The behaviour in your example can be explaned by the following:
Since SQL Server 2016, datetime rounding have been changed. In short: since 2016 SQL Server, value doesn't round before comparison and comparison executes with raw value. Before 2016 SQL Server, value is rounded and then compare.
By default, comparison datetime and datetime2 performs with conversion datetime to datetime2(7). You can see that in execution plan.
datetime variable with 3 at the end - for example .003 - gets converted in .0033333. 007 gets converted in .0066667.
And the most interest part: nanoseconds. During comparison SQL Server uses 8 (or more!) digits in fractional part. I just show two examples to explane.
DECLARE #DateTime datetime = '2016-01-01T00:00:00.003';
DECLARE #DateTime2 datetime2(7) = #DateTime;
select datepart(NANOSECOND,#DateTime) as "DateTimeRes",
datepart(nanosecond,#DateTime2) as "DateTime2Res"
go
DECLARE #DateTime datetime = '2016-01-01T00:00:00.007';
DECLARE #DateTime2 datetime2(7) = #DateTime;
select datepart(NANOSECOND,#DateTime) as "DateTimeRes",
datepart(nanosecond,#DateTime2) as "DateTime2Res"
Results:
+-------------+--------------+
| DateTimeRes | DateTime2Res |
+-------------+--------------+
| 3333333 | 3333300 |
+-------------+--------------+
+-------------+--------------+
| DateTimeRes | DateTime2Res |
+-------------+--------------+
| 6666666 | 6666670 |
+-------------+--------------+
I took it all from this article.
Also, there is a similar question on SO.
I believe this behaviour is independent of your server repformance (virtual machine or etc).
Good luck!
Turns out the behaviour of getdate changed from SQL 2000 to SQL 2005.
See https://stackoverflow.com/a/3620119/32429 explaining the old behaviour:
In practice, GETDATE() is only evaluated once for each expression
where it is used -- at execution time rather than compile time.
However, Microsoft puts rand() and getdate() into a special category,
called non-deterministic runtime constant functions.
and the following discussion:
In SQL 2000 if you did something like this
INSERT INTO tbl (fields, LOADDATE) SELECT fields, GETDATE() FROM tblb
you would get the same date/time for all records inserted.
This same command In SQL 2005, reruns GETDATE() for every single
record selected from tblb and gives you potentially unique values for
each record. Also causes HUGE performance problems if you are
inserting say, 17 million rows at a time.
This has caused me many a headache, as we use this code to do batch
date/times in many tables. This was a very simple way to back out a
"batch" of transactions, because everything had the same date/time.
Now in 2005, that is not true.

Error in SQL Server Agent: "the conversation of a varchar data type to a datetime data type resulted in an out-of-range.""

I created a small script that works in SQL Server Management Studio, then I created a SQL Server Agent job with that same script; but after I run it, I get an error:
The conversation of a varchar data type to a datetime data type resulted in an out-of-range
I fixed the problem by change the format of date to ISO 8601, but I don't release how my first script works on SSMS and not in SQL Server Agent.
First script:
declare #teste datetime
set #teste = '31/12/2099 00:00:00'
select #teste
Fix error:
declare #teste datetime
set #teste = '20991231 00:00:00'
select #teste
This is one reason why using unambiguous formats are so important when using date(time) datatypes. The only formats that an unambgious in SQL Server, regardless of language and datatype are the formats yyyyMMdd and yyyy-MM-ddThh:mm:ss.
For the date '31/12/2099 00:00:00' and the language your Login is using it appears that SQL Server is interpreting the value as the 12th day, of the 31st month, of the year 2099. There aren't 31 months in the year, and hence the error. (DB<>Fiddle). It's worth noting that date (and the other "new" datetime data types) behave differently and also can unambiguously understand the format yyyy-MM-dd; notice in this DB<>fiddle the difference in the values for the datetime values simply due to the language setting.
As you can see, the solution is to use a unambiguous format. So, as you're using a date and time, I would suggest the string '2099-12-31T00:00:00'.

The conversion of a datetimeoffset data type to a datetime data type resulted in an out-of-range value

Using SQL Server 2008.I have a table called User which has a column LastLogindata with datetimeoffset datatype
The following query works on production server but not on replication server.
select top 10 CAST(LastLoginDate AS DATETIME) from User.
I am getting the following error.The conversion of a datetimeoffset data type to a datetime data type resulted in an out-of-range value.
Thanks
Check the LastLoginDate columns value like this '0001-01-01' or '0001/01/01'.
If u have means get this error ..
Try this one
select top 10 CAST(CASE when cast(LastLoginDate as varchar) = '0001-01-01 00:00:00'
THEN NULL ELSE GETDATE() end AS DATETIME) from User
If a field in database is of type datetimeoffset type, then it should contain date within range 0001-01-01 through 9999-12-31. I think the issue is the date inside your database.
Please check the official link of SQL server Click Here
I solved it this way. I had an nvarchar(max) column casted as an xml and used the T-SQL expression ISDATE() to exclude the bad rows in the where clause.
where cast(DataObject as xml).value('(/DataObjects/#LastLoginDate)[1]', 'varchar(10)') is not null
and isdate(cast(DataObject as xml).value('(/DataObjects/#LastLoginDate)[1]', 'varchar(10)')) = 1
On SQL Server 2016, I used:
CONVERT(DATETIME2, DateValueColumn)
This worked for values that were giving errors when trying to convert to DATETIME, giving the message "The conversion of a datetimeoffset data type to a datetime data type resulted in an out-of-range value." The offending values had dates of 0001-01-01, as a previous answer has mentioned.
Not sure if this works on SQL Server 2008 though.

Is March 27th, 2012 of significance to SQL Server in a Varchar to Datetime conversion?

I have a stored procedure that takes a datetime parameter which is passed in as a string. Such as this:
Procedure:
CREATE PROCEDURE [dbo].[MyFancySP]
#MyStartDate datetime = NULL,
#MyEndDate datetime = NULL
AS
....
Call:
EXEC [dbo].[MyFancySP]
#MyStartDate = N'01/01/2012',
#MyEndDate = N'03/01/2012'
The stored procedure has been working like this forever. Now, here's the interesting part. As soon as I change the date to 03/27/2012 or past it, I get the following error: The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.
The only place in the stored procedure where the dates are used is in the where clause. In case it has to do with it, I'll copy it in here as well:
WHERE
((#MyStartDate IS NOT NULL AND #MyEndDate IS NOT NULL
AND d.SomeDate >= #MyStartDate AND d.SomeDate <= #MyEndDate)
OR #MyStartDate IS NULL AND #MyEndDate IS NULL)
Any ideas why I'm getting the out of range exception on March 27th or beyond? This is running on SQL Server 2008 R2 by the way.
Thanks!
Execute the following on each new database connection.
SET DATEFORMAT DMY
After you do this, your problem should disappear. I suspect that your issue is conditioned by a combination of server locale, and whether the day-of-month is 13th to 31st or not.
Not only that you see the error, you may also be fetching data for incorrect periods without noticing; other layers of your software may be correcting for that, but maybe only in some cases.
What type is d.SomeDate? Is it a NVARCHAR by any chance? That would explain it, as the WHERE clause would contain in such case an implicit conversion that the rules of Data Type Precedence state that should occur as a DATETIME. The apparent randomness of the error occurs due to the simple fact that the query scans rows that have invalid dates in the d.SomeDate field. In such a case you're dealing with data purity issues and you should fix your tables, preferably by making the column a DATETIME.
In addition:
always use the canonical date format YYYYMMMDD (no delimiters) in string representation: EXEC [dbo].[MyFancySP]
N'20120101', N'20120301';. This format is independent of the host locale, DATEFORMAT and LANGUAGE settings, .
Read Dynamic Search Conditions in T-SQL. WHERE column=#value or #value is null stops query performance optimizations dead on its track. Read the article.

Resources