循环和更新而无需光标

时间:2018-11-25 22:40:50

标签: sql sql-server database query-optimization

我有一个表,用于存储项目余额。

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

该表可能包含相同的商品代码,但成本不同。这段代码对于一次更新几个项目来说是可以的,但是每当有很多项目/数量被发送出去时,过程就会变得很慢。有没有一种方法可以优化此代码?我猜光标正在变慢,所以我想为此过程探索一个不同的代码。

2 个答案:

答案 0 :(得分:0)

看起来您只需要一个简单的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; 

答案 1 :(得分:0)

您对于该机制的要求方式和作用方式以及原因尚不清楚。

但是,假设您必须拥有多笔余额未清的记录,并且必须顺序考虑多条记录作为该机制的一部分,那么您可以通过两种方法在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更新。