我正在更新我的IDENTITY
溢出检查脚本以说明DECIMAL
和NUMERIC
IDENTITY
列。
作为检查的一部分,我计算每一IDENTITY
列的数据类型范围的大小;我用它来计算该范围的百分比已用尽。对于该范围的大小,DECIMAL
精度在哪里。NUMERIC
2 * 10^p - 2
p
我创建了一堆带有DECIMAL
和NUMERIC
IDENTITY
列的测试表,并尝试按如下方式计算它们的范围:
SELECT POWER(10.0, precision)
FROM sys.columns
WHERE
is_identity = 1
AND type_is_decimal_or_numeric
;
这引发了以下错误:
Msg 8115, Level 16, State 6, Line 1
Arithmetic overflow error converting float to data type numeric.
我将它缩小到IDENTITY
类型的列DECIMAL(38, 0)
(即具有最大精度),所以我然后POWER()
直接尝试对该值进行计算。
以下所有查询
SELECT POWER(10.0, 38.0);
SELECT CONVERT(FLOAT, (POWER(10.0, 38.0)));
SELECT CAST(POWER(10.0, 38.0) AS FLOAT);
也导致了同样的错误。
- 为什么 SQL Server 会尝试将
POWER()
类型为 的输出转换FLOAT
为NUMERIC
(尤其是在FLOAT
具有更高优先级时)? - 如何为所有可能的精度(当然包括)动态计算 a
DECIMAL
或列的范围?NUMERIC
p = 38
与其进一步干涉马丁的回答,我将在
POWER()
这里添加我的其余发现。抓住你的短裤。
前言
首先,我向您展示展览 A,即 MSDN 文档
POWER()
:POWER()
您可以通过阅读返回类型为的最后一行得出结论FLOAT
,但请再次阅读。float_expression
是“浮点型或可以隐式转换为浮点型的类型”。所以,尽管它的名字,float_expression
实际上可能是 aFLOAT
、 aDECIMAL
或 anINT
。由于 的输出与 的输出POWER()
相同float_expression
,它也可能是其中一种类型。所以我们有一个标量函数,其返回类型取决于输入。可以吗?
观察
我向您展示了展览 B,该测试展示了
POWER()
根据其输入将其输出转换为不同数据类型的测试。相关结果是:
似乎正在发生的事情是
POWER()
转换float_expression
成适合它的最小类型,不包括BIGINT
.因此,
SELECT POWER(10.0, 38);
失败并出现溢出错误,因为10.0
被强制转换NUMERIC(38, 1)
为不足以容纳 10 38的结果。那是因为 10 38扩展为小数点前 39 位,而NUMERIC(38, 1)
可以存储小数点前 37 位加上小数点后 1 位。因此,可以保持的最大值NUMERIC(38, 1)
为 10 37 - 0.1。有了这种理解,我可以编造另一个溢出失败,如下所示。
10 亿(与第一个示例中的 1 万亿相反,它被转换为
NUMERIC(38, 0)
)只是小到可以放入INT
. 然而,10 亿的三次方对于 来说太大了INT
,因此会出现溢出错误。其他几个函数表现出类似的行为,它们的输出类型取决于它们的输入:
POWER()
,CEILING()
,FLOOR()
,RADIANS()
,DEGREES()
, 和ABS()
NULLIF()
,ISNULL()
,COALESCE()
,IIF()
,CHOOSE()
, 和CASE
表达式SELECT 2 * @MAX_INT;
都会SELECT @MAX_SMALLINT + @MAX_SMALLINT;
导致算术溢出。结论
在这种特殊情况下,解决方案是使用
SELECT POWER(1e1, precision)...
. 这将适用于所有可能的精度,因为1e1
被强制转换为FLOAT
,它可以容纳非常大的数字。由于这些函数非常常见,因此了解您的结果可能会被四舍五入或由于它们的行为可能会导致溢出错误,这一点很重要。如果您期望或依赖特定数据类型的输出,请根据需要显式转换相关输入。
所以孩子们,既然你知道了这一点,你就可以前进并繁荣昌盛。
从
POWER
文档中:float
如有必要,第一个输入将被隐式转换为。float
内部计算由标准 C 运行时库 (CRT) 函数使用算术执行pow
。然后将
float
输出pow
转换回左侧操作数的类型(暗示numeric(3,1)
当您使用文字值 10.0 时)。在您的情况下使用显式
float
工作正常:10 38的精确结果不能存储在 SQL Server 中
decimal/numeric
,因为它需要 39 位精度(1 后跟 38 个零)。最大精度为 38。我在更新我自己的身份溢出检查脚本时调查溢出错误时遇到了这篇文章:)
Martin Smith 在这里接受的答案和他在https://stackoverflow.com/a/5663463/1508467上的另一个答案提供了技术信息,但我可以为问题的第二部分添加另一种方法 - 如何创建最小值/最大值十进制列的值。
使用字符串REPLICATE函数的精度和比例来构建一个 9 的字符串,然后转换为合适的小数。
对于十进制/数字标识列,比例必须为零,因此可以进一步简化上述内容。