SQL Server 2016 突然产生 Error:402,State:1,Class:16 用 DATETIME/DATE/TIME 算法更新 sproc,这在之前很好

Ast*_*tor 4 sql-server t-sql datetime date sql-server-2016

在其中一个存储过程中,我需要捕获实时事件的持续时间。保证永远不会超过 24 小时,因此结果列的数据类型选择为TIME(3)(映射到TimeSpan应用程序端)。计算过程如下:

update tbEvent set tWait= @dtEvent - dEvent - tEvent [where idEvent = @idEvent]
Run Code Online (Sandbox Code Playgroud)

此处dEventtEvent先前设置为dtEvent(原始事件@dtEvent的时刻)的日期和时间部分,并且是后续关闭事件的时刻。因此,我从 DATETIME 变量中减去 DATE 和 TIME 列值,并将结果填充到 TIME 列中。

[合理吗?] 预期行为: SQL 将每个表达式项隐式转换为 DATETIME,执行减法,然后通过简单地删除 DATE 部分将结果隐式转换为 TIME这一直有效! 嗯, - 到目前为止..


一个更新脚本突然在 SQL 2016 (13.0.1742.0) 上产生了一个错误

System.Data.SqlClient.SqlException (0x80131904):
    The data types datetime and date are incompatible in the subtract operator.
    Error Number:402,State:1,Class:16
Run Code Online (Sandbox Code Playgroud)

但是相同的脚本在 2008 R2 (10.50.2500.0) 和 2019 (15.0.2080.9) 上运行良好!

这个存储过程已经用这段代码更新了很长一段时间(几年),对它的后续修改(用那个代码)已经多次成功地应用于所有这些登台服务器。是什么赋予了?


发现的第一个有希望且中肯的解释是这篇文章(从 2013 年开始),它指出从 SQL Server 2012 开始,Microsoft 不允许在没有显式转换的情况下从 DATETIME 中添加/减去 TIME(尽管没有给出直接参考) . MS 自己的文档(DATETIMEDATETIME)甚至没有提到直接算术运算。

正如建议的那样,解决方案很简单 - 使用显式转换为 DATETIME(适用于所有版本):

update tbEvent set tWait= @dtEvent - cast(dEvent as datetime) - cast(tEvent as datetime)
Run Code Online (Sandbox Code Playgroud)

我没有 2012 年或 2014 年和 2017 年的版本来进行完整检查。但我确实有这些问题:

  1. 为什么该错误未在 2019 年显示?MS 是否回到了这种行为并允许“简单的时间算术”?
  2. 为什么在 2016 年第一次将上述表达式添加到 sproc(大约 2 年前)时,该错误没有出现?
  3. 为什么不需要将 DATETIME 值显式转换到 TIME 列中?

QA 团队表示,2016 年盒子上没有最近的 SQL Server 更新。

2021 年 8 月 27 日:更新
Hannah 的答案是正确的(涵盖 Q#1,2):
不久前,2016 年 box DB 的兼容性级别设置为 130(即 2016 年)。
初始值为 100 (2008 R2),这就是为什么我们的 2019 盒子没有触发错误的原因。

Q#3 保持打开状态。 我相信它是相关的,并希望了解完整性

Han*_*non 10

正如此简单测试所证明的那样,该错误确实发生在 SQL Server 2019 上。

让我们先检查服务器版本:

SELECT [SQL Server Version] = @@VERSION;
Run Code Online (Sandbox Code Playgroud)

结果:

SQL Server 版本
Microsoft SQL Server 2019 (RTM-CU12) (KB5004524) - 15.0.4153.1 (X64)
2021 年 7 月 19 日 15:37:34
版权所有 (C) 2019 微软公司
Linux (Red Hat Enterprise Linux) 上的开发人员版(64 位)

测试代码:

DECLARE @dt datetime = GETDATE();
DECLARE @d date = GETDATE();

SELECT [datetime] = @dt
    , [date] = @d;
Run Code Online (Sandbox Code Playgroud)

结果:

约会时间 日期
2021-08-19 16:12:10.770 2021-08-19

错误行为:

DECLARE @dt datetime = GETDATE();
DECLARE @d date = GETDATE();

SELECT @dt - @d;
Run Code Online (Sandbox Code Playgroud)

和错误:

消息 402,级别 16,状态 1,第 7 行
数据类型 datetime 和 date 在减法运算符中不兼容。

如果我在 SQL Server 2019 中创建兼容级别为 100(即 SQL Server 2008 R2)的数据库,则代码运行良好:

CREATE DATABASE date_test;
ALTER DATABASE date_test SET COMPATIBILITY_LEVEL = 100;

USE date_test;
DECLARE @dt datetime = GETDATE();
DECLARE @d date = GETDATE();

SELECT @dt - @d;
Run Code Online (Sandbox Code Playgroud)

结果:

(无列名)
1900-01-01 16:15:21.430

因此,似乎有人更改了数据库的兼容性级别。用这个语句检查一下:

SELECT d.name
    , d.compatibility_level
FROM sys.databases d
WHERE d.database_id = DB_ID();
Run Code Online (Sandbox Code Playgroud)

结果:

姓名 兼容性_级别
日期_测试 100

一个datetime值可以隐式转换为一个time值,因为SQL Server有内置的隐式转换代码,要做到这一点,因为有不明确没有机会时,只需丢弃值的日期部分。查看图表的隐式转换,显示可以隐式转换的内容以及需要显式转换语句的内容。