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.937
to2021-09-27 18:36:01.9366667
when casting todatetimeoffset
anddatetime2
. Especially since casting adatetimeoffset(3)
ordatetime2(3)
to adatetimeoffset
would 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.