如何做SQL递归逻辑

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

我有这样的示例数据集;

acctid 支付序列 总额 acctnum 预计价格 付款单
aac1 99 1000.00 1111aa 800.00 1
aac1 99 1000.00 1111bb 800.00 2
aac1 99 1000.00 1111cc 800.00 3
aac1 99 1000.00 1111dd 1200.00 4
aac1 100 2000.00 1111aa 800.00 1
aac1 100 2000.00 1111bb 800.00 2
aac1 100 2000.00 1111cc 800.00 3
aac1 100 2000.00 1111dd 1200.00 4

我想要一个付费金额列,其中总金额分布在该 acctid、payseq 组合的 acctnum 之间;最终结果应该是这样的;

acctid 支付序列 总额 acctnum 预计价格 付款单 支付金额
aac1 99 1000.00 1111aa 800.00 1 800.00
aac1 99 1000.00 1111bb 800.00 2 200.00
aac1 99 1000.00 1111cc 800.00 3
aac1 99 1000.00 1111dd 1200.00 4
aac1 100 2000.00 1111aa 800.00 1
aac1 100 2000.00 1111bb 800.00 2 600.00
aac1 100 2000.00 1111cc 800.00 3 800.00
aac1 100 2000.00 1111dd 1200.00 4 600.00

对于第一个 payseq 99 - 我们根据每个 acctnum 的预期价格在前 2 个 acctnumns(1111aa 和 1111bb)之间分配总金额 1000。 对于第二个 payseq 100 - 我们需要从第一个剩余的位置开始分配,即第二个 acctnum 剩余 600,因此总金额 2000 的分配应在 acctnum(1111bb、1111cc 和 1111dd)之间分配。 同样,如果有更多 payseq,也是如此。

我尝试使用类似的东西

totalamount - sum(ifnull(ExpectPrice,0.0)) 
over(partition by acctId, payseq order by payorder asc rows between unbounded preceding and 0 preceding) as PaidAmount

但这总是从第一个acctnum开始分配。

任何人都可以帮助我使用递归 CTE 或其他方式构建 SQL 查询来完成此逻辑吗?;

PS:我不想将其写为存储过程。我只在 SQL 查询中需要这个。

尝试过写案例陈述等,但似乎没有任何帮助!

sql sql-server common-table-expression recursive-query
1个回答
0
投票

这很棘手。在下面的讨论中,我将使用术语“支付”和“支付金额”来指代

{acctid, payseq, totalamount}
数据,使用“账户”和“预期金额”来指代
{acctnum, expectprice, payorder}
数据。 「分配金额」是计算结果。

对于单笔付款,可以通过首先将付款金额减去可能从具有较早付款订单的帐户分配的最大金额来计算每个帐户的分配金额。该调整只是运行 SUM() 直到紧邻的前一行。然后,调整后的值将被限制在当前行的范围

[0, expectprice]
内。如果是负数,我们就没有什么可以分配的了。如果超过
expectprice
,我们将分配全部
expectprice
金额,并且仍有一些剩余部分供以后分配。

问题是这只适用于单次支付。如果我们尝试进行多次支付,则每次都会重置数字,并且我们会继续从相同的帐户分配相同的金额,而不会对已分配或耗尽的帐户进行调整。

解决这个问题很棘手。我们需要首先调整支付金额,使其包含所有之前的支付金额。实际上是支付金额的当前运行总额。现在我们将之前讨论的分配算法应用于当前的“调整后”支出。这将迫使分配超过之前的高水位线并分配新资金 "这很好,但我们� 75e1 ��然有一个问题。"当前分配还会重新分配所有先前分配的账户资金。我们该如何解决这个问题?答案出人意料地简单(也许是这样)——我们只需对之前的累计支付金额执行相同的分配计算,然后从当前的计算中减去这些金额。这将只留下当前的分配。

生成的代码是:

WITH CTE_Payout AS ( SELECT *, SUM(totalamount) OVER(ORDER BY acctid, payseq) AS CummulativeTotalAmount, ISNULL(SUM(totalamount) OVER( ORDER BY acctid, payseq ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING ), 0) AS PriorCummulativeTotalAmount FROM ( SELECT DISTINCT acctid, payseq, totalamount FROM Data ) P ), CTE_Acct AS ( SELECT *, SUM(expectprice) OVER(ORDER BY payorder) AS CummulativeExpectedPrice, ISNULL(SUM(expectprice) OVER( ORDER BY payorder ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING ), 0) AS PriorCummulativeExpectedPrice FROM ( SELECT DISTINCT acctnum, expectprice, payorder FROM Data ) A ) SELECT P.acctid, P.payseq, P.totalamount, A.acctnum, A.expectprice, A.payorder, LEAST(GREATEST( P.CummulativeTotalAmount - A.PriorCummulativeExpectedPrice, 0), A.expectprice) - LEAST(GREATEST( P.PriorCummulativeTotalAmount - A.PriorCummulativeExpectedPrice, 0), A.expectprice) AS paidamount, -- The following just show the working calculations P.CummulativeTotalAmount, LEAST(GREATEST( P.CummulativeTotalAmount - A.PriorCummulativeExpectedPrice, 0), A.expectprice) AS CurrentCummulativeAllocation, P.PriorCummulativeTotalAmount, LEAST(GREATEST( P.PriorCummulativeTotalAmount - A.PriorCummulativeExpectedPrice, 0), A.expectprice) AS PriorCummulativeAllocation FROM CTE_Payout P CROSS APPLY CTE_Acct A ORDER BY acctid, payseq, payorder

请参阅 
this db<>fiddle

进行演示。

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