分发值SQL

时间:2012-09-05 11:58:07

标签: sql sql-server tsql

我正在尝试找到一种更有效的方法来实现这一点,因为我已经走了光标路线并且讨厌在SQL中使用游标时遇到的性能损失。我正在尝试分配有价值物品的付款并跟踪任何剩余金额。例如......

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

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

本质上会返回第一笔付款(10)适用于前两项并且已经用尽。第二笔付款(20)适用于第三个项目,剩下10个。我可以使用游标完成这个任务。只是好奇是否有人对如何更有效地做到这一点有一些想法。

干杯!

4 个答案:

答案 0 :(得分: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)。

答案 1 :(得分: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
    }
}

答案 2 :(得分: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 

(当然使用原子事务)

答案 3 :(得分: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