Nic*_*mas 11 sql-server-2008 sql-server datatypes
继续我最近玩大数字的趋势,我最近将一个错误归结为以下代码:
DECLARE @big_number DECIMAL(38,0) = '1' + REPLICATE(0, 37);
PRINT @big_number + 1;
PRINT @big_number - 1;
PRINT @big_number * 1;
PRINT @big_number / 1;
Run Code Online (Sandbox Code Playgroud)
我为此代码得到的输出是:
10000000000000000000000000000000000001
9999999999999999999999999999999999999
10000000000000000000000000000000000000
Msg 8115, Level 16, State 2, Line 6
Arithmetic overflow error converting expression to data type numeric.
Run Code Online (Sandbox Code Playgroud)
什么?
为什么前 3 个操作有效,而最后一个无效?如果@big_number显然可以存储 的输出,怎么会出现算术溢出错误@big_number / 1?
Nic*_*mas 18
让我们分解一下,仔细看看除法算术运算符的细节。这就是 MSDN 关于除法运算符的结果类型的说法:
结果类型
返回具有较高优先级的参数的数据类型。有关详细信息,请参阅数据类型优先级 (Transact-SQL)。
如果整数被除数除以整数除数,则结果是一个整数,该整数的任何小数部分都被截断。
我们知道这@big_number是一个DECIMAL. SQL Server 强制转换为什么数据类型1?它将其强制转换为INT. 我们可以在以下帮助下确认这一点SQL_VARIANT_PROPERTY():
SELECT
SQL_VARIANT_PROPERTY(1, 'BaseType') AS [BaseType] -- int
, SQL_VARIANT_PROPERTY(1, 'Precision') AS [Precision] -- 10
, SQL_VARIANT_PROPERTY(1, 'Scale') AS [Scale] -- 0
;
Run Code Online (Sandbox Code Playgroud)
对于踢球,我们还可以1用显式键入的值替换原始代码块中的 ,DECLARE @one INT = 1;并确认我们得到相同的结果。
所以我们有 aDECIMAL和 an INT。由于DECIMAL具有比更高的数据类型优先级INT,我们知道我们的除法的输出将被强制转换为DECIMAL。
那么问题出在哪里呢?
问题在于DECIMAL输出中的比例。以下是有关 SQL Server 如何确定从算术运算获得的结果的精度和小数位数的规则表:
Operation Result precision Result scale *
-------------------------------------------------------------------------------------------------
e1 + e2 max(s1, s2) + max(p1-s1, p2-s2) + 1 max(s1, s2)
e1 - e2 max(s1, s2) + max(p1-s1, p2-s2) + 1 max(s1, s2)
e1 * e2 p1 + p2 + 1 s1 + s2
e1 / e2 p1 - s1 + s2 + max(6, s1 + p2 + 1) max(6, s1 + p2 + 1)
e1 { UNION | EXCEPT | INTERSECT } e2 max(s1, s2) + max(p1-s1, p2-s2) max(s1, s2)
e1 % e2 min(p1-s1, p2 -s2) + max( s1,s2 ) max(s1, s2)
* The result precision and scale have an absolute maximum of 38. When a result
precision is greater than 38, the corresponding scale is reduced to prevent the
integral part of a result from being truncated.
Run Code Online (Sandbox Code Playgroud)
这是我们对这个表中的变量所拥有的:
e1: @big_number, a DECIMAL(38, 0)
-> p1: 38
-> s1: 0
e2: 1, an INT
-> p2: 10
-> s2: 0
e1 / e2
-> Result precision: p1 - s1 + s2 + max(6, s1 + p2 + 1) = 38 + max(6, 11) = 49
-> Result scale: max(6, s1 + p2 + 1) = max(6, 11) = 11
Run Code Online (Sandbox Code Playgroud)
根据上表中的星号注释,aDECIMAL可以具有的最大精度为 38。所以我们的结果精度从 49 减少到 38,并且“相应的比例被减少以防止结果的整数部分被截断”。从这个评论中不清楚规模是如何缩小的,但我们确实知道这一点:
根据表中的公式,将两个s相除后您可以得到的最小尺度DECIMAL是 6。
因此,我们最终得到以下结果:
e1 / e2
-> Result precision: 49 -> reduced to 38
-> Result scale: 11 -> reduced to 6
Note that 6 is the minimum possible scale it can be reduced to.
It may be between 6 and 11 inclusive.
Run Code Online (Sandbox Code Playgroud)
现在答案显而易见:
我们部门的输出被强制转换为DECIMAL(38, 6),DECIMAL(38, 6)不能容纳 10 37。
就这样,我们就可以构造另一个部门,通过确保结果成功可以适应DECIMAL(38, 6):
DECLARE @big_number DECIMAL(38,0) = '1' + REPLICATE(0, 37);
DECLARE @one_million INT = '1' + REPLICATE(0, 6);
PRINT @big_number / @one_million;
Run Code Online (Sandbox Code Playgroud)
结果是:
10000000000000000000000000000000.000000
Run Code Online (Sandbox Code Playgroud)
注意小数点后的 6 个零。我们可以DECIMAL(38, 6)通过使用SQL_VARIANT_PROPERTY()如上来确认结果的数据类型:
DECLARE @big_number DECIMAL(38,0) = '1' + REPLICATE(0, 37);
DECLARE @one_million INT = '1' + REPLICATE(0, 6);
SELECT
SQL_VARIANT_PROPERTY(@big_number / @one_million, 'BaseType') AS [BaseType] -- decimal
, SQL_VARIANT_PROPERTY(@big_number / @one_million, 'Precision') AS [Precision] -- 38
, SQL_VARIANT_PROPERTY(@big_number / @one_million, 'Scale') AS [Scale] -- 6
;
Run Code Online (Sandbox Code Playgroud)
那么我们如何绕过这个限制呢?
嗯,这当然取决于您进行这些计算的目的。您可能会立即跳转到的一种解决方案是将您的数字转换为FLOAT用于计算,然后DECIMAL在完成后将它们转换回。
这在某些情况下可能有效,但您应该小心了解这些情况是什么。众所周知,将数字相互转换FLOAT是危险的,可能会给您带来意外或不正确的结果。
在我们的例子中,将10 37和从FLOAT得到的结果,这只是简单的错误:
DECLARE @big_number DECIMAL(38,0) = '1' + REPLICATE(0, 37);
DECLARE @big_number_f FLOAT = CAST(@big_number AS FLOAT);
SELECT
@big_number AS big_number -- 10^37
, @big_number_f AS big_number_f -- 10^37
, CAST(@big_number_f AS DECIMAL(38, 0)) AS big_number_f_d -- 9999999999999999.5 * 10^21
;
Run Code Online (Sandbox Code Playgroud)
你有它。小心分开,我的孩子们。