如何使T-SQL游标更快?

时间:2009-05-13 17:52:09

标签: sql sql-server cursor

嘿,我在SQL Server 2000下的存储过程中有一个游标(现在无法更新),它会更新所有表,但通常需要几分钟才能完成。我需要让它更快。这是由任意产品ID过滤的示例表; Example table http://img231.imageshack.us/img231/9464/75187992.jpg 鉴于GDEPO:入境仓库,CDEPO:退出仓库,Adet:数量,E_CIKAN使用的数量。

记录解释:
1:20单位进入仓库01, 2:10单位离开01。 3:5单位离开01(第一记录的E_CIKAN现在为15) 4:10更多单位进入仓库01。 5:3单位从第1记录离开01。现在注意第一条记录的E_CIKAN设置为18。 6:这就是问题所在:3个单位需要离开仓库01.从第1个记录需要2个单位,从第5个记录需要1个单位。我的SP可以处理这种情况,如图所示,除非它真的很慢。

这是存储过程翻译成英文;

CREATE PROC [dbo].[UpdateProductDetails]
as
UPDATE PRODUCTDETAILS SET E_CIKAN=0;
DECLARE @ID int
DECLARE @SK varchar(50),@DP varchar(50)  --SK = STOKKODU = PRODUCTID, DP = DEPOT
DECLARE @DEMAND float     --Demand=Quantity, We'll decrease it record by record
DECLARE @SUBID int
DECLARE @SUBQTY float,@SUBCK float,@REMAINS float
DECLARE SH CURSOR FAST_FORWARD FOR
SELECT [ID],PRODUCTID,QTY,EXITDEPOT FROM PRODUCTDETAILS  WHERE (EXITDEPOT IS NOT NULL) ORDER BY [DATE] ASC
OPEN SH
FETCH NEXT FROM SH INTO @ID, @SK,@DEMAND,@DP

WHILE (@@FETCH_STATUS = 0)
BEGIN
   DECLARE SA CURSOR FAST_FORWARD FOR
   SELECT [ID],QTY,E_CIKAN FROM PRODUCTDETAILS  WHERE (QTY>E_CIKAN) AND (PRODUCTID=@SK) AND (ENTRYDEPOT=@DP) ORDER BY [DATE] ASC
   OPEN SA
   FETCH NEXT FROM SA INTO @SUBID, @SUBQTY,@SUBCK
   WHILE (@@FETCH_STATUS = 0) AND (@DEMAND>0)
   BEGIN
      SET @REMAINS=@SUBQTY-@SUBCK
      IF @DEMAND>@REMAINS  --current record isnt sufficient, use it and move on
      BEGIN
         UPDATE PRODUCTDETAILS SET E_CIKAN=QTY WHERE ID=@SUBID;
         SET @DEMAND=@DEMAND-@REMAINS
      END
      ELSE
      BEGIN
         UPDATE PRODUCTDETAILS SET E_CIKAN=E_CIKAN+@DEMAND WHERE ID=@SUBID;
         SET @DEMAND=0
      END
      FETCH NEXT FROM SA INTO @SUBID, @SUBAD,@SUBCK
   END
   CLOSE SA
   DEALLOCATE SA
   FETCH NEXT FROM SH INTO @ID, @SK,@DEMAND,@DP
END
CLOSE SH
DEALLOCATE SH

6 个答案:

答案 0 :(得分:10)

根据我在对这个问题的其他答案中的对话,我想我找到了加快日常工作的方法。

你有两个嵌套游标:

  • 第一个是选择指定了exitdepot的每一行。它需要产品,depo和数量,然后:
  • 内部游标循环遍历指定了entrydepot的产品/库的行。它为每个产品添加了E_CIKAN,直到它分配了所有产品。

因此内部游标循环对于您拥有的每个exitdepot行至少运行一次。但是,您的系统并不关心哪些项目与哪个事务有关 - 您只是想计算最终的E_CIKAN值。

