Tsql 这可以在没有循环或光标的情况下完成吗
我有一个表格,列出了各个项目以及我们为它们支付的金额。我们收到的付款可能少于账单总金额。我想按照原始账单金额的比例将付款分配给每个项目 这是棘手的部分 每个个人支付的金额不能有零分 个人支付金额的总和仍必须等于总支付金额 设置数据: 这样不行 它将返回:Tsql 这可以在没有循环或光标的情况下完成吗,tsql,sql-server-2008-r2,Tsql,Sql Server 2008 R2,我有一个表格,列出了各个项目以及我们为它们支付的金额。我们收到的付款可能少于账单总金额。我想按照原始账单金额的比例将付款分配给每个项目 这是棘手的部分 每个个人支付的金额不能有零分 个人支付金额的总和仍必须等于总支付金额 设置数据: 这样不行 它将返回: A 1.67 C 1 D 0.67 ----- 3.34 <--- Note the sum doesn't equal the Total Paid 我知道我可以通过游标或循环来实现这一点,在每一步跟踪未分
A 1.67
C 1
D 0.67
-----
3.34 <--- Note the sum doesn't equal the Total Paid
我知道我可以通过游标或循环来实现这一点,在每一步跟踪未分配的金额,并确保在最后一项之后分配全部已支付金额
然而,我希望有一种不用循环或游标的方法来实现这一点
这是我试图解决的问题的一个大大简化的版本。实际数据有超过10万行,而游标方法非常慢 对于这种情况,您无法应用精确分布;正如您已经显示的那样,四舍五入的结果超过了收到的付款总额 因此,您需要将剩余的内容分发给最终[账单],因此您需要做两件事 确定当前行是否为该组中的最后一行。 确定已经分配了多少付款。 在这里,您没有提供太多的数据来处理,因此下面的内容并不理想,但是这与您想要的内容大致相同
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具有更高的键,这将构成组,因此您可以相应地调整窗口功能。如果你能提供更多的带有附加列的示例数据等,我可能会想出一个更优雅的解决方案。这里是一个使用递归的有点复杂的解决方案 我认为这是一个可行的方法 将1作为第三个参数传递给舍入,以确保舍入始终向下,然后将构成余额的奇数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.665到1.67。我需要将每个分配四舍五入为一便士。我不能在一次分配中有零分,2008-r2Ah当然没有领先优势,直到2012年才引入;很抱歉错过了2008R2,在这里,我认为我对递归cte很聪明。不错!
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;
;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
+----+------+
| id | paid |
+----+------+
| A | 1.66 |
| B | 1.00 |
| C | 0.67 |
+----+------+
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