为什么SQL Server会改变操作顺序和装箱方式呢?

Ric*_*ell 6 sql sql-server sql-server-2014

四个简单的SELECT语句:

SELECT 33883.50 * -1;
SELECT 33883.50 / -1.05;
SELECT 33883.50 * -1 / 1.05;
SELECT (33883.50 * -1) / 1.05;
Run Code Online (Sandbox Code Playgroud)

但结果并不如我所料:

-33883.50
-32270.000000
-32269.96773000
-32270.000000
Run Code Online (Sandbox Code Playgroud)

第三个结果似乎是值得怀疑的结果.我可以看到发生了什么,首先SQL Server评估这个:

SELECT -1 / 1.05;
Run Code Online (Sandbox Code Playgroud)

得到答案:

-0.952380
Run Code Online (Sandbox Code Playgroud)

然后它接受该答案并使用它来执行此计算:

SELECT 33883.50 * -0.952380;
Run Code Online (Sandbox Code Playgroud)

得到(错误的)答案:

-32269.96773000
Run Code Online (Sandbox Code Playgroud)

但为什么这样做呢?

Vla*_*nov 3

在你的例子中

33883.50 * -1 / 1.05
Run Code Online (Sandbox Code Playgroud)

被评估为

33883.50 * (-1 / 1.05) 
Run Code Online (Sandbox Code Playgroud)

代替

(33883.50 * -1) / 1.05
Run Code Online (Sandbox Code Playgroud)

这会导致精度损失。

我用它玩了一下。我使用 SQL Sentry Plan Explorer 来查看 SQL Server 如何计算表达式的详细信息。例如,

2 * 3 * -4 * 5 * 6 
Run Code Online (Sandbox Code Playgroud)

被评估为

((2)*(3)) * ( -((4)*(5))*(6))
Run Code Online (Sandbox Code Playgroud)

我会这样解释。在 T-SQL 中,一元减法的优先级与减法相同,低于乘法。是的,

当表达式中的两个运算符具有相同的运算符优先级时,将根据它们在表达式中的位置从左到右计算它们。

,但这里我们有一个混合具有不同优先级的运算符的表达式,并且解析器严格遵循这些优先级。乘法必须先进行,因此它4 * 5 * 6首先进行计算,然后对结果应用一元减法。

通常(例如在 C++ 中)一元减号具有更高的优先级(如按位 NOT),并且此类表达式会按预期进行解析和计算。他们应该将一元减/加设置为与 T-SQL 中的按位 NOT 相同的最高优先级,但他们没有,这就是结果。所以,这不是一个错误,而是一个糟糕的设计决策。它甚至被记录下来,尽管相当模糊。

当您提到 Oracle 时,同一示例在 Oracle 中的工作方式与在 SQL Server 中的工作方式不同:

  • Oracle对于运算符优先级的规则可能与 SQL Server 不同。所需要做的就是使一元减去最高优先级,因为它应该是这样。
  • 在使用类型计算表达式时,Oracle 可能有不同的规则来确定结果精度和小数位数decimal
  • Oracle 可能有不同的舍入中间结果的规则。SQL Server “在将数字转换为小数或具有较低精度和小数位数的数值时使用舍入”。
  • Oracle 可能对这些类型的表达式使用完全不同的类型,而不是decimal. 在SQL Server中,“带有小数点的常量会使用必要的最小精度和小数位数自动转换为数值数据值。例如,常量 12.345 会转换为精度为 5、小数位数为 3 的数值。 ”
  • 甚至 Oracle 中的定义也decimal可能不同。即使在SQL Server中,“数字和小数数据类型的默认最大精度也是 38。在早期版本的 SQL Server 中,默认最大值是 28”。