【问题标题】:SQL Server 2005: Scale of numeric expression changes when adding SUM()SQL Server 2005:添加 SUM() 时数值表达式的比例发生变化
【发布时间】:2023-03-07 05:29:01
【问题描述】:

我有一个名为“Jrl”的表,包含三列:

code  AS varchar(15)
total AS numeric(13,2)
rem   AS numeric(13,4)

为了论证,我们假设表中只有一行的值为“001”、400.00 和 52.1745。

考虑以下查询:

SELECT code, total - rem
 FROM Jrl

它返回一行包含 '001' 和 347.8255。这是正确的。

如果我将查询更改如下(这实际上是我在代码中需要的查询):

SELECT code, SUM(total) - SUM(rem)
 FROM Jrl
 GROUP BY code

它返回一行,其中包含 '001' 和 347.83(即,比例尺为 2 而不是 4)。

现在根据http://msdn.microsoft.com/en-us/library/ms190476%28v=sql.90%29.aspx 的文档,数值表达式(减法)的类型应该是 numeric(16,4),但显然不是。 (我在 SQL Server 2005 和 2008 R2 上得到了相同的行为。)

有人可以告诉我那里发生了什么吗?

顺便说一句。我确实找到了解决方法,但我不喜欢它,这就是我发布这个问题的原因。解决方法是添加显式转换:

SELECT code, CAST(SUM(total) AS numeric(13,4)) - SUM(rem)
 FROM Jrl
 GROUP BY code

