无游标循环和更新

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 执行存储过程 无法中断 但是是循环执行 怎么办

MySQL 使用游标触发并循环更新错误值

sqlserver中游标循环中只更新当前行的方法

在使用游标时,使用 Oracle SQL 更新记录会导致无限循环

循环遍历带有更新字段条件的游标 [PLSQL]