我正在尝试找到一种更有效的方法来实现这一点,因为我已经走了光标路线并且讨厌在SQL中使用游标时遇到的性能损失。我正在尝试分配有价值物品的付款并跟踪任何剩余金额。例如......
Payments
--------
10,
20
Items
------
5,
5,
10
本质上会返回第一笔付款(10)适用于前两项并且已经用尽。第二笔付款(20)适用于第三个项目,剩下10个。我可以使用游标完成这个任务。只是好奇是否有人对如何更有效地做到这一点有一些想法。
干杯!
答案 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