这里有一个递归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,则查询实际上会检索下一个支付和下一个存储桶。)然后,将与第一次迭代相同的逻辑应用于新的存储桶数量和支付值。
迭代持续进行,直到没有更多桶或没有更多付款。或直至达到100默认MAXRECURSION值,在这种情况下,你可能想
OPTION (MAXRECURSION n)
追加到上面的查询,其中n
必须是一个非负整数高达32767规定的最大数量迭代(递归)。 (注意:0
实际上代表无限)
你可以试试这个查询at SQL Fiddle。
你的桶表中是否有任何表示顺序的东西?否则你的结果几乎没有定义。另外,你的数据类型是什么,你需要担心随机舍入问题吗? – 2012-07-23 22:59:55
@ X-Zero - 桶表中没有订单。只要数量永远不会超过每个桶的最大数量,并且总价值被分配给一个或多个桶,那么对桶的任何付款分配都可以。舍入问题绝对重要。数据类型为十进制(18,6),但是对第三位十进制数字进行舍入。 – lsoliveira 2012-07-23 23:07:24
您可能需要阅读SQL CLR,并查看它是否比纯Transact-SQL解决方案更易于访问。 – 2012-07-23 23:37:53