I'm working with a legacy database (running on SQL Server 2019) which uses datetime for columns storing timestamp information and I'm trying to get a better handle on what SQL Server is doing under the hood when comparing a datetime value to a datetimeoffset value.
We're attempting to migrate some code to a new platform which will use the Microsoft SQL Server JDBC drivers. In order to run the database in Compatibility Level 150 (for SQL Server 2019), the Microsoft JDBC driver will translate all timestamps into datetimeoffset parameters. This is causing precision issues when the datetime value ends in either 3 or 7.
I've been reading through other Stack Overflow response (such as SQL Server behaviour change while using datetimeoffset & [x][2]) and understand how SQL Server coerces a datetime value into datetimeoffset, but that coercion does not appear to be exactly what is happening in a WHERE clause on a SQL Statement.
For example, take the following SQL:
declare @tmp table (id int identity primary key, createDate datetime not null)
insert into
@tmp
(createDate)
values
('2021-09-27 18:36:01.930')
, ('2021-09-27 18:36:01.933')
, ('2021-09-27 18:36:01.937')
declare @input datetimeoffset = '2021-09-27 18:36:01.937'
select
*
from
@tmp
where
createDate = @input
When running this query, you get no matches.
I know that if you run select cast('2021-09-27 18:36:01.937' as datetimeoffset) you get 2021-09-27 18:36:01.9370000 +00:00 and if you run select cast(cast('2021-09-27 18:36:01.937' as datetime) as datetimeoffset) you get 2021-09-27 18:36:01.9366667 +00:00, so that potentially make sense on why the values might not match if the createDate column is being implicitly converted to a datetimeoffset. However, when I look at the execution plan this does not appear to be the case and if I change the code to:
declare @tmp table (id int identity primary key, createDate datetime not null)
insert into
@tmp
(createDate)
values
('2021-09-27 18:36:01.930')
, ('2021-09-27 18:36:01.933')
, ('2021-09-27 18:36:01.937')
declare @input datetimeoffset = '2021-09-27 18:36:01.9366667 +00:00'
select
*
from
@tmp
where
createDate = @input
I still do not get a match, which would appear to mean that createDate is not being coerced into a datetimeoffset datatype for comparison. Now if I explicitly cast the datetimeoffset to datetime, I do get a match:
declare @tmp table (id int identity primary key, createDate datetime not null)
insert into
@tmp
(createDate)
values
('2021-09-27 18:36:01.930')
, ('2021-09-27 18:36:01.933')
, ('2021-09-27 18:36:01.937')
declare @input datetimeoffset = '2021-09-27 18:36:01.937'
select
*
from
@tmp
where
createDate = cast(@input as datetime)
Or if I try to find match the 2021-09-27 18:36:01.930 value, I do not need to explicitly cast the value:
declare @tmp table (id int identity primary key, createDate datetime not null)
insert into
@tmp
(createDate)
values
('2021-09-27 18:36:01.930')
, ('2021-09-27 18:36:01.933')
, ('2021-09-27 18:36:01.937')
declare @input datetimeoffset = '2021-09-27 18:36:01.930'
select
*
from
@tmp
where
createDate = @input
This tells me there is some kind of conversion going on, but I cannot figure out what's happening under the hood.
Can anyone help me understand what SQL Server is doing under the hood when it runs the following?
declare @tmp table (id int identity primary key, createDate datetime not null)
insert into
@tmp
(createDate)
values
('2021-09-27 18:36:01.930')
, ('2021-09-27 18:36:01.933')
, ('2021-09-27 18:36:01.937')
declare @input datetimeoffset = '2021-09-27 18:36:01.937'
select
*
from
@tmp
where
createDate = @input
I am trying to see if there is some way to format timestamp data so that all the existing code will not break. I could drop just drop the millisecond precision to the 1/100 (instead of 3.33ms precision), but I would like to keep as much precision as possible. I originally started down the path of converting the datetime values to datetime2, but due to the volume of data, indices, stats, etc. around the data converting everything would require a fair amount of downtime. Not to mention the code uses various datetime math tricks that would need to be refactored.
I know I could go through all the code and also add explicit conversions of the datetimeoffset to datetime as well, but some of the code is being generated by fluent SQL builders so doing that is not exactly straightforward either.
Lastly, I know I could convert the values to a varchar(23) field and allow SQL Server to do the implicit conversion that way, but I'd like to avoid that if possible.
So, if anyone can shed light on what SQL Server is doing internally to compare the datetime to datetimeoffset values, I would greatly appreciate it.
RANT — I really don't know why Microsoft changed SQL Server 2016 to convert values like
2021-09-27 18:36:01.937to2021-09-27 18:36:01.9366667when casting todatetimeoffsetanddatetime2. Especially since casting adatetimeoffset(3)ordatetime2(3)to adatetimeoffsetwould result in2021-09-27 18:36:01.9370000 +00:00. For example:select cast(cast('2021-09-27 18:36:01.937' as datetime) as datetimeoffset) as [datetime] , cast(cast('2021-09-27 18:36:01.937' as datetimeoffset(3)) as datetimeoffset) as [datetimeoffset(3)] , cast(cast('2021-09-27 18:36:01.937' as datetime2(3)) as datetimeoffset) as [datetime2(3)]Results in: | datetime | datetimeoffset(3) | datetime2(3) | |--|--|--| | 2021-09-27 18:36:01.9366667 +00:00 | 2021-09-27 18:36:01.9370000 +00:00 | 2021-09-27 18:36:01.9370000 +00:00 |
It would seem to be more consistent if they were all cast the same way. I know the change was supposedly made to improve precision accuracy, but my guess is it causes way more problems than it solves.