无游标循环和更新
Posted
技术标签:
【中文标题】无游标循环和更新【英文标题】:Loop and update without cursor 【发布时间】:2018-11-25 22:40:49 【问题描述】:我有一个存储项目余额的表。
CREATE TABLE itembalance (
ItemID VARCHAR(15),
RemainingQty INT,
Cost Money,
Id INT
)
我需要确保每次发出物品时,都会从 itembalance 表中扣除适当的余额。我是这样做的:
DECLARE crsr CURSOR LOCAL FAST_FORWARD FOR
SELECT
itembalance.Cost,
itembalance.RemainingQty
itembalance.Id
FROM dbo.itembalance
WHERE itembalance.ItemID = @v_item_to_be_updated AND RemainingQty > 0
OPEN crsr
FETCH crsr
INTO
@cost,
@qty,
@id
WHILE @@FETCH_STATUS = 0
BEGIN
IF @qty >= @qty_to_be_deducted
BEGIN
UPDATE itembalance SET RemainingQty = RemainingQty - @qty_to_be_deducted WHERE Id = @id
/*do something with cost*/ BREAK
END
ELSE
BEGIN
UPDATE itembalance SET RemainingQty = 0 WHERE Id = @id
/*do something with cost*/ SET @qty_to_be_deducted = @qty_to_be_deducted - @qty
END
FETCH crsr
INTO
@cost,
@qty,
@id
END
CLOSE crsr
DEALLOCATE crsr
该表可能包含相同的项目代码,但成本不同。此代码适用于一次更新的少量项目,但每当发送大量项目/数量时,该过程变得非常缓慢。有没有办法优化这段代码?我猜光标让它变慢了,所以我想为这个过程探索不同的代码。
【问题讨论】:
你怎么称呼那个 T-SQL?从触发器?还是来自客户端应用程序? 根据您的代码编写方式,您会看到单个 ItemId 有许多 ItemBalance 记录。但是你怎么知道它们的顺序是什么?您需要多条记录(历史)还是一条记录(当前状态)? 以前,它是在触发器上。我现在将其移至另一个应用程序,该应用程序将完成所有后端工作。 相同的项目可能有不同的成本。此循环将更新发送记录的成本。 您是否偶然简化了您的 SQL?因为您上面的代码正在选择具有剩余数量的选定 ItemId 的所有记录。没有基于成本的选择或更新,并且游标中处理记录的顺序是随机的,这看起来很奇怪。 【参考方案1】:这看起来你只需要一个简单的CASE
表达式:
UPDATE dbo.itembalance
SET Qty = CASE WHEN Qty >= @qty_to_be_deducted THEN Qty - @qty_to_be_deducted ELSE 0 END
WHERE ItemID = @v_item_to_be_updated
--What is the difference between Qty and RemainingQty?
--Why are you checking one and updating the other?
AND RemainingQty > 0;
【讨论】:
如果我要在这里使用一个案例,它将更新 itembalance 表中具有相同项目的所有记录。因此,如果我多次写入项目“A”,每次 100 个数量,而我只想扣除 20 个,它们都会被更新,这是我不希望发生的。我只需要扣除正在发出的数量。【参考方案2】:您的代码不太清楚该机制是如何以及为什么需要和工作的。
但是,假设您必须有多个未结余额的记录,并且您必须按顺序考虑多个记录作为此机制的一部分,那么您有两个选项可以在 SQL 中解决这个问题(在客户端代码中处理是另一个选项):
1) 像以前一样使用光标
2) 使用临时表或表变量并对其进行迭代 - 非常类似于游标,但可能更快 - 您必须尝试查看例如
declare @TableVariable table (Cost money, RemainingQty int, Id int, OrderBy int, Done bit default(0))
declare @Id int, @Cost money, @RemainingQty int
insert into @TableVariable (Cost, RemainingQty, Id, OrderBy)
SELECT
itembalance.Cost
, itembalance.RemainingQty
, itembalance.Id
, 1 /* Some order by condition */
FROM dbo.itembalance
WHERE itembalance.ItemID = @v_item_to_be_updated AND RemainingQty > 0
while exists (select 1 from @TableVariable where Done = 0) begin
select top 1 @Id = id, @Cost = Cost, @RemainingQty
from @TableVariable
where Done = 0
order by OrderBy
-- Do stuff here
update @TableVariable set Done = 1 where id = @Id
end
但是,您显示的代码似乎并不应该很慢 - 因此可能是您缺少适当的索引,并且单个 ItemId 更新锁定了 ItemBalance 表中的太多行,这会影响其他 ItemId 更新。
【讨论】:
将数据移动到临时表会影响结果,例如多个用户一次访问同一个项目? 是的,但是游标也是如此……您需要锁定所有可能在持续时间内更新的行。这个问题很可能有更好的整体设计。以上是关于无游标循环和更新的主要内容,如果未能解决你的问题,请参考以下文章
oracle 执行存储过程 无法中断 但是是循环执行 怎么办