这是适合您的递归 CTE 方法:
WITH BucketsRanked AS (
SELECT *, rnk = ROW_NUMBER() OVER (ORDER BY BucketId) FROM Buckets
)
, PaymentsRanked AS (
SELECT *, rnk = ROW_NUMBER() OVER (ORDER BY PaymentId) FROM Payments
)
, PaymentsDistributed AS (
SELECT
b.BucketId,
p.PaymentId,
Bucket = b.MaxAmount,
Payment = p.Value,
BucketRnk = b.rnk,
PaymentRnk = p.rnk,
BucketPayment = CASE
WHEN p.Value > b.MaxAmount
THEN b.MaxAmount
ELSE p.Value
END,
CarryOver = p.Value - b.MaxAmount
FROM
BucketsRanked b,
PaymentsRanked p
WHERE b.rnk = 1 AND p.rnk = 1
UNION ALL
SELECT
b.BucketId,
p.PaymentId,
Bucket = b.MaxAmount,
Payment = p.Value,
BucketRnk = b.rnk,
PaymentRnk = p.rnk,
BucketPayment = CASE
WHEN x.PaymentValue > x.BucketValue
THEN x.BucketValue
ELSE x.PaymentValue
END,
CarryOver = x.PaymentValue - x.BucketValue
FROM PaymentsDistributed d
INNER JOIN BucketsRanked b
ON b.rnk = d.BucketRnk + CASE SIGN(CarryOver) WHEN -1 THEN 0 ELSE 1 END
INNER JOIN PaymentsRanked p
ON p.rnk = d.PaymentRnk + CASE SIGN(CarryOver) WHEN +1 THEN 0 ELSE 1 END
CROSS APPLY (
SELECT
CONVERT(
decimal(18,6),
CASE SIGN(CarryOver) WHEN -1 THEN -d.CarryOver ELSE b.MaxAmount END
),
CONVERT(
decimal(18,6),
CASE SIGN(CarryOver) WHEN +1 THEN +d.CarryOver ELSE p.Value END
)
) x (BucketValue, PaymentValue)
)
SELECT
BucketId,
PaymentId,
Bucket,
Payment,
BucketPayment
FROM PaymentsDistributed
;
基本上,这个查询接受第一笔付款和第一个桶,找出哪个少并产生第一个BucketPayment
项目。支付值和桶容量之间的差值会被记住,以便在下一次迭代中使用。
在下一次迭代中,差额(取决于其符号)被用作存储桶金额或付款。此外,根据差异的符号,查询要么从Payments
表中获取下一笔付款,要么从Buckets
. (但如果差值为 0,则查询实际上检索下一笔付款和下一个桶。)然后将与第一次迭代相同的逻辑应用于新的桶金额和付款值。
迭代一直持续到没有更多的桶或没有更多的付款为止。或者直到达到默认的 MAXRECURSION 值 100,在这种情况下,您可能需要追加
OPTION (MAXRECURSION n)
对于上述查询,其中n
必须是最大为 32767 的非负整数,指定最大迭代次数(递归)。(请注意,0
这实际上代表无限。)
您可以在 SQL Fiddle尝试此查询。