所以......

您的外部循环只需获得每个产品/仓库组合的数量。因此,您可以将外部游标定义更改为:

DECLARE SH CURSOR FAST_FORWARD FOR
    SELECT PRODUCTID,EXITDEPOT, Sum(Qty) as TOTALQTY
    FROM PRODUCTDETAILS  
    WHERE (EXITDEPOT IS NOT NULL) 
    GROUP BY PRODUCTID, EXITDEPOT
OPEN SH
FETCH NEXT FROM SH INTO @SK,@DP,@DEMAND

(然后还要在代码末尾从SH更改匹配的FETCH以匹配,显然)

这意味着你的外部游标将循环通过更少的行,并且你的内部游标将具有相同数量的行来循环。

所以这应该更快。

答案 1 :(得分:2)

使用T-SQL时,游标必须是解决任何问题的最差解决方案。

根据您真正想要完成的事情的复杂性,您有两种选择:

  1. 尝试重写整个代码集以使用set操作。这将是执行速度最快的方法......但有时您无法使用集合操作来执行此操作。

  2. 将光标替换为表变量(带标识列),计数器和while循环的组合。然后,您可以循环遍历表变量的每一行。执行比光标更好......即使它看起来不像它。

答案 2 :(得分:1)

删除游标并将其重写为加入游标查询的UPDATE FROM,如果需要,可以将IF作为一个案例。今天我太忙了,今天给你写UPDATE ......

答案 3 :(得分:1)

删除光标并进行批量更新。我还没有找到一个无法批量完成的更新。

答案 4 :(得分:1)

首先,如果你必须使用游标,并且你正在更新东西,那么使用FOR UPDATE子句声明游标。 (请参阅下面的示例。请注意,该示例根本不基于您的代码。)

话虽如此,有许多方法可以使用游标之外的其他东西,通常利用临时表。我会调查这条路线而不是游标。

DECLARE LoopingCursor CURSOR LOCAL DYNAMIC
FOR
    select sortorder from customfielddefinition
    where context=@targetContext
FOR UPDATE OF sortorder

答案 5 :(得分:1)

我可以看到你试图解决的问题非常复杂:

  • 当指定了GDEPO的行时,它表示进入depo的库存,并且您希望使用该行的E_CIKAN来跟踪以后使用的库存量。 E_CIKAN将从0开始,然后在股票熄灭时加入 - 直到达到ADET。

  • 因此,当指定了CDEPO的后续行时,它会重新显示库存中断,并且您希望返回到GDEPO行的E_CIKAN并调整E_CIKAN,方法是将库存量添加到它

  • 当有两个单独的行进入库存(指定GDEPO)时,有时当一行的E_CIKAN达到最大值(ADET)时会出现溢出,然后您希望将剩余部分添加到下一行

这是一个非常棘手的计算,因为您必须比较不同的行并返回并更改一行或两行中的值以跟踪每个股票交易。

正如其他人所建议的那样,可能是一种没有光标的方法。但我认为如果你可以重新安排你的表并以不同的方式存储数据,你可能会使问题更容易。

例如,您可以在一个单独的表中使用'Product_id,Depo_id,amount'列来跟踪每个depo中每个产品的总量,而不是跟踪记录库存交易的同一个表中的库存在一次?

这样的数据库设计更改可以使事情变得更容易。

或者......而不是使用E_CIKAN来跟踪的使用,使用它来跟踪剩下的。并在每行中保留E_CIKAN值。所以每当股票进出depo时,在那个时间点重新计算E_CIKAN 并将其存储在该交易行中(而不是试图回到原来的'库存'行'在那里更新)。然后,要查看当前的库存,您只需查看该产品/ depo的最新交易。

总而言之,我所说的是,由于您以奇怪的方式存储数据,因此计算速度慢而且繁琐。从长远来看,可能值得更改数据库设计以使编程更容易。