2012-07-23 157 views
3

我正在编写一个SQLServer 2008存储过程,该过程需要一张付款表并尝试根据相关表中描述的一组规则(基本上是一组存储桶)来分配这些付款。但是,分配(将支付价值放入一个桶中)是目前令我头痛的原因。SQL累积值

比方说,支付表中包含支付的价值和表格。桶是关于应该投入每个存储桶多少钱,直到要支付的初始值用尽(达到0)。

使用下面的表作为示例(实际使用的情况下是作为有一些复杂的标准来选择哪一个是适合于每个付款桶略偏做作):

PaymentId  Value     BucketId  MaxAmount 
--------------------------   -------------------------------- 
1    16.5     1    5.5 
2    7.0     2    10 
            3    8.3 

对于付款1:5.5个单位(该桶的最大值)应进入桶1,桶2中有10个单元(11.5是来自桶1的剩余量,但仍超过桶2的最大值),应将1个单元(16.5-5.5-10)放入桶中3.重复所有付款。

这很容易在任何命令式语言中实现,甚至可能在SQL中使用for/while循环,但我试图了解是否有更好的方法(即使它不可移植且特定于SQLServer 2005+)。

我已经做了一些研究(主要是递归CTE),但没有什么真正的辉煌想到。我确信有很多StackOverflowers与SQL-fu来回答他们的头,所以我想把它放在那里,看看...

非常感谢您的帮助。

+0

你的桶表中是否有任何表示顺序的东西?否则你的结果几乎没有定义。另外,你的数据类型是什么,你需要担心随机舍入问题吗? – 2012-07-23 22:59:55

+0

@ X-Zero - 桶表中没有订单。只要数量永远不会超过每个桶的最大数量,并且总价值被分配给一个或多个桶,那么对桶的任何付款分配都可以。舍入问题绝对重要。数据类型为十进制(18,6),但是对第三位十进制数字进行舍入。 – lsoliveira 2012-07-23 23:07:24

+0

您可能需要阅读SQL CLR,并查看它是否比纯Transact-SQL解决方案更易于访问。 – 2012-07-23 23:37:53

回答

3

我尝试不使用光标:

DECLARE @Buckets TABLE (
    BucketId INT, 
    MaxAmount DECIMAL(18,6) 
) 

INSERT INTO @Buckets VALUES (1, 5.5) 
INSERT INTO @Buckets VALUES (2, 10) 
INSERT INTO @Buckets VALUES (3, 8.3) 

DECLARE @Payments TABLE (
    PaymentId INT, 
    Value DECIMAL(18,6) 
) 

INSERT INTO @Payments VALUES (1,16.5) 
INSERT INTO @Payments VALUES (2,7.0) 

SELECT 
    P1.PaymentId 
, P1.Value as TotalPayment 
, B4.BucketId 
, B4.MaxAmount 
, CASE WHEN B3.BucketId = B4.BucketId THEN P1.Value - MaxAmountRunningTotalOfPreviousBuckets ELSE B4.MaxAmount END AS BucketPaymentAmount 
FROM @Payments P1 
INNER JOIN (
    SELECT 
     B2.BucketId 
    , B2.MaxAmount as BucketMaxAmount 
    , SUM(B1.MaxAmount) - B2.MaxAmount as MaxAmountRunningTotalOfPreviousBuckets 
    FROM @Buckets B1 
    INNER JOIN @Buckets B2 
     ON B1.BucketId <= B2.BucketId 
    GROUP BY B2.BucketId, B2.MaxAmount 
) AS B3 
    ON P1.Value > B3.MaxAmountRunningTotalOfPreviousBuckets AND P1.Value <= (B3.MaxAmountRunningTotalOfPreviousBuckets + BucketMaxAmount) 
INNER JOIN @Buckets B4 
    ON B4.BucketId <= B3.BucketId 
ORDER BY P1.PaymentId, B3.BucketId 
+0

对于一个案例+1并且不使用慢速光标。 3个答案,还有一个仍然停留在DBase时间......做得很好。我希望我有一个+2,你甚至不使用临时表。 – TomTom 2012-07-24 05:02:44

