在行集合中平均分配价值

时间:2018-07-23 23:33:06

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

试图找到一种将给定数量分配给可变数量的行的方法。在下面的示例中,金额是固定的

Set @Amt = 1000

获取行数

Set @AmtofRows = select count(*) from <table>

Set @AmtPerRow = @Amt / @AmtofRows

这不起作用,因为@AmtofRows不能均匀地分成@Amt。例如,如果@Amt = 1000并且@AmtofRows = 18,则@AmtPerRow将为55.56 ...当将此金额应用于所有18行时,总计为1000.08,总和不应超过1000。任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:1)

这是我在90年代记得的典型会计问题。

解决方案非常简单。除最后一行外,对所有行都照常进行。对于最后一个,不要使用您计算出的值,只需将差异与总数相加即可。

例如:在“相等”的3行中分配$ 100.00。

因此,您将$ 100.00除以3,得到$ 33.33。

  • 第1行:您投入了 33.33美元。总计:$ 33.33
  • 第2行:您投入了 33.33美元。总计:$ 66.66
  • 第3行:您投入了 33.34美元。即:$ 100.00-$ 66.66。

答案 1 :(得分:0)

基本技术是在临时表或CTE中将值分配给行。然后,您需要对金额的值求和,找到总和与初始金额之间的差额,并分配该差额以确保总和正确。最简单的方法是将总和之差添加到一行,像这样

DECLARE @Amount float = 1000;
DECLARE @Count int = (Select Count(RowID) from Test);

With Data as (
Select RowID, --Unique Row ID
    Round(@Amount / @Count, 2) as Val1, -- The calculated amount, rounded to cents
    row_number() over (ORDER BY RowID) as RowNo --A rowno for each result (so we can pick just one row later)
from Test
), SumData as (
Select SUM(Val1) as ValSum from Data  --Sum the allocated amounts
)
--This returns the allocated values, but on row 1, we adjust it
Select RowID, Val1 - case when RowNo = 1 then (Select Round(@Amount - ValSum, 2) from SumData) else 0 end as Val2
From Data;

然后,您将使用此Val2图形实际更新您的表或进行任何操作。最大的调整将是行数的一半(以美分计)。如果将所有这些都放在一行上是不可接受的,并且您想将舍入误差分布在各行上,那么您需要做更多的处理。

此外,顺便说一句,您确定应该将金额平均分配给所有行吗?通常,在这种情况下,您应该基于行中的某些数量将值分布在行上-即,如果您在一行中具有200的值,而在另一行中具有100的值,则200行将得到两倍的值。为此,您需要对行值求和,然后每行获得Amount * RowValue / TotalRowValues。不过,此问题无关紧要,因为四舍五入仍然会导致数字未加总,因此您仍然需要如上所述分配四舍五入误差。


已编辑添加

如果要舍入误差到尽可能多的行,则首先分配数量和总和以得到如上所述的差。获取您仍需要分配的舍入误差量,即Round(@Amount-ValSum,2)。将此数字乘以100,这将为您提供需要将行数量相加/相减一分才能使所有行正确相加的行数。此数字将始终小于行数,因为每行最多只能有1/2美分的舍入误差。

例如,如果舍入误差为-2.72,则272行需要从行数中减去1美分。如果是2.72,则他们需要添加1美分。我将使用一个临时表,而不是CTE,并使用RowID,要分配的金额和一个rowno。填写第一步(与CTE相同,按上述方法计算舍入误差,然后,如果存在舍入误差,请执行以下操作(我没有运行此操作,但应该将其关闭)

Update TempTable Set AllocatedAmount = AllocatedAmount + case when @RoundingError > 0 then 0.01 else -0.01 end
Where RowNo <= (ABS(@RoundingError) * 100)  --No of rows to adjust

您可以在不使用临时表的情况下,将其作为一个查询编写为上面的查询,但是用这种方法很难读取。如果必须将舍入误差分布到所有行上,而不是将它们排在最前面,则需要执行一些模运算来确定是否应调整特定的行号。