SQL Server SUM 和 UNION 丢失小数精度

问题描述 投票:0回答:1

这是一个与实体框架和 SQL Server (2016) 相关的问题。

当对具有较低小数位数和精度的小数列进行求和,并将其与另一个具有较高小数位数和精度的小数列合并时,我注意到 SQL Server 中存在一些奇怪的行为。结果值都具有较低的比例和精度。

这似乎不正确,因为此页建议在 UNIONing 时,精度将调整为适应它所需的最宽范围。

通过这个例子就可以很容易看出问题:

create table low_scale (val decimal(13,2))
create table high_scale (val decimal(19,8))

insert into low_scale values (10.00), (2.23)
insert into high_scale values (0.0000002), (2.02302023)

-- Query 1: Works fine - Result is at the widest precision required to accomodate the two types:
select * from low_scale
union all 
select * from high_scale

-- Query 2: Strange - result is rounded to two decimal places:
select sum(val) from low_scale
union all 
select sum(val) from high_scale

如您所料,查询 1 的结果是:

10.00000000
2.23000000
0.00000020
2.02302023

但是,这是查询 2 的结果:

12.23
2.02

看来我可以通过首先将较低精度的列转换为较高的精度来解决这个问题,如下所示:

-- Result is at the expected 8 decimal places:
select sum(CAST(val as decimal(19,8))) from low_scale
union all 
select sum(val) from high_scale

但是,我使用的是 Entity Framework 6,并且对生成的 SQL 没有太多控制权。有什么方法可以强制实体框架在进行 SUM 时转换为更高的精度,或者使用其他方法来确保正确的行为?

编辑: 我不确定为什么这被标记为有关浮点的问题的重复项。这与浮点无关 - 它使用十进制 - 一种定点数据类型。

sql-server entity-framework
1个回答
3
投票

扩展马丁·史密斯在评论中提到的内容,这就是正在发生的事情:

根据 SUM 的 SQL Server 文档,对

decimal(p, s)
类型进行 SUM 的结果始终为
decimal(38, s)

因此 UNION 前半部分的输入为

decimal(38, 2)
,后半部分为
decimal(38, 8)

本页指出,当 UNIONing 时,结果精度为

max(s1, s2) + max(p1-s1, p2-s2)
,小数位数为
max(s1, s2)

因此,将

s1 = 2
s2 = 8
p1, p2 = 38
放入其中,我们得到的精度为 44,小数位数为 8。

但是同一页面指出 结果精度和小数位数的绝对最大值为 38。当结果精度大于 38 时,相应的小数位数会减小,以防止结果的整数部分被截断。

因此,它将小数位数减少 6(降至 2),使总精度降至 38。

这解释了这种行为,但没有提供解决方案。 也就是说,有一些选择:

  1. 将 UNION 的两半作为单独的查询发出。此时,我们将处理 .NET 小数,并且我们可以
    .Concat()
    而不必担心 SQL 行为。
  2. 将所有小数类型保持相同的精度 - 但在存储到数据库时始终适当舍入。
  3. (讨厌)- 在调用
    Sum()
    之前将
    .Concat() or .Union()
    的结果转换为 float 或 double。显然这会带来很多其他问题,但在某些情况下它可能是可行的。

就我而言,我可能会选择#2,因为肯定会有其他地方对这两个表进行求和和联合,并且我不希望其他开发人员认识到他们会受到此 SQL Server 行为的影响(因为,从 EF 的角度来看,我们正在处理 .NET 小数)。

© www.soinside.com 2019 - 2024. All rights reserved.