SELECT vs UPDATE,使用ABS函数时出现意外的舍入

时间:2018-11-07 19:16:52

标签: sql sql-server floating-point numbers

附加的是在SQL中运行的代码示例。对于SQL Server,这似乎是意外行为。应该发生的是从数字中删除负数,但是在update命令下使用相同的功能时,它会确定绝对值并四舍五入。为什么会这样?

DECLARE @TEST TABLE (TEST varchar(2048));

INSERT INTO @TEST VALUES ('  -29972.95');

SELECT TEST FROM @TEST;

SELECT ABS(TEST) FROM @TEST;

UPDATE @TEST SET TEST = ABS(TEST);

SELECT TEST FROM @TEST;

下面是该代码的结果。

  -29972.95
29972.95
29973

4 个答案:

答案 0 :(得分:3)

这似乎是CONVERT函数的“功能”,而不是与SELECTUPDATE有关的任何功能(唯一的不同是因为UPDATE隐式转换FLOAT(8)返回的ABS(...)回到VARCHAR)。

更新计划中的计算标量包含表达式

[Expr1003] = Scalar Operator(CONVERT_IMPLICIT(varchar(2048),
                                              abs(CONVERT_IMPLICIT(float(53),[TEST],0))
                                              ,0) /*<-- style used for convert from float*/
                            )
  

值-输出

     

0(默认)-最多6位数字。适当时以科学计数法使用。

     

1-始终为8位数字。始终以科学计数法使用。

     

2-始终为16位数字。始终以科学计数法使用。

从MSDN:https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-2017

这可以在下面的示例中看到:

SELECT
    [# Digits],
    CONVERT(FLOAT(8), CONVERT(VARCHAR(20), N)) AS [FLOAT(VARCHAR(N))],
    CONVERT(FLOAT(8), CONVERT(VARCHAR(20), N, 0)) AS [FLOAT(VARCHAR(N, 0))],
    CONVERT(FLOAT(8), CONVERT(VARCHAR(20), N, 1)) AS [FLOAT(VARCHAR(N, 1))]
FROM (SELECT '6 digits', ABS('9972.95') UNION ALL SELECT '7 digits', ABS('29972.95')) T ([# Digits], N)

这将返回以下结果:

# Digits FLOAT(VARCHAR(N)) FLOAT(VARCHAR(N, 0)) FLOAT(VARCHAR(N, 1))
-------- ----------------- -------------------- --------------------
6 digits 9972.95           9972.95              9972.95
7 digits 29973             29973                29972.95

这证明UPDATE以默认样式“ 0”有效地使用了CONVERT(VARCHAR, ABS(...))。这将FLOAT的范围从ABS限制为6位数字。删除1个字符以免隐式转换溢出,在这种情况下,您将保留实际值。

将其返回给OP:

  • 在这种情况下,ABS函数在示例中返回了FLOAT(8)
  • UPDATE然后导致隐式转换,该转换实际上是CONVERT(VARCHAR(2048),ABS(...),0),然后溢出了默认样式的最大位数。
  • 要解决此问题(如果与实际问题相关),您需要指定1或2的样式(甚至3以获得17位数字),以避免这种截断(但请务必处理科学表示法,因为在这种情况下现在总是返回它)

答案 1 :(得分:1)

(为简洁起见,删除了一些初步测试)

这肯定与INSERT / UPDATE期间的静默截断有关。

如果将值插入更改为此:

INSERT INTO @TEST SELECT ABS('  -29972.95')

您无需进行UPDATE即可立即获得相同的舍入/截断。

与此同时,SELECT ABS(' -29972.95')产生了预期的结果。

进一步的测试支持隐式浮点转换的理论,并指出罪魁祸首是转换回varchar的原因:

DECLARE @Flt float = '  -29972.95'

SELECT @Flt;

SELECT CAST(@Flt AS varchar(2048))

产生:

-29972.95

-29972

可能是最终编辑:

我在嗅着和马丁一样的一棵树。 I found this

这让我尝试了这个:

DECLARE @Flt float = '  -29972.95'

SELECT @Flt;

SELECT CONVERT(varchar(2048),@Flt,128)

哪个制作的:

-29972.95

-29972.95

因此,我将其称为 kinda文档,因为128样式是一种旧样式,已过时,将来可能会消失。但是,当前记录的样式均未产生相同的结果。非常有趣。

答案 2 :(得分:1)

ABS()应该用于数字值,并且varchar输入将转换为float。对此行为最可能的解释是,浮点数在所有数字数据类型(例如十进制,整数,位)中都具有highest precedence

您的SELECT语句仅返回浮点结果。但是UPDATE语句隐含地将浮点数转换回varchar并产生意外结果:

SELECT
    test,
    ABS(test) AS test_abs,
    CAST(ABS(test) AS VARCHAR(100)) AS test_abs_str
FROM (VALUES
    ('-29972.95'),
    ('-29972.94'),
    ('-29972.9')
) AS test(test)

test      | test_abs | test_abs_str
----------|----------|-------------
-29972.95 | 29972.95 | 29973
-29972.94 | 29972.94 | 29972.9
-29972.9  |  29972.9 | 29972.9

我建议您使用显式转换和精确的数值数据类型来避免隐式转换/浮点数的这种和其他潜在问题:

SELECT
    test,
    ABS(CAST(test AS DECIMAL(18, 2))) AS test_abs,
    CAST(ABS(CAST(test AS DECIMAL(18, 2))) AS VARCHAR(100)) AS test_abs_str
FROM (VALUES
    ('-29972.95'),
    ('-29972.94'),
    ('-29972.9')
) AS test(test)

test      | test_abs | test_abs_str
----------|----------|-------------
-29972.95 | 29972.95 | 29972.95
-29972.94 | 29972.94 | 29972.94
-29972.9  | 29972.90 | 29972.90

答案 3 :(得分:0)

ABS是一个数学函数,这意味着它被设计为使用数字值,在使用其他数据类型(例如VARCHAR)时,您不能期望该函数的正确行为,我建议首先对数字进行必要的CAST在应用ABS功能之前,请选择以下数据类型:

UPDATE @TEST SET TEST = ABS(CAST(TEST AS DECIMAL(18,2)))

此后,您的查询将输出

  

29972.95

这不能解决ABS在选择而不是在更新值时工作正常的可能性,这也许是sqlserver上的错误,但是避免转换为函数所需的正确数据类型确实是一种不好的做法。当执行SELECT子句但在UPDATE上被忽略时,可能会发生隐式强制转换,因为Microsoft期望您做正确的事。