11

I want to find any records greater than the time supplied in my predicate, which is coming from a C# web application in which there is only type DateTime. However, SQL Server is returning a row that is equal to the supplied predicate value. Why is it doing this? The ShipDateTimeUTC column is a datetime data type.

DECLARE @p__linq__1 datetime2(7) = '2024-08-07 00:14:11.3630000';

SELECT 
    [OrderHeaderId],
    Id,
    [ShipDateTimeUTC]
FROM  
    [dbo].[ShippingContainerHeader]
WHERE 
    [ShipDateTimeUTC] > @p__linq__1;

Here are the results where you can see the ShipDateTimeUTC matches the predicate value. I don't want it to be included. Why is it being included in the results?

sql results

0

2 Answers 2

17

You will get this behaviour if ShipDateTimeUTC is a datetime datatype.

datetime2(7) has higher datatype precedence than datetime so you would expect the column to be implicitly cast to datetime2(7)

There is an annoying (†) "by design" behaviour that when casting to datetime2(7) values such as 2024-08-07 00:14:11.363 get converted to 2024-08-07 00:14:11.3633333 (and 2024-08-07 00:14:11.367 would be converted to 2024-08-07 00:14:11.3666667)

After this implicit conversion 2024-08-07 00:14:11.3633333 is larger than 2024-08-07 00:14:11.3630000.

You should change the datatype of @p__linq__1 to datetime to match the column as implicit conversions in the WHERE clause should be avoided anyway.

I initially thought that if for some reason you are stuck with datetime2(7) then you would need to change the value to use 2024-08-07 00:14:11.3633333 to take account of this. However the implicit comparison behaviour with mixed datatypes actually operates differently than with explicitly cast data types. Fiddle. This looks like a bug to me so reported here.

All of the below use an index seek despite the CASTs

DECLARE @T TABLE (DT DATETIME PRIMARY KEY)

INSERT @T values ('2024-08-07 00:14:11.363')

--Returns 0 rows as expected from above
SELECT *
FROM @T
WHERE DT = CAST('2024-08-07 00:14:11.3630000' AS datetime2(7))

--Returns 0 rows - unexpected
SELECT *
FROM @T
WHERE DT = CAST('2024-08-07 00:14:11.3633333' AS datetime2(7))

--Returns 1 rows so explicit cast to datetime2 helps
SELECT *
FROM @T
WHERE CAST(DT AS datetime2(7)) = CAST('2024-08-07 00:14:11.3633333' AS datetime2(7))

--Returns 1 rows - not sure why casting the column to the datatype it already has helps!
SELECT *
FROM @T
WHERE CAST(DT AS datetime) = CAST('2024-08-07 00:14:11.3633333' AS datetime2(7))

† The rationale for this behaviour (new under compat level 130) is

Improved precision when you convert to date/time types with higher precision. Be aware that datetime values are stored as ticks that represent 1/300th of a second. The newer time and datetime2 types store a discrete number of digits, where the number of digits matches the precision.

I have checked SQL Server 2000 Books online and that does say for datetime

Date and time data from January 1, 1753 through December 31, 9999, to an accuracy of one three-hundredth of a second (equivalent to 3.33 milliseconds or 0.00333 seconds). Values are rounded to increments of .000, .003, or .007 seconds

So this behaviour is consistent with what was always documented even though it can cause potentially unexpected results.

4
  • In my actual scenario, the predicate value is coming from a C# web app which retrieves this sql DateTime from the same DB column, then it must be rounding to .3630000, where as SQL Server is rounding to .3633333. So when you include a predicate value that is of datetime2(7), SQL Server will first cast the DB values to datetime2(7), then apply the predicate condition instead of the opposite in casting the predicate down to match the column data type?
    – crichavin
    Commented Aug 7 at 15:43
  • 1
    @crichavin - yes. datetime2 has higher datatype precedence than datetime so the cast happens that way round. learn.microsoft.com/en-us/sql/t-sql/data-types/… - In general this is also bad news as it can prevent an index seek happening on the column (though looks like there is some logic to still allow seeks in this case). You ideally need to get the parameter datatype to be the same as the column Commented Aug 7 at 15:49
  • 1
    When ShipDateTimeUTC is indexed there is actually no CAST operator shown in the execution plan. It just looks like a normal seek with a predicate comparing to a datetime2(7) value. so in practice maybe this is implemented by some comparator logic between the two datatypes that maintain the same semantics as CAST would (and not treating the 2024-08-07 00:14:11.363 datetime as equal to the 2024-08-07 00:14:11.3630000 datetime2) Commented Aug 7 at 16:12
  • 1
    @crichavin - My expectation about the CAST looks wrong anyway, see x.com/SQL_Kiwi/article/1822670731291050176 for more details. So basically, you need to get the parameter datatype and column datatype the same and not mix the two as in the best case there is no datetime2 value that compares equal to the datetime value 2024-08-07 00:14:11.363 and seems it is a bug that this does happen in one case! Commented Aug 13 at 10:39
6

Mixing datatypes is a bad idea. If the database type is datetime then the variable should be datetime.

The problem you have is the implicit cast of datetime to datetime2, for ShipDateTimeUTC, will produce 2024-08-07 00:14:11.3633333 which is greater than 2024-08-07 00:14:11.3630000.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.