2012-09-05 40 views
0

我試圖找到一個更有效的方式來做到這一點,因爲我已經走了遊標路線,並且討厭我在SQL中使用遊標時遇到的性能問題。我試圖在有價值的物品之間分配付款,並跟蹤任何剩餘金額。例如...分配值SQL

Payments    
-------- 
10, 
20 

Items 
------ 
5, 
5, 
10 

實質上會返回第一個付款(10)適用於前兩個項目,並用盡。第二項付款(20)適用於第三項,剩下10項。我能夠使用遊標完成這項工作。只是好奇,如果有人對如何更有效地做到這一點有一些想法。

乾杯!

+1

向我們展示它是什麼樣子與你的光標,我們可以看到,如果事情可以更好地工作... – LaGrandMere

+0

你能不能請註明您的問題。看到你的光標代碼來解釋你的問題也是有用的。 –

+0

何時付款「已用盡」?包裝問題通常並不重要 - 這與使用遊標無關。 – Lucero

回答

1

假設你有一組有序的ID爲支付和物品,你可以創建一個項目來支付的映射表,然後做這樣的事情(見http://sqlfiddle.com/#!3/4b6f8/4):

-- Populate mapping table 
INSERT INTO PaymentsForItems (ItemID, PaymentID) 
SELECT ItemID, 
     (SELECT MIN(PaymentID) 
     FROM Payments p1 
     WHERE (SELECT SUM(ItemValue) 
       FROM Items i2 
       WHERE i2.ItemID <= i1.ItemID) <= 
       (SELECT SUM(PaymentValue) 
       FROM Payments p2 
       WHERE p2.PaymentID <= p1.PaymentID)) 
FROM Items i1; 

這並沒有顯示餘數 - 不知道你想如何表示這個,但可以很容易地分開插入一行(例如用NULL作爲ID)。

+0

爲了進一步提高性能,您還可以考慮在兩個表中包含「累計總數」列,但是每當數據更改時都需要更新(可能通過觸發器?)。 –

+0

這種方法更符合我所尋找的內容(如果可以的話,我會加倍努力)...我已經在表中列出了所有付款和物品信息。重組表格/添加觸發器對我來說不是一個真正的選擇。我想在存儲過程中把所有東西都包裝起來。 –

0

而不是光標,爲什麼你沒有得到你所有的物品和付款,並在你的代碼中工作?

它可能會更快,你將不得不擱置這2種方式。

在Java:

PreparedStatement psItems = con.prepareStatement("SELECT itemId,item FROM items"); 
PreparedStatement psPayments = con.prepareStatement("SELECT payId,payment FROM payments"); 
ResultSet rsItems = psItems.executeQuery(); 
ResultSet rsPayments = psPayments.executeQuery(); 

int currPaymentLeft = 0; 
int currentPayId = 0; 
int currentItemId = 0; 

while(rsItems.next()) { 
    int priceItem = rsItems.getInt("item"); 
    currentItemId = rsItems.getInt("itemId"); 
    if (currPayment < priceItem) { 
     // Here we are in the case where the last payment was over the items it bought. 
     // Except if currPayment is 0. In this case, there is no rest. 
     if (currPayment > 0) { 
      // This is the rest case 
      // In currentPayId, we have the Id of a Payment 
      // where there is currPayment left which will be unused. 
      // Do whatever you want with this Id and amount. 
     } 

     currPayment = 0; 
     while(rsPayments.next()) { 
      currPaymentLeft = rsItems.getInt("payment"); 
      int payId = rsItems.getInt("payId"); 
      if(payment >= priceItem) { 
       currPaymentLeft -= priceItem; 
       // Link currentPayId with currentItemId 
       // Create a query, store it in a collection, wo what you want. 

       // Get out of this while loop 
       break; 
      } 
      // else, the payment is less than the priceItem 
      // so this payment won't be of any use. 
      // This is a total rest case 
      // In currentPayId, we have the Id of a Payment 
      // where there is currPayment left which will be unused. 
      // Do whatever you want with this Id and amount. 
     } 
    } 
    else { 
     currPaymentLeft -= priceItem; 
     // Link currentPayId with currentItemId because itemId was paid with currentPayId 
    } 
} 
+1

這就是我正在努力與我想?我在思考如何在Java或VB中攻擊這個問題,並且我需要更多的SQL優化路線。 –

+0

@CaptainHammer:用Java中的double while循環更新了我的帖子,並且在評論所有不同的情況下,您存儲哪些付款支付了哪個項目,以及哪裏有一些休息,哪些是您想要的。 – LaGrandMere

0

據我所知,你有兩個截然不同但相似的事件 - 收到新的付款,並創建一個新的成本項目。在每個時刻,您需要更新另一個表,因此看起來需要存儲過程或觸發器,具體取決於您的插入機制。

創建新項目時,您可以找到未付款項的最早付款,然後將其減少該項目的成本。

同樣,創建新付款時,您按順序分配金額(@amount)。

update items 
set 
    paid = 
     case when @amount>=runningTotal then items.amount 
     else 
      case when @amount - (runningTotal - (amount-paid))>0 
       then @amount - (runningTotal - (amount-paid)) 
       else 0 
      end 
     end   
from 
    items 
cross apply 
    (
     select sum(amount-paid) as runningTotal 
     from items 
     where id <= items.id 
    ) as rt 

(當然使用原子事務)

0

我測試了這段代碼與SQL Server 2005,並且它似乎確定。 希望它可以幫助一些。

WITH tItems 
AS 
(
SELECT A.ItemId, A.ItemValue, (SELECT COALESCE(SUM(B.ItemValue), 0) 
           FROM Items B 
           WHERE B.ItemId < A.ItemId) AS PrevItemTotal, 

           (SELECT COALESCE(SUM(C.ItemValue), 0) 
           FROM Items C 
           WHERE C.ItemId <= A.ItemId) AS CurrItemTotal 
FROM Items A 
), 
tPayments 
AS 
(
SELECT A.PaymentId, A.PaymentValue, (SELECT COALESCE(SUM(B.PaymentValue), 0) 
            FROM Payments B 
            WHERE B.PaymentId < A.PaymentId) AS PrevPaymentTotal, 

            (SELECT COALESCE(SUM(C.PaymentValue), 0) 
            FROM Payments C 
            WHERE C.PaymentId <= A.PaymentId) AS CurrPaymentTotal 
FROM Payments A 
), 
tDistribution 
AS 
(
SELECT *, 
CASE 
    WHEN PrevPaymentTotal - PrevItemTotal <= 0 THEN 
     CASE 
      WHEN PaymentValue - (PrevItemTotal-PrevPaymentTotal) <= ItemValue THEN PaymentValue - (PrevItemTotal-PrevPaymentTotal) 
      ELSE ItemValue 
     END 
    ELSE -- PrevPaymentTotal - PrevItemTotal > 0 
     CASE 
      WHEN ItemValue - (PrevPaymentTotal - PrevItemTotal) < PaymentValue THEN ItemValue - (PrevPaymentTotal - PrevItemTotal) 
      ELSE PaymentValue 
     END 
END AS Distribution 
FROM tItems X, tPayments Y 
WHERE Y.CurrPaymentTotal > X.PrevItemTotal AND Y.PrevPaymentTotal < X.CurrItemTotal 
) 

SELECT ItemId, ItemValue, PaymentId, PaymentValue, Distribution, 
ItemValue - SUM (Distribution) OVER (PARTITION BY ItemId) AS Remaining 
FROM tDistribution 
+0

感謝您編輯代碼,使它看起來更好,Bummi。我是新來的論壇,只想快速發佈解決方案來幫助有需要的人,對於這個爛攤子感到抱歉。 –