我正在使用一个遗留数据库(在 SQL Server 2019 上运行),它使用 datetime
作为存储时间戳信息的列,我正试图更好地处理 SQL Server 正在做什么将 datetime
值与 datetimeoffset
值进行比较时的引擎盖。
我们正在尝试将一些代码迁移到将使用 Microsoft SQL Server JDBC 驱动程序的新平台。为了在兼容级别 150(对于 SQL Server 2019)中运行数据库,Microsoft JDBC 驱动程序会将所有时间戳转换为 datetimeoffset
参数。当 datetime
值以 3
或 7
结尾时,这会导致精度问题。
我一直在阅读其他 Stack Overflow 响应(例如 SQL Server behaviour change while using datetimeoffset 和 [x][2])并了解 SQL Server 如何将 datetime
值强制转换为 datetimeoffset
,但这种强制转换似乎并不完全是 SQL 语句的 WHERE
子句中发生的事情。
例如,采用以下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
运行此查询时,您找不到任何匹配项。
我知道如果你运行 select cast('2021-09-27 18:36:01.937' as datetimeoffset)
你会得到 2021-09-27 18:36:01.9370000 + 00:00
如果你运行 select cast(cast('2021-09-27 18:36:01.937' as datetime) as datetimeoffset)
你会得到 2021-09- 27 18:36:01.9366667 +00:00
,这样可能就可以理解为什么如果 createDate
列正在隐式转换为 datetimeoffset
。但是,当我查看执行计划时,情况似乎并非如此,如果我将代码更改为:
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
我仍然没有得到匹配项,这似乎意味着 createDate
没有被强制转换为 datetimeoffset
数据类型以进行比较。现在,如果我明确地将 datetimeoffset
转换为 datetime
,我会得到一个匹配项:
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)
或者,如果我尝试找到与 2021-09-27 18:36:01.930
值匹配的值,则不需要显式转换该值:
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
这告诉我正在进行某种转换,但我无法弄清楚幕后发生了什么。
任何人都可以帮助我了解 SQL Server 在运行以下命令时在幕后做了什么吗?
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
我正在尝试查看是否有某种方法可以格式化时间戳数据,以便所有现有代码都不会中断。我可以将毫秒精度降低到 1/100(而不是 3.33 毫秒精度),但我想尽可能保持精度。我最初开始着手将 datetime
值转换为 datetime2
,但由于数据量、索引、统计信息等,围绕数据转换所有内容都需要一个相当多的停机时间。更不用说代码使用了各种需要重构的 datetime
数学技巧。
我知道我可以遍历所有代码,也可以添加从 datetimeoffset
到 datetime
的显式转换,但是一些代码是由流畅的 SQL 生成的 build 者这样做也不是很简单。
最后,我知道我可以将值转换为 varchar(23)
字段并允许 SQL Server 以这种方式进行隐式转换,但我想尽可能避免这种情况。
因此,如果有人能阐明 SQL Server 内部正在做什么来比较 datetime
和 datetimeoffset
值,我将不胜感激。
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.
最佳答案
好的,这里的问题是隐式转换。正如您所注意到的,当您将 datetime
转换为 datetimeoffset
recent 版本的 SQL Server (SQL Server 2016+) 时,该值为正确转换为精确到 1/300 秒。例如,时间 18:36:01.937
转换为 datetimeoffset(7)
(甚至 datetime2(7)
)将是 18:36:01.9366667
。 (旧版本的 SQL Server 不是这种情况,它会将值错误地转换为 18:36:01.9370000
。)
您将参数定义为 datetimeoffset
,没有精度(坏主意),因此默认为 datetimeoffset(7)
。因此,值 2021-09-27T18:36:01.937
变为 2021-09-27T18:36:01.9370000+00:00
。
此处的 datetime
值被隐式转换为 datetimeoffset
,因为后者具有更高的 Data Type Precedence。 .所以你的 3 个值变成:
2021-09-27T18:36:01.9300000+00:00
2021-09-27T18:36:01.9333333+00:00
2021-09-27T18:36:01.9366667+00:00
如您所见,这些都不是与您的变量 @input
相同的值,后者的值是 2021-09-27T18:36:01.970000+00:00
.
因此,有人会认为将@input
的精度定义为3
可以解决这个问题,可惜事实并非如此。似乎在幕后,SQL Server 在进行比较时仍然隐式地将 datetime
转换为 datetimeoffset(7)
。
因此解决方案是使用显式转换,这也在 documentation 中说明。 :
Under database compatibility level 130, implicit conversions from datetime to datetime2 data types show improved accuracy by accounting for the fractional milliseconds, resulting in different converted values, as seen in the example above. Use explicit casting to datetime2 datatype whenever a mixed comparison scenario between datetime and datetime2 datatypes exists. For more information, refer to this Microsoft Support Article.
由于您正在查询的表有一个datetime
,您希望将参数显式转换为一个datetime
,而不是将列转换为维护 SARGability:所以你想要的正确查询是:
DECLARE @tmp table (id int IDENTITY PRIMARY KEY,
createDate datetime NOT NULL);
INSERT INTO @tmp (createDate)
VALUES ('2021-09-27T18:36:01.930'),
('2021-09-27T18:36:01.933'),
('2021-09-27T18:36:01.937');
DECLARE @input datetimeoffset(3) = '2021-09-27T18:36:01.937';
SELECT createDate
FROM @tmp
WHERE createDate = CONVERT(datetime, @input);
如果反过来,您的列是 datetimeoffset
并且参数是 datetime
那么您可以将所述参数转换为 datetimeoffset(3 )
:
DECLARE @tmp table (id int IDENTITY PRIMARY KEY,
createDate datetimeoffset NOT NULL);
INSERT INTO @tmp (createDate)
VALUES ('2021-09-27T18:36:01.930'),
('2021-09-27T18:36:01.933'),
('2021-09-27T18:36:01.937');
DECLARE @input datetime = '2021-09-27T18:36:01.937';
SELECT createDate
FROM @tmp
WHERE createDate = CONVERT(datetimeoffset(3), @input);
解决你的“咆哮”:
因为"new"方法更准确(正如我之前所说)。 datetime
值 2021-09-27T18:36:01.937
不是 2021-09 -27T18:36:01.9370000~
。 datetime
精确到 1/300 秒。这就是为什么 datetime
值都以 0
、3
或 7
结尾的原因;后两个是 0.0033333~
和 0.0066666~
秒四舍五入到小数点后三位。因此,例如,当您将 datetime
值转换为 datetime2(7)
时,转换值尊重先前的准确性是完全合理的,因此 datetime
值 2021-09-27T18:36:01.937
转换为 datetime2(7)
值 2021-09-27T18:36 :01.9366667
(2021-09-27T18:36:01.9366666666~
四舍五入到 7 的精度)。
关于sql-server - Microsoft SQL Server 如何比较查询语句中的 datetime 和 datetimeoffset 值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69362779/