SQL ROUND - 算术溢出的真正原因是什么?

SQL ROUND - What is the actual reason for arithmetic overflow?

我的任务是在工作中调试我们的一些旧代码并遇到了一个问题。代码是用 VB6 编写的,我们有一个名为 RoundIt() 的函数,它接受一个值,然后将其四舍五入到小数点后两位。令人惊讶的是,该函数(我在 VB6 中没有太多经验,所以我假设它可能只是语言的限制)构建了一个 SQL 字符串来执行以舍入值。

所以在代码中我们有一个 double 类型的变量,我称之为 myVal。在这种特殊情况下,我们得到一个错误,因为 myVal 的值为 0.997721736173984,并且构建的字符串变为

 SELECT ROUND(0.997721736173984, 2) as RoundedNum

导致消息 "Arithmetic overflow error converting expression to data type numeric." 根据我的理解,这是由于值试图舍入为 1 但无法这样做,因为现在正在 returned 的数据类型与ROUND函数中输入的不同,必须相同

我的问题是,由于这是一个动态构建的 SQL 字符串,我们并不是要声明一个具有数据类型的 SQL 变量并在 ROUND 函数中使用它,我们是只是构建字符串 - 那么 默认情况下 0.997721736173984 的数据类型到底是什么?那么试图 returned 的数据类型是什么? 我猜是小数(不确定精度或小数位数),现在精度或小数位数不同它试图 return 舍入值,但我想确定一下。

我不是要避免算术溢出,也不是要确定不同服务器上的差异,所以这个问题与建议的内容不重复。我的问题是动态构建的 SQL 字符串中的 input/output 是什么数据类型,为什么会导致算术溢出错误(如果根据下面的评论,它们属于相同的数据类型)。

如果你执行这段代码:

select 0.097721736173984 RoundedNum
into #temp

exec tempdb.dbo.sp_help '#temp'

您会发现它将您的字面数字解释为数字 (15,15)。

Column_name Type    Computed  Length Prec  Scale Nullable  TrimTrailingBlanks  FixedLenNullInSource  Collation
----------- ------- --------- ------ ----- ----- --------- ------------------- --------------------- ---------
RoundedNum  numeric no        9      15    15    no        (n/a)               (n/a)                 NULL

这意味着您只能使用 15 位数字,并且所有 15 位数字都必须位于小数点的右边。当您舍入该数字时,它不再适合数据类型,因此返回错误。

您可以通过显式转换文字来修复它。如:

select round(convert(float,0.997721736173984),2) RoundedNum

您可以选择任何您想要的数据类型,只要它能捕获您期望的所有有效数字即可。 Float 为您提供范围内最大的灵活性,但可能会降低准确性。如果您知道要四舍五入的所有数字都小于 1 且不超过 15 位,那么 numeric(16,15) 将保留您的原始数字和四舍五入的数字。当使用数字类型时,您只需要考虑您的数字将落入的范围,并确保您已分配足够的 space 来容纳所有可能的结果。

这里有一些关于 SQL 服务器如何解析表达式中的数字数据类型的更有用的信息:

Precision, Scale, and Length (Transact-SQL)

您可以使用SQL_VARIANT_PROPERTY 函数查找数据类型。

SELECT
    SQL_VARIANT_PROPERTY(x.y, 'BaseType')   AS DataType,
    SQL_VARIANT_PROPERTY(x.y, 'Precision')  AS Precision,
    SQL_VARIANT_PROPERTY(x.y, 'Scale')      AS Scale
FROM
    (
        VALUES
            (0.997721736173984)
    ) AS x(y)
;

在这种情况下,您有一个数字 (15, 15)。因为所有可用的 space 都分配给了你不能四舍五入到 1 的比例。

使用 CAST 明确设置数据类型将解决您的问题。

    SELECT
        ROUND(CAST(0.997721736173984 AS NUMERIC(16, 15)), 2)