Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a T-SQL Cursor faster?

I Have a cursor in stored procedure under SQL Server 2000 (not possible to update right now) that updates all of table but it usually takes few minutes to complete. I need to make it faster.

Whereas GDEPO:Entry depot, CDEPO:Exit depot,Adet: quantity,E_CIKAN quantity that's used.

Record explanations:

  1. 20 unit enters depot 01,
  2. 10 unit leaves 01.
  3. 5 Unit leaves 01 (E_CIKAN for 1st record will be 15 now)
  4. 10 more unit enters depot 01.
  5. 3 unit leaves 01 from 1st record. Notice now 1st record has E_CIKAN set to 18.
  6. This is where the problem comes in: 3 unit needs to leave depot 01. It takes 2 unit from 1st record and 1 unit from 5th record. My SP can handle this fine as seen in picture, except it's REALLY slow.

Here's the stored procedure translated into English;

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
like image 726
Ertugrul Kara Avatar asked May 13 '09 17:05

Ertugrul Kara


People also ask

Why are SQL cursors so slow?

This is because the set-based logic for which RDBMS systems like SQL Server are optimized is completely broken and the entire query process has to be repeated for each row.

Does cursor affect performance?

Cursors could be used in some applications for serialized operations as shown in example above, but generally they should be avoided because they bring a negative impact on performance, especially when operating on a large sets of data.


1 Answers

Based on our conversation in my other answer to this question, I think I have found a way to speed up your routine.

You have two nested cursors:

  • The first one is selecting each row that has an exitdepot specified. It takes the product, depo and amount, and then:
  • The inner cursor loop runs through the rows for that product/depot that have entrydepot specified. It adds onto the E_CIKAN for each one, until it has allocated all the product.

So the inner cursor loop runs at least once for every exitdepot row you have. However, your system doesn't really care which items went out with which transaction - you are only trying to calculate the final E_CIKAN values.

So ...

Your outer loop only needs to get the total amount of items shipped out for each product/depot combo. Hence you could change the outer cursor definition to:

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

(and then also change the matching FETCH from SH at the end of the code to match, obviously)

This means your outer cursor will have many fewer rows to loop through, and your inner cursor will have roughtly the same amount of rows to loop through.

So this should be faster.

like image 93
codeulike Avatar answered Sep 20 '22 12:09

codeulike