sql-server - Microsoft SQL Server 如何比较查询语句中的 datetime 和 datetimeoffset 值?

标签 sql-server datetime jdbc

我正在使用一个遗留数据库(在 SQL Server 2019 上运行),它使用 datetime 作为存储时间戳信息的列,我正试图更好地处理 SQL Server 正在做什么将 datetime 值与 datetimeoffset 值进行比较时的引擎盖。

我们正在尝试将一些代码迁移到将使用 Microsoft SQL Server JDBC 驱动程序的新平台。为了在兼容级别 150(对于 SQL Server 2019)中运行数据库,Microsoft JDBC 驱动程序会将所有时间戳转换为 datetimeoffset 参数。当 datetime 值以 37 结尾时,这会导致精度问题。

我一直在阅读其他 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 数学技巧。

我知道我可以遍历所有代码,也可以添加从 datetimeoffsetdatetime 的显式转换,但是一些代码是由流畅的 SQL 生成的 build 者这样做也不是很简单。

最后,我知道我可以将值转换为 varchar(23) 字段并允许 SQL Server 以这种方式进行隐式转换,但我想尽可能避免这种情况。

因此,如果有人能阐明 SQL Server 内部正在做什么来比较 datetimedatetimeoffset 值,我将不胜感激。

RANT — I really don't know why Microsoft changed SQL Server 2016 to convert values like 2021-09-27 18:36:01.937 to 2021-09-27 18:36:01.9366667 when casting to datetimeoffset and datetime2. Especially since casting a datetimeoffset(3) or datetime2(3) to a datetimeoffset would result in 2021-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"方法更准确(正如我之前所说)。 datetime2021-09-27T18:36:01.937 不是 2021-09 -27T18:36:01.9370000~datetime 精确到 1/300 秒。这就是为什么 datetime 值都以 037 结尾的原因;后两个是 0.0033333~0.0066666~ 秒四舍五入到小数点后三位。因此,例如,当您将 datetime 值转换为 datetime2(7) 时,转换值尊重先前的准确性是完全合理的,因此 datetime2021-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/

相关文章:

sql-server - 如何在检查约束中使用递归 CTE?

sql - Inner Join 是否存在性能问题?

sql - 通过SQL查询获取特定数据库的所有表名?

c# - 如何根据日期时间和时区偏移获取 UTC 时间?

java - Maven - JDBC jar 文件的正确范围是什么?

java - 如何在没有java的情况下运行SQL命令?

java - 使用 JDBC 将用户定义的表类型传递给 SQL Server 存储过程

sql-server - 我可以仅使用 SQL 查询来完成此操作还是需要存储过程?

python - 使用 Python 将 CSV 文件中的 GMT 转换为 EST

python - 处理具有可怕日期时间数据的数据集