如何在一个SQL请求中舍入一列,而不改变总的求和?
我有一个定义如下的表:如何在一个SQL请求中舍入一列,而不改变总的求和?,sql,sql-server,algorithm,sql-server-2008,Sql,Sql Server,Algorithm,Sql Server 2008,我有一个定义如下的表: create table #tbFoo (bar float) 我正在寻找一种方法,在不改变总和的情况下对列栏中包含的每个值进行四舍五入(已知总和为整数,或由于浮点数精度非常接近整数) 将每个值四舍五入到最接近的整数将不起作用(例如:1,5;1,5将四舍五入到1;1或2;2) 使用几个请求(例如存储原始和、舍入、计算新和,以及根据需要更新尽可能多的行以返回原始和)可以很容易地做到这一点,但这不是一个非常优雅的解决方案 有没有一种方法可以使用一个SQL请求来实现这一点
create table #tbFoo
(bar float)
我正在寻找一种方法,在不改变总和的情况下对列栏中包含的每个值进行四舍五入(已知总和为整数,或由于浮点数精度非常接近整数)
将每个值四舍五入到最接近的整数将不起作用(例如:1,5;1,5将四舍五入到1;1或2;2)
使用几个请求(例如存储原始和、舍入、计算新和,以及根据需要更新尽可能多的行以返回原始和)可以很容易地做到这一点,但这不是一个非常优雅的解决方案
有没有一种方法可以使用一个SQL请求来实现这一点
我使用的是SQL Server 2008,因此欢迎使用此特定供应商的解决方案
编辑:我正在寻找一个最小化旧值和新值之间差异的请求。换句话说,如果一个较大的值被向下舍入,则该值不应向上舍入,反之亦然。更新: 请参阅我的博客文章中详细介绍的此解决方案:
您需要为每个值保留累积偏移量:
1.2 (1 + 0.0) ~ 1 1 1.2 +0.2
1.2 (1 + 0.2) ~ 1 2 2.4 +0.4
1.2 (1 + 0.4) ~ 1 3 3.6 +0.6
1.2 (1 + 0.6) ~ 2 5 4.8 -0.2
1.2 (1 - 0.2) ~ 1 6 6.0 0.0
这在MySQL
中很容易实现,但在sqlserver
中,您必须编写游标或使用累积子选择(效率较低)
更新: 下面的查询选择值和向下舍入到最接近的较小整数之间的差值 这给了我们应该取整的数值(
N
)
然后,我们按分数部分对值进行排序(那些更接近其上限的先排序),并将第一个N
向上舍入,其他值向下舍入
SELECT value,
FLOOR(value) + CASE WHEN ROW_NUMBER() OVER (ORDER BY value - FLOOR(value) DESC) <= cs THEN 1 ELSE 0 END AS nvalue
FROM (
SELECT cs, value
FROM (
SELECT SUM(value) - SUM(FLOOR(value)) AS cs
FROM @mytable
) c
CROSS JOIN
@mytable
) q
选择值,
下限(值)+行数()超过时的情况(按值排序-下限(值)描述)首先获取舍入和与实际和之间的差值,以及记录数:
declare @Sum float, @RoundedSum float, @Cnt int
select @Sum = sum(bar), @RoundedSum = sum(round(bar)), @Cnt = count(*)
from #tbFoo
然后在四舍五入之前,将差值平均分布在所有值上:
declare @Offset float
set @Offset = (@Sum - @RoundedSum) / @Cnt
select bar = round(bar + @Offset)
from #tbFoo
如果您有一个n个值的列表,其中的元素仅精确到一个整数值(+-0.5)以内,那么这些元素的任何和都将有一个累积误差或+-(n*0.5)。如果列表中有6个元素,加起来应该是一个数字,那么最坏的情况是,如果只加上整数值,则会减少3个元素
如果你想办法将10.2表示为11,以使求和有效,那么你已经将该元素的精度从+-0.5更改为+-0.8,这在查看整数时是违反直觉的
一个可能的解决方案是只在显示期间(在输出中使用一些格式字符串)对数字进行四舍五入,而不是在检索阶段。每个数字将尽可能接近实际值,但总和也将更准确
示例:如果有3个值,每个值为1/3,显示为整数百分比,则应显示33、33和33。做任何其他事情都是为任何单个值创建大于+-0.5的误差范围。您的合计仍应显示为100%,因为这是可能的最佳值(与使用已四舍五入值的总和不同)
另外,请注意,通过使用浮点,您已经对精度引入了限制,因为您无法准确表示0.1。欲了解更多信息,请阅读
@Quassnoi:谢谢你的回答。使用您的解决方案,1.4/1.4/1.2将四舍五入为1/1/2,这是不公平的,因为1.4大于1.2。@Quassnoi:这正是我想要的。谢谢@在我的现实世界问题中,我有另一列'ListID',我希望每个ListID的总和保持不变。我已经使用GROUPBY子句修改了您的请求,它几乎可以工作,但是行号应用于我的整个请求,而不是GROUPBY请求。有什么我能做的吗?@Brann:你能发布一些示例数据和所需的结果集吗?@Brann
:同时,尝试用OVER(按值排序)
替换OVER(按列表ID按值排序)
。这将按组返回行号。@guffa:如果我没有弄错的话:0.2/0.2/0.2/0.2/0.2,sum=1,roundedsum=0,offest=0.2,finalresulst=0/0/0/0,finalsum=0。@Brann:是的,这不会给出正确的结果。然而,平均分配偏差的原则是正确的。不过我会再给它一些…@Galghamon:是的,将10.2显示为11正是我想要实现的。这也是用户想要看到的。一个好问题。我把它放在我的博客帖子待办事项列表中。
declare @Offset float
set @Offset = (@Sum - @RoundedSum) / @Cnt
select bar = round(bar + @Offset)
from #tbFoo