2012-07-23 223 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