0

我正在尝试找到一种更有效的方法来执行此操作,因为我已经走了游标路线,并且讨厌在 SQL 中使用游标所带来的性能损失。我正在尝试在有价值的项目之间分配付款并跟踪任何剩余金额。例如...

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

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

实质上将返回第一笔付款 (10) 应用于前两项并已用尽。第二笔付款(20)适用于第三项,还剩 10 笔。我可以使用游标完成这项工作。只是好奇是否有人对如何更有效地做到这一点有一些想法。

干杯!

4

4 回答 4

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)。

于 2012-09-05T12:53:38.190 回答
0

您为什么不获取所有项目和付款,而不是光标,并在您的代码中处理它们?

它可能会更快,您必须采用两种方法来做到这一点。

在 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
    }
}
于 2012-09-05T12:25:44.557 回答
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 

(当然使用原子事务)

于 2012-09-05T13:22:53.660 回答
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
于 2014-11-06T10:04:35.207 回答