我有一张表格列出了单个项目以及我们为它们收取的费用。我们收到的付款可能低于结算总额。我想按照原始账单金额的比例将该付款分配给每个项目。
这是棘手的部分。
每个个人的付款金额不能有分数美分
个人付款金额的总和仍然必须加到TotalPaid金额上。
设置数据:
declare @t table
(
id varchar(4) primary key,
Billed money not null
)
insert into @t
(
id,
billed
)
values
( 'A', 5),
( 'B', 3),
( 'C', 2)
declare @TotalPaid money
Set @TotalPaid = 3.33
这种方式不起作用
SELECT
ID,
Round(@TotalPaid * Billed / (Select Sum(Billed) from @t), 2)
From
@T
它将返回:
A 1.67
C 1
D 0.67
-----
3.34 <--- Note the sum doesn't equal the Total Paid
我知道我可以通过游标或循环完成此操作,跟踪每一步的未分配金额,并确保在最后一项之后分配整个TotalPaid金额。
但是我希望有一种方法可以在没有循环或游标的情况下执行此操作。
这是我试图解决的问题的大大简化版本。实际数据超过100K行,光标方法非常慢。
答案 0 :(得分:3)
我认为这是一种可行的方法......
(将1
作为ROUND
的第三个参数传递,以确保舍入始终为向下,然后将构成余额的奇数0.01
分配给舍入金额之差的那些理想的数量是最大的)
WITH t1
AS (SELECT *,
billed_adj = @TotalPaid * Billed / Sum(Billed) OVER(),
billed_adj_trunc = ROUND(@TotalPaid * Billed / Sum(Billed) OVER(), 2, 1)
FROM @t)
SELECT id,
billed,
billed_adj_trunc + CASE
WHEN ROW_NUMBER() OVER (ORDER BY billed_adj - billed_adj_trunc DESC)
<= 100 * ( @TotalPaid - SUM(billed_adj_trunc) OVER() )
THEN 0.01
ELSE 0
END
FROM t1
ORDER BY id
答案 1 :(得分:2)
这是一个使用递归common table expression
的(有些复杂)解决方案;with cte as (
select
id
, Paid = round(@TotalPaid * Billed / (Select Sum(Billed) from @t), 2,1)
, Remainder = @TotalPaid * Billed / (Select Sum(Billed) from @t)
- round(@TotalPaid * Billed / (Select Sum(Billed) from @t), 2,1)
, x.next_id
from @t t
outer apply (
select top 1 next_id = i.id
from @t as i
where i.id > t.id
order by i.id asc
) x
)
, r_cte as (
--anchor row(s) / starting row(s)
select
id
, Paid
, Remainder
, next_id
from cte t
where not exists (
select 1
from cte as i
where i.id < t.id
)
union all
--recursion starts here
select
c.id
, c.Paid + round(c.Remainder + p.Remainder,2,1)
, Remainder = c.Remainder + p.Remainder - round(c.Remainder + p.Remainder,2,1)
, c.next_id
from cte c
inner join r_cte p
on c.id = p.next_id
)
select id, paid
from r_cte
rextester演示:http://rextester.com/MKLDX88496
返回:
+----+------+
| id | paid |
+----+------+
| A | 1.66 |
| B | 1.00 |
| C | 0.67 |
+----+------+
答案 2 :(得分:0)
对于类似的内容,您无法应用完全分发;因为你已经显示了舍入结果总数超过收到的款项。
因此,你需要将“剩下的东西”分发给最终[收费],所以你需要做两件事......
您没有提供太多数据可供使用,因此以下情况并不理想,但这与您想要的一致......
SELECT
ID,
CASE WHEN lead(billed,1) OVER(ORDER BY (SELECT 1)) IS NULL THEN @TotalPaid - (sum(round(@TotalPaid * Billed / (Select Sum(Billed) from @t),2)) OVER(ORDER BY (SELECT 1) ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING))
ELSE round(@TotalPaid * Billed / (Select Sum(Billed) from @t),2)
END AS solution
FROM
@T;
请注意,如果A,B,C具有更高的键,则这将组成“组”,因此您将相应地调整窗口函数。如果您可以提供更多样本数据和其他列等,我可以提出更优雅的解决方案。