+0

非常好!这正是我所拍摄的。对不起,最初不要发布DDL。桶表通常很小(它是更大表的一个小子集),使用table变量实际上与我的设置一致。我仍然需要检查其性能,但现在,非常感谢! – lsoliveira 2012-07-24 10:00:47

+0

@Gulli Meel的答案与你的答案基本相同,但阅读起来更简单一点(性能明智,我必须检查)。仍然接受你的答案,因为它是第一个可以解决问题的答案。 – lsoliveira 2012-07-24 10:08:43

5

把你的水桶表到一个临时表,然后有一个叫做总运行一个额外的列。这将有运行总和,直到这个加入,然后交叉加入付款和tempbucket表,并指定付款< =运行totalin tempbucket表中的条件。这应该可以解决你的问题。 我用Mafu Josh的DDL创建了以下查询,因此感谢他。我希望OP应该总是发布这些东西让其他人更容易生活。

它看起来像buckte表相当small.But如果是非常大的table.Then生成运行总使用与variables.That更新的效率比低于method.To我听起来,这表是或多或少的静态表,因此您可以将总计作为表本身的一部分。

DECLARE @Buckets TABLE ( 
    BucketId INT, 
    MaxAmount DECIMAL(18,6) 
) 

INSERT INTO @Buckets VALUES (1, 5.5) 
INSERT INTO @Buckets VALUES (2, 10) 
INSERT INTO @Buckets VALUES (3, 8.3) 

DECLARE @Payments TABLE ( 
    PaymentId INT, 
    Value DECIMAL(18,6) 
) 

INSERT INTO @Payments VALUES (1,16.5) 
INSERT INTO @Payments VALUES (2,7.0) 
INSERT INTO @Payments VALUES (3,23.8) 

DECLARE @tempBuckets TABLE ( 
    BucketId INT, 
    MaxAmount DECIMAL(18,6) , 
    currentruntotal decimal(18,6) 
) 
insert into @tempBuckets select bucketid,maxamount ,(select SUM(maxamount) from @Buckets bin where b.bucketid >=bin.bucketid) 
--,isnull((select SUM(maxamount) from @Buckets bin where b.bucketid > bin.bucketid),0) 
from @Buckets b 

select * from @tempBuckets 
select PaymentId,Value,BucketId, 
case when p.Value >= tb.currentruntotal then tb.MaxAmount else p.Value - tb.currentruntotal + tb.MaxAmount end as bucketamount 
from @Payments p inner join @tempBuckets tb on (p.Value >= tb.currentruntotal or p.Value between tb.currentruntotal - tb.MaxAmount and tb.currentruntotal) 
order by PaymentId 
go 
+0

+1不使用游标,并允许SQL Server实际优化不同的步骤。 – TomTom 2012-07-24 05:01:47

+0

+1用于清理Mafu Josh的答案,但由于查询实际上是他的,我会接受他的回答(尽管添加了一条引用您的答案的评论)。 – lsoliveira 2012-07-24 10:05:11

+0

没有问题......但我会建议,如果桶表很小,并没有太大的变化保持运行总列,然后查询将只是连接2个表之间。 – 2012-07-24 10:06:34

2

这里有一个递归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

+0

我将不得不更详细地研究您的解决方案,但它似乎试图将所有付款的总和分配到可用存储区空间中。然而,所需要的是每个支付都要填充桶(对于上面的支付2,桶存储仍然是所有桶的总和)。对不起,如果不够清楚。虽然,这个问题的解决方案很好。让我们看看我是否可以将它适应我的要求(这样我可以将其性能与@Gulli Meel的解决方案进行比较)。 – lsoliveira 2012-07-24 09:52:17

+0

你是完全正确的,那确实是我如何理解你的问题(分配所有支出的总和)。对不起,我不能更有帮助。至于我,我很乐意解决我读到你的问题,所以无论如何都要感谢你的问题。 :) – 2012-07-24 09:56:23

+0

很高兴你没有发现它浪费时间。 ;-) – lsoliveira 2012-07-24 10:15:13