【问题讨论】:

    标签: sql sql-server sql-server-2008 sql-server-2005


    【解决方案1】:

    1) 请运行此脚本并阅读我的 cmets。

    2) 我希望这个答案对你有所帮助。

    3) SUM()-SUM() 的精度为 2,因为您选择先求和(SUM(total) 和 SUM(rem)),然后再减去(SUM(total) - SUM(rem))。

    4) 我的建议是使用SELECT t.code, SUM(t.total - t.rem) AS diff ...(先减,然后求和)。

    5) 你可以阅读我对这个问题的回答SQL Numeric data type truncating value?:

    DECLARE @Test TABLE(
        code  varchar(15),
        total numeric(13,2),
        rem   numeric(13,4)
    );
    
    INSERT  @Test (code, total, rem)
    VALUES  ('001', 11.78, 5.6789);
    
    --Test [1]
    SELECT  dt.*,
            SQL_VARIANT_PROPERTY(dt.diff, 'BaseType') AS diff_BaseType,
            SQL_VARIANT_PROPERTY(dt.diff, 'Precision') AS diff_Precision,
            SQL_VARIANT_PROPERTY(dt.diff, 'Scale') AS diff_Scale
    FROM
    (
            SELECT  t.code, t.total - t.rem AS diff
            FROM    @Test t
    ) dt;
    
    /*
    Operation: e1 - e2
    Result precision: max(s1, s2) + max(p1-s1, p2-s2) + 1 = max(2,4) + max(13-2, 13-4) + 1 = 4 + 11 + 1 = 16
    Result scale: max(s1, s2) = max(2, 4) = 4
    */
    
    --Test [2]
    SELECT  dt.*,
            SQL_VARIANT_PROPERTY(dt.diff, 'BaseType') AS diff_BaseType,
            SQL_VARIANT_PROPERTY(dt.diff, 'Precision') AS diff_Precision,
            SQL_VARIANT_PROPERTY(dt.diff, 'Scale') AS diff_Scale
    FROM
    (
            SELECT  t.code, SUM(t.total - t.rem) AS diff
            FROM    @Test t
            GROUP BY t.code
    ) dt;
    
    /*
    Operation: SUM(e1 - e2)
    Result precision: 38--For SUM function, I think (it's just a hipotese), SQL Server choose the maximum precision to prevent the overflow error
                        Argument:
                        DECLARE @t TABLE (Col NUMERIC(2,1)); INSERT @t VALUES (1);
                        SELECT  SQL_VARIANT_PROPERTY(SUM(t.Col), 'Precision') FROM @t t;
                        Result: precision = 38 (maximum DECIMAL/NUMERIC precision)
    Result scale: the same scale as (e1-e2)= 4 (please see Test [1])
    */
    
    --Test [3]
    SELECT  dt.*,
            SQL_VARIANT_PROPERTY(dt.SUM_total, 'BaseType')  AS SUM_total_BaseType,
            SQL_VARIANT_PROPERTY(dt.SUM_total, 'Precision') AS SUM_total_Precision,
            SQL_VARIANT_PROPERTY(dt.SUM_total, 'Scale')     AS SUM_total_Scale,
    
            SQL_VARIANT_PROPERTY(dt.SUM_rem, 'BaseType')    AS SUM_rem_BaseType,
            SQL_VARIANT_PROPERTY(dt.SUM_rem, 'Precision')   AS SUM_rem_Precision,
            SQL_VARIANT_PROPERTY(dt.SUM_rem, 'Scale')       AS SUM_rem_Scale,
    
            SQL_VARIANT_PROPERTY(dt.diff, 'BaseType')       AS diff_BaseType,
            SQL_VARIANT_PROPERTY(dt.diff, 'Precision')      AS diff_Precision,
            SQL_VARIANT_PROPERTY(dt.diff, 'Scale')          AS diff_Scale
    FROM
    (
            SELECT  t.code, 
                    SUM(t.total) AS SUM_total, SUM(t.rem) AS SUM_rem, SUM(t.total) - SUM(t.rem) AS diff
            FROM    @Test t
            GROUP BY t.code
    ) dt;
    
    /*
    Operation: SUM(total) (<> e1 + e2 + ...)
    Result precision: 38--I think SQL Server choose the maximum precision to prevent the overflow error
    Result scale: the same precision as total= 2
    */
    
    
    /*
    Operation: SUM(rem) (<> e1 + e2 + ...)
    Result precision: 38--I think SQL Server choose the maximum precision to prevent the overflow error
    Result scale: the same precision as rem= 4
    */
    
    /*
    Operation: SUM(total) - SUM(rem) = e1 - e2
    Result precision: max(s1, s2) + max(p1-s1, p2-s2) + 1 = max(2,4) + max(38-2, 38-4) + 1 = 4 + 36 + 1 = 41 
    but max. precision is 38 so result precision = 38
    
    Calculated result scale: max(s1, s2) = 4 
    but because the real precision for result (41) is greater than maximum precision (38)
    SQL Server choose to decrease the precision of the result to 2 (please see Test [3] - diff_Scale).
    In this case (the real precision for result is greater than maximum precision) I think the 
    expression for result's precision is max(s1, s2) - (real precision - maximum precision) + 1 = 4 - (41 - 38) + 1 = 4 - 3 + 1 = 2
    For example you could try to modify the definition of total column to `total numeric(13,1)` 
    and you will see that the precision for SUM(total) - SUM(rem) becomes 4 - 4(4+37+1=42) + 1 = 1
    */
    

    结果:

    --Test [1] SELECT t.code, t.total - t.rem AS diff
    code diff   diff_BaseType  diff_Precision diff_Scale
    ---- ------ -------------- -------------- ----------
    001  6.1011 numeric        16             4
    
    --Test [2] SELECT t.code, SUM(t.total - t.rem) AS diff
    code diff   diff_BaseType diff_Precision diff_Scale
    ---- ------ ------------- -------------- ----------
    001  6.1011 numeric       38             4
    
    --Test [3] SELECT t.code, ..., SUM(t.total) - SUM(t.rem) AS diff
    code SUM_total SUM_rem diff SUM_total_BaseType SUM_total_Precision SUM_total_Scale SUM_rem_BaseType SUM_rem_Precision SUM_rem_Scale diff_BaseType diff_Precision diff_Scale
    ---- --------- ------- ---- ------------------ ------------------- --------------- ---------------- ------------------------------- ------------- -------------- ----------
    001  11.78     5.6789  6.10 numeric            38                  2               numeric          38                4             numeric       38             2
    

    【讨论】:

      【解决方案2】:

      那是因为 total AS numeric(13,2) 字段

      由于您要减去两个不同精度字段的和,sql server 会以最小的精度显示结果。

      如果你这样做:

       create table jrl2(
      code  varchar(15),
      total numeric(13,4),
      rem   numeric(13,4)
      )
      insert into jrl2 values ('001', 400.00 , 52.1745)
      
      select * from jrl2
      SELECT code, total - rem  FROM Jrl
      
      SELECT code, SUM(total) - SUM(rem)
       FROM Jrl2
       GROUP BY code
      

      你会得到:347.8255

      【讨论】:

      • 查看 Andreas 提供的链接;它在那里说,对于e1 - e2 操作,结果比例将为max(s1, s2)
      • 一个脚注提到,如果通常的规则超过了 38 的最大精度,则比例会减小。这是因为 SUM()。
      • 我看到了那个注释,但是根据公式,得到的精度应该是 4 +max(11,9) + 1 = 16。你是怎么断定它超过 38 的?
      【解决方案3】:

      安德烈亚斯,

      问题在于 SUM() 的返回类型使用了 38 的最大精度。(参见联机丛书中 SUM 下的“返回类型”部分:http://msdn.microsoft.com/en-us/library/ms187810%28v=sql.90%29.aspx。)

      “总计”列的类型是数字 (13,2),因此 SUM(total) 的结果类型是(不幸的是)数字 (38,2)。当操作数的精度为 38 时,e1 + e2 的比例(再次不幸)不是 max(s1,s2)。

      这在 BOL 的脚注中提到:http://msdn.microsoft.com/en-us/library/ms190476.aspx。 *结果精度和小数位数的绝对最大值为 38。当结果精度大于 38 时,相应的小数位数会减小,以防止结果的整数部分被截断。

      另见http://support.microsoft.com/kb/281341。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-07-29
        • 2020-11-14
        • 2020-09-25
        • 1970-01-01
        • 2011-04-18
        • 2014-05-06
        • 1970-01-01
        • 2018-04-07
        相关资源
        最近更新 更多