SQL Function takes more than 2 hours to return a table..

  • I have a function with cursors which returns a table in SQL Server 2008. On executing the query which calls this function, it takes more than 2 hours to complete. Can anybody tell me how to optimize this function or where is the problem. I checked it with SQL profiler, but couldn't find any solution. The above mentioned function is below:

    Alter FUNCTION [dbo].[TEST_STOCK_SUMMARY_NEW](@F_WAREHOUSE NVARCHAR(20), @T_WAREHOUSE NVARCHAR(20), @F_ITEMGROUP NVARCHAR(20), @T_ITEMGROUP NVARCHAR(20), @F_DATE DATETIME, @T_DATE DATETIME)

    Returns

    @TAB_STOCK Table (WH NVARCHAR(20),ITEMGROUPNAME NVARCHAR(100),ITEMCODE NVARCHAR(20), ITEMDESC NVARCHAR(100), OB NUMERIC(16,4), OB_VAL NUMERIC(16,4), IN_QTY NUMERIC(16,4), IN_VAL NUMERIC(16,4), ITMGRP_IN_QTY NUMERIC(16,4), ITMGRP_IN_VAL NUMERIC(16,4), OUT_QTY NUMERIC(16,4), OUT_VAL NUMERIC(16,4), ITMGRP_OUT_QTY NUMERIC(16,4), ITMGRP_OUT_VAL NUMERIC(16,4), CB_QTY NUMERIC(16,4), CB_VAL NUMERIC(16,4),packcartons NUMERIC(16,4),itmprice NUMERIC(16,4),ITMGRPCD NVARCHAR(20),DTE DATETIME)

    AS

    BEGIN

    --Declaration Part

    DECLARE @WAREHOUSE NVARCHAR(20), @ITEMCODE NVARCHAR(20), @ITEMDESC NVARCHAR(100), @ITEMGROUP NVARCHAR(20), @OPENQTY NUMERIC(16,4), @OPENVALUE NUMERIC(16,4), @RECEIPTQTY NUMERIC(16,4), @RECEIPTVALUE NUMERIC(16,4), @GROUPTOT_RECEIPTQTY NUMERIC(16,4), @GROUPTOT_RECEIPTVALUE NUMERIC(16,4), @ISSUEDQTY NUMERIC(16,4), @ISSUEDVALUE NUMERIC(16,4), @CLOSINGQTY NUMERIC(16,4), @CLOSINGVALUE NUMERIC(16,4), @GROUPTOT_ISSUEDQTY NUMERIC(16,4), @GROUPTOT_ISSUEDVALUE NUMERIC(16,4)

    DECLARE @DATE DATETIME

    DECLARE @INQTY NUMERIC(16,4), @OUTQTY NUMERIC(16,4), @TRANSTYPE NVARCHAR(6), @CALCPRICE NUMERIC(16,4)

    DECLARE @TOT_IN NUMERIC(16,4), @TOT_OUT NUMERIC(16,4), @TOT_IN_PRICE NUMERIC(16,4), @TOT_OUT_PRICE NUMERIC(16,4)

    DECLARE @OPENQTYPRICE NUMERIC(16,4), @OpQty NUMERIC(16,4), @OpVal NUMERIC(16,4), @ClQty NUMERIC(16,4), @ClVal NUMERIC(16,4)

    DECLARE @PREVDAYCLOSING NUMERIC(16,4), @PREVDAYCLOSINGVAL NUMERIC(16,4), @DAYOPENINGBALANCE NUMERIC(16,4), @DAYOPENINGBALANCEVAL NUMERIC(16,4), @DAYCLOSINGBALANCE NUMERIC(16,4), @DAYCLOSINGBALANCEVAL NUMERIC(16,4)

    DECLARE @TAB_DAY_STOCK TABLE (WH NVARCHAR(20),ITMCORTON NUMERIC(16,4), ITEMGROUP NVARCHAR(20),ITEMCODE NVARCHAR(20), DATE DATETIME, OB NUMERIC(16,4), OB_VAL NUMERIC(16,4), IN_QTY NUMERIC(16,4), IN_QTY_VAL NUMERIC(16,4), OUT_QTY NUMERIC(16,4), OUT_QTY_VAL NUMERIC(16,4), CB_QTY NUMERIC(16,4), CB_QTY_VAL NUMERIC(16,4), INV_REV_FLAG INT,itmprice NUMERIC(16,4))

    DECLARE @ST_DATE DATETIME, @EN_DATE DATETIME, @IN_DATE DATETIME

    DECLARE @ITMCOD NVARCHAR(20)

    DECLARE @INV_REVALUE NUMERIC(16,4), @INV_REV_FLAG INT

    DECLARE @TOT_BALANCE NUMERIC(16,4), @TOT_TRANS_BALANCE NUMERIC(16,4)

    declare @packunit NUMERIC(16,4), @packcartons NUMERIC(16,4),@itmprice NUMERIC(16,4)

    SET @packunit = 0

    SET @packcartons = 0

    SET @TOT_BALANCE = 0

    SET @TOT_TRANS_BALANCE = 0

    SET @INV_REV_FLAG = 0

    SET @ST_DATE = (SELECT TOP 1 DOCDATE FROM OINM ORDER BY DOCDATE)

    SET @EN_DATE = (SELECT TOP 1 DOCDATE FROM OINM ORDER BY DOCDATE DESC)

    SET @DATE = @ST_DATE

    --Warehouse Cursor

    DECLARE CUR_WAREHOUSE CURSOR FOR SELECT WHSCODE FROM OWHS WHERE ((WHSCODE >= @F_WAREHOUSE AND WHSCODE <= @T_WAREHOUSE) OR (@F_WAREHOUSE = '' AND @T_WAREHOUSE = '')) ORDER BY WHSCODE

    OPEN CUR_WAREHOUSE

    FETCH NEXT FROM CUR_WAREHOUSE INTO @WAREHOUSE

    WHILE(@@FETCH_STATUS = 0)

    BEGIN

    --Item Group Cursor

    DECLARE CUR_ITEMGROUP CURSOR FOR SELECT ITMSGRPCOD FROM OITB

    WHERE ((ITMSGRPCOD = @F_ITEMGROUP AND ITMSGRPCOD <= @T_ITEMGROUP)

    OR (@F_ITEMGROUP = ''AND @T_ITEMGROUP = ''))

    ORDER BY ITMSGRPCOD

    OPEN CUR_ITEMGROUP

    FETCH NEXT FROM CUR_ITEMGROUP INTO @ITEMGROUP

    WHILE(@@FETCH_STATUS = 0)

    BEGIN

    --Item Cursor

    DECLARE CUR_ITEM CURSOR FOR SELECT DISTINCT T6.ITEMCODE FROM OINM T6 WHERE ((T6.ITEMCODE IN (SELECT T0.[ItemCode] FROM OITM T0 INNER JOIN OITB T1 ON T0.ItmsGrpCod = T1.ItmsGrpCod WHERE T1.ITMSGRPCOD = @ITEMGROUP)) AND (T6.WAREHOUSE = @WAREHOUSE)) ORDER BY T6.ITEMCODE -- AND ((T6.DOCDATE >= @F_DATE AND T6.DOCDATE <= @T_DATE) OR (@F_DATE = '' AND @T_DATE = '') )) ORDER BY T6.ITEMCODE

    OPEN CUR_ITEM

    FETCH NEXT FROM CUR_ITEM INTO @ITEMCODE

    WHILE(@@FETCH_STATUS = 0)

    BEGIN

    SET @PREVDAYCLOSING = 0

    SET @PREVDAYCLOSINGVAL = 0

    SET @ST_DATE = (SELECT TOP 1 DOCDATE FROM OINM ORDER BY DOCDATE)

    SET @EN_DATE = (SELECT TOP 1 DOCDATE FROM OINM ORDER BY DOCDATE DESC)

    SET @DATE = @ST_DATE

    WHILE (@DATE <= @EN_DATE)

    BEGIN

    SET @INQTY = 0

    SET @OUTQTY = 0

    SET @CALCPRICE = 0

    SET @TOT_IN = 0

    SET @TOT_OUT = 0

    SET @TOT_IN_PRICE = 0

    SET @TOT_OUT_PRICE = 0

    SET @OPENQTY = 0

    SET @OPENQTYPRICE = 0

    SET @DAYOPENINGBALANCE = 0

    SET @DAYOPENINGBALANCEVAL = 0

    SET @DAYCLOSINGBALANCE = 0

    SET @DAYCLOSINGBALANCEVAL = 0

    SET @INV_REVALUE = 0

    DECLARE CUR_ITEM_IN CURSOR FOR SELECT (CONVERT(NUMERIC(16,4), T6.INQTY)), (CONVERT(NUMERIC(16,4), T6.OUTQTY)), (CONVERT(NUMERIC(16,4), T6.TRANSVALUE)), T6.TRANSTYPE FROM OINM T6 WHERE ((T6.ITEMCODE = @ITEMCODE) AND (T6.WAREHOUSE = @WAREHOUSE) AND (T6.DOCDATE = @DATE) and t6.transtype <> 67)

    OPEN CUR_ITEM_IN

    FETCH NEXT FROM CUR_ITEM_IN INTO @INQTY, @OUTQTY, @CALCPRICE, @TRANSTYPE

    WHILE(@@FETCH_STATUS = 0)

    BEGIN

    IF @INQTY = 0 AND @OUTQTY = 0

    SET @INV_REVALUE = @INV_REVALUE + @CALCPRICE

    IF @CALCPRICE < 0

    SET @CALCPRICE = @CALCPRICE * (-1)

    IF (@INQTY > 0)

    BEGIN

    SET @TOT_IN = @TOT_IN + @INQTY

    SET @TOT_IN_PRICE = @TOT_IN_PRICE + @CALCPRICE

    END

    IF (@OUTQTY > 0)

    BEGIN

    SET @TOT_OUT = @TOT_OUT + @OUTQTY

    SET @TOT_OUT_PRICE = @TOT_OUT_PRICE + @CALCPRICE

    END

    FETCH NEXT FROM CUR_ITEM_IN INTO @INQTY, @OUTQTY, @CALCPRICE, @TRANSTYPE

    END

    CLOSE CUR_ITEM_IN

    DEALLOCATE CUR_ITEM_IN

    SET @INV_REV_FLAG = 0

    IF @OPENQTY != 0 OR @TOT_IN != 0 OR @TOT_OUT != 0 OR @F_DATE = @DATE OR @T_DATE = @DATE OR @INV_REVALUE != 0

    BEGIN

    IF @INV_REVALUE != 0 AND @TOT_IN = 0 AND @TOT_OUT = 0

    SET @INV_REV_FLAG = 1

    SET @DAYOPENINGBALANCE = @PREVDAYCLOSING

    SET @DAYOPENINGBALANCEVAL = @PREVDAYCLOSINGVAL

    SET @DAYCLOSINGBALANCE = @DAYOPENINGBALANCE + (@OPENQTY + @TOT_IN) - @TOT_OUT

    SET @DAYCLOSINGBALANCEVAL = @DAYOPENINGBALANCEVAL + (@OPENQTYPRICE + @TOT_IN_PRICE) - @TOT_OUT_PRICE + @INV_REVALUE

    INSERT INTO @TAB_DAY_STOCK VALUES(@WAREHOUSE,@packcartons, @ITEMGROUP, @ITEMCODE, @DATE, @DAYOPENINGBALANCE, @DAYOPENINGBALANCEVAL, (@OPENQTY + @TOT_IN), (@OPENQTYPRICE + @TOT_IN_PRICE), @TOT_OUT, @TOT_OUT_PRICE, @DAYCLOSINGBALANCE, @DAYCLOSINGBALANCEVAL, @INV_REV_FLAG,@itmprice)

    SET @PREVDAYCLOSING = @DAYCLOSINGBALANCE

    SET @PREVDAYCLOSINGVAL = @DAYCLOSINGBALANCEVAL

    IF @T_DATE = @DATE

    SET @TOT_BALANCE = @TOT_BALANCE + @DAYCLOSINGBALANCEVAL

    END

    SET @DATE = @DATE + 1

    END

    FETCH NEXT FROM CUR_ITEM INTO @ITEMCODE

    END

    CLOSE CUR_ITEM

    DEALLOCATE CUR_ITEM

    FETCH NEXT FROM CUR_ITEMGROUP INTO @ITEMGROUP

    END

    CLOSE CUR_ITEMGROUP

    DEALLOCATE CUR_ITEMGROUP

    FETCH NEXT FROM CUR_WAREHOUSE INTO @WAREHOUSE

    END

    CLOSE CUR_WAREHOUSE

    DEALLOCATE CUR_WAREHOUSE

    DECLARE @WH_TS NVARCHAR(20), @ITEMGROUP_TS NVARCHAR(20),@ITEMCODE_TS NVARCHAR(20), @DATE_TS DATETIME, @OB_TS NUMERIC(16,4), @OB_VAL_TS NUMERIC(16,4), @IN_QTY_TS NUMERIC(16,4), @IN_QTY_VAL_TS NUMERIC(16,4), @OUT_QTY_TS NUMERIC(16,4), @OUT_QTY_VAL_TS NUMERIC(16,4), @CB_QTY_TS NUMERIC(16,4), @CB_QTY_VAL_TS NUMERIC(16,4), @INV_REV_FLAG_TS INT

    DECLARE @TEMP NUMERIC(16,4)

    DECLARE @DATE_IN DATETIME

    DECLARE @FIRST_REC INT

    DECLARE @OB_QTY_IN NUMERIC(16,4), @OB_VAL_IN NUMERIC(16,4), @CL_QTY_IN NUMERIC(16,4), @CL_VAL_IN NUMERIC(16,4)

    DECLARE @TOT_IN_QTY_IN NUMERIC(16,4), @TOT_IN_VAL_IN NUMERIC(16,4), @TOT_OUT_QTY_IN NUMERIC(16,4), @TOT_OUT_VAL_IN NUMERIC(16,4), @TOT_INV_REV_FALG INT

    DECLARE @GTOT_IN_QTY_IN NUMERIC(16,4), @GTOT_IN_VAL_IN NUMERIC(16,4), @GTOT_OUT_QTY_IN NUMERIC(16,4), @GTOT_OUT_VAL_IN NUMERIC(16,4)

    DECLARE @GRPNAME NVARCHAR(100), @ITMDESC NVARCHAR(100)

    DECLARE @GRAND_TOT_IN NUMERIC(16,4), @GRAND_TOT_OUT NUMERIC(16,4)

    SET @FIRST_REC = 0

    SET @GRAND_TOT_IN = 0

    SET @GRAND_TOT_OUT = 0

    --Warehouse Cursor

    DECLARE CUR_WAREHOUSE CURSOR FOR SELECT WHSCODE FROM OWHS WHERE ((WHSCODE >= @F_WAREHOUSE AND WHSCODE <= @T_WAREHOUSE) OR (@F_WAREHOUSE = '' AND @T_WAREHOUSE = '')) ORDER BY WHSCODE

    OPEN CUR_WAREHOUSE

    FETCH NEXT FROM CUR_WAREHOUSE INTO @WAREHOUSE

    WHILE(@@FETCH_STATUS = 0)

    BEGIN

    --Item Group Cursor

    DECLARE CUR_ITEMGROUP CURSOR FOR SELECT ITMSGRPCOD FROM OITB

    WHERE ((ITMSGRPCOD = @F_ITEMGROUP AND ITMSGRPCOD <= @T_ITEMGROUP)

    OR (@F_ITEMGROUP = ''AND @T_ITEMGROUP = ''))

    ORDER BY ITMSGRPCOD

    OPEN CUR_ITEMGROUP

    FETCH NEXT FROM CUR_ITEMGROUP INTO @ITEMGROUP

    WHILE(@@FETCH_STATUS = 0)

    BEGIN

    SET @GTOT_IN_QTY_IN = 0

    SET @GTOT_IN_VAL_IN = 0

    SET @GTOT_OUT_QTY_IN = 0

    SET @GTOT_OUT_VAL_IN = 0

    --Item Cursor

    DECLARE CUR_ITEM CURSOR FOR SELECT DISTINCT T6.ITEMCODE FROM OINM T6 WHERE ((T6.ITEMCODE IN (SELECT T0.[ItemCode] FROM OITM T0 INNER JOIN OITB T1 ON T0.ItmsGrpCod = T1.ItmsGrpCod WHERE T1.ITMSGRPCOD = @ITEMGROUP)) AND (T6.WAREHOUSE = @WAREHOUSE)) ORDER BY T6.ITEMCODE -- AND ((T6.DOCDATE >= @F_DATE AND T6.DOCDATE <= @T_DATE) OR (@F_DATE = '' AND @T_DATE = '') )) ORDER BY T6.ITEMCODE

    OPEN CUR_ITEM

    FETCH NEXT FROM CUR_ITEM INTO @ITEMCODE

    WHILE(@@FETCH_STATUS = 0)

    BEGIN

    SET @TOT_IN_QTY_IN = 0

    SET @TOT_IN_VAL_IN = 0

    SET @TOT_OUT_QTY_IN = 0

    SET @TOT_OUT_VAL_IN = 0

    SET @TOT_INV_REV_FALG = 0

    SET @FIRST_REC = 0

    DECLARE CUR_DATE_IN CURSOR FOR SELECT DISTINCT DATE FROM @TAB_DAY_STOCK WHERE (WH = @WAREHOUSE) AND (ITEMGROUP = @ITEMGROUP)AND (ITEMCODE = @ITEMCODE) AND ((DATE >= @F_DATE AND DATE <= @T_DATE) OR (@F_DATE = '' AND @T_DATE = '') )

    OPEN CUR_DATE_IN

    FETCH NEXT FROM CUR_DATE_IN INTO @DATE_IN

    WHILE(@@FETCH_STATUS = 0)

    BEGIN

    DECLARE CUR_TRANS CURSOR FOR SELECT * FROM @TAB_DAY_STOCK WHERE ((WH = @WAREHOUSE) AND (ITEMGROUP = @ITEMGROUP) AND (DATE = @DATE_IN) AND (ITEMCODE = @ITEMCODE))

    OPEN CUR_TRANS

    FETCH NEXT FROM CUR_TRANS INTO @WH_TS,@packcartons, @ITEMGROUP_TS,@ITEMCODE_TS, @DATE_TS, @OB_TS, @OB_VAL_TS, @IN_QTY_TS, @IN_QTY_VAL_TS, @OUT_QTY_TS, @OUT_QTY_VAL_TS, @CB_QTY_TS, @CB_QTY_VAL_TS, @INV_REV_FLAG_TS,@itmprice

    WHILE (@@FETCH_STATUS = 0)

    BEGIN

    SET @FIRST_REC = @FIRST_REC + 1

    IF @FIRST_REC = 1

    BEGIN

    SET @OB_QTY_IN = @OB_TS

    SET @OB_VAL_IN = @OB_VAL_TS

    END

    SET @TOT_IN_QTY_IN = @TOT_IN_QTY_IN + @IN_QTY_TS

    SET @TOT_IN_VAL_IN = @TOT_IN_VAL_IN + @IN_QTY_VAL_TS

    SET @TOT_OUT_QTY_IN = @TOT_OUT_QTY_IN + @OUT_QTY_TS

    SET @TOT_OUT_VAL_IN = @TOT_OUT_VAL_IN + @OUT_QTY_VAL_TS

    SET @GTOT_IN_QTY_IN = @GTOT_IN_QTY_IN + @IN_QTY_TS

    SET @GTOT_IN_VAL_IN = @GTOT_IN_VAL_IN + @IN_QTY_VAL_TS

    SET @GTOT_OUT_QTY_IN = @GTOT_OUT_QTY_IN + @OUT_QTY_TS

    SET @GTOT_OUT_VAL_IN = @GTOT_OUT_VAL_IN + @OUT_QTY_VAL_TS

    SET @CL_QTY_IN = @CB_QTY_TS

    SET @CL_VAL_IN = @CB_QTY_VAL_TS

    SET @TOT_INV_REV_FALG = @TOT_INV_REV_FALG + @INV_REV_FLAG_TS

    FETCH NEXT FROM CUR_TRANS INTO @WH_TS,@packcartons, @ITEMGROUP_TS,@ITEMCODE_TS, @DATE_TS, @OB_TS, @OB_VAL_TS, @IN_QTY_TS, @IN_QTY_VAL_TS, @OUT_QTY_TS, @OUT_QTY_VAL_TS, @CB_QTY_TS, @CB_QTY_VAL_TS, @INV_REV_FLAG_TS,@itmprice

    END

    CLOSE CUR_TRANS

    DEALLOCATE CUR_TRANS

    FETCH NEXT FROM CUR_DATE_IN INTO @DATE_IN

    END

    CLOSE CUR_DATE_IN

    DEALLOCATE CUR_DATE_IN

    SET @GRPNAME = (SELECT ITMSGRPNAM FROM OITB WHERE ITMSGRPCOD = @ITEMGROUP)

    SET @ITMDESC = (SELECT ITEMNAME FROM OITM WHERE ITEMCODE = @ITEMCODE)

    declare cur_item_packunit cursor for select SALPACKUN from oitm where itemcode = @ITEMCODE

    open cur_item_packunit

    fetch next from cur_item_packunit into @packunit

    while(@@FETCH_STATUS = 0)

    begin

    SET @packcartons = @CL_QTY_IN/@packunit

    fetch next from cur_item_packunit into @packunit

    end

    close cur_item_packunit

    deallocate cur_item_packunit

    ---- item price

    declare cur_item_price cursor for select price from itm1 where itemcode = @ITEMCODE

    open cur_item_price

    fetch next from cur_item_price into @itmprice

    set @itmprice=@itmprice

    close cur_item_price

    deallocate cur_item_price

    -----------------

    INSERT INTO @TAB_STOCK VALUES(@WH_TS,@GRPNAME, @ITEMCODE_TS, @ITMDESC, @OB_QTY_IN, @OB_VAL_IN, @TOT_IN_QTY_IN, @TOT_IN_VAL_IN, @GTOT_IN_QTY_IN, @GTOT_IN_VAL_IN, @TOT_OUT_QTY_IN, @TOT_OUT_VAL_IN, @GTOT_OUT_QTY_IN, @GTOT_OUT_VAL_IN, @CL_QTY_IN, @CL_VAL_IN,@packcartons,@itmprice,@ITEMGROUP,@DATE)

    IF @TOT_IN_QTY_IN != 0 OR @TOT_OUT_QTY_IN != 0 OR @TOT_INV_REV_FALG > 0

    BEGIN

    SET @TOT_TRANS_BALANCE = @TOT_TRANS_BALANCE + @CL_VAL_IN

    END

    FETCH NEXT FROM CUR_ITEM INTO @ITEMCODE

    END

    CLOSE CUR_ITEM

    DEALLOCATE CUR_ITEM

    IF @GTOT_IN_VAL_IN != 0 OR @GTOT_OUT_VAL_IN != 0

    BEGIN

    SET @GRAND_TOT_IN = @GRAND_TOT_IN + @GTOT_IN_VAL_IN

    SET @GRAND_TOT_OUT = @GRAND_TOT_OUT + @GTOT_OUT_VAL_IN

    END

    FETCH NEXT FROM CUR_ITEMGROUP INTO @ITEMGROUP

    END

    CLOSE CUR_ITEMGROUP

    DEALLOCATE CUR_ITEMGROUP

    FETCH NEXT FROM CUR_WAREHOUSE INTO @WAREHOUSE

    END

    CLOSE CUR_WAREHOUSE

    DEALLOCATE CUR_WAREHOUSE

    Return

    END

  • I'm not suprised it took a couple of hours looking at the query you have loops with cursors which is going to drag performance down into the basement and beyond then on top of that the SQL function reads like a VB program function.

    A couple of blatently obvious things apart from all the loops and Cursors, things that are going to kill this include this type of thing.

    SET @ST_DATE = (SELECT TOP 1 DOCDATE FROM OINM ORDER BY DOCDATE)

    SET @EN_DATE = (SELECT TOP 1 DOCDATE FROM OINM ORDER BY DOCDATE DESC)

    Thats two queries with a sort operator, and they can be better expressed, on top of that this is run again in the next block, which is superflous as I cannot see where the @ST_DATE, and @EN_DATE changes in the interim.

    SELECT @ST_DATE=MAX(DOCDATE), @EN_DATE=MIN(DOCDATE) FROM OINM

    Evaluate the Top part on its own and then evaluate the second query I'll put money on the second part of code beating the top one.

    In short this needs a total rewrite so that its less procedural and more Set based.

    _________________________________________________________________________
    SSC Guide to Posting and Best Practices

  • Jason-299789 (2/5/2013)


    I'm not suprised it took a couple of hours looking at the query you have loops with cursors which is going to drag performance down into the basement and beyond then on top of that the SQL function reads like a VB program function.

    A couple of blatently obvious things apart from all the loops and Cursors, things that are going to kill this include this type of thing.

    SET @ST_DATE = (SELECT TOP 1 DOCDATE FROM OINM ORDER BY DOCDATE)

    SET @EN_DATE = (SELECT TOP 1 DOCDATE FROM OINM ORDER BY DOCDATE DESC)

    Thats two queries with a sort operator, and they can be better expressed, on top of that this is run again in the next block, which is superflous as I cannot see where the @ST_DATE, and @EN_DATE changes in the interim.

    SELECT @ST_DATE=MAX(DOCDATE), @EN_DATE=MIN(DOCDATE) FROM OINM

    Evaluate the Top part on its own and then evaluate the second query I'll put money on the second part of code beating the top one.

    In short this needs a total rewrite so that its less procedural and more Set based.

    yes, I thought I would be seeing a single cursor,after formatting the code ;but, it was cusror within a cursor , within a cursor and so on..:w00t:

    You should restructure the query ..

    Use case statements to get the results and calculation ; Like inner most cursor can be do away with case statements in the select ..

    use temptables to store data ; does it have to be a function ? I think, stored procedure should be better in this case .

    And as jason suggested , @st_date and @en_date would be same wherever you set it ; in the inner most cursor , adding the same , would be executed each time for each item read by cursor..

    For restructuring , If you need a result set to work on with then have temptable, try to work in batches , avoid cursors, if possible..

    And, lastly , I hope DOCDATE is not null column , otherwise , it would be a mess to carry on

    as @date = @date+1 ; that is why taking max(date) or min(date) is better ...

    ~ demonfox
    ___________________________________________________________________
    Wondering what I would do next , when I am done with this one :ermm:

  • No less than four levels of nested queries! Good Lord, no wonder it takes so long. Examining the code, none of it, even the few queries, look as if they've been written by an experienced SQL developer and there are mistakes - compare these two cursor definitions:

    -- CUR_WAREHOUSE

    SELECT WHSCODE -- @WAREHOUSE

    FROM OWHS

    WHERE ((WHSCODE >= @F_WAREHOUSE AND WHSCODE <= @T_WAREHOUSE)

    OR (@F_WAREHOUSE = '' AND @T_WAREHOUSE = ''))

    ORDER BY WHSCODE

    -- CUR_ITEMGROUP

    SELECT ITMSGRPCOD -- @ITEMGROUP

    FROM OITB

    WHERE ((ITMSGRPCOD = @F_ITEMGROUP AND ITMSGRPCOD <= @T_ITEMGROUP)

    OR (@F_ITEMGROUP = ''AND @T_ITEMGROUP = ''))

    ORDER BY ITMSGRPCOD

    The second definition is supposed to have a range in the WHERE clause.

    I'd suggest that if this function generates correct results, then it's by accident, not by design. However, it shouldn't be too tough to convert it to a proper set-based function. The four nested cursors can be combined something like this:

    SELECT w.WHSCODE, T6.ITEMCODE, T0.ItmsGrpCod,

    (CONVERT(NUMERIC(16,4), T6.INQTY)), -- @INQTY

    (CONVERT(NUMERIC(16,4), T6.OUTQTY)), -- @OUTQTY

    (CONVERT(NUMERIC(16,4), T6.TRANSVALUE)), -- @CALCPRICE

    T6.TRANSTYPE -- @TRANSTYPE

    FROM OINM T6

    INNER JOIN OWHS w

    ON w.WHSCODE = T6.WAREHOUSE

    INNER JOIN OITM T0

    ON T0.[ItemCode] = T6.ITEMCODE

    AND T0.ItmsGrpCod = @ITEMGROUP

    WHERE (w.WHSCODE >= @F_WAREHOUSE AND w.WHSCODE <= @T_WAREHOUSE)

    OR (@F_WAREHOUSE = '' AND @T_WAREHOUSE = '')

    The arithmetic appears to be fairly simple, I can't see any reason why it can't be done in the output.

    This is just the first phase. The output of this first chunk of the function is used to drive a second bank of nested cursors which perform more calculations.

    This looks like a VB programmer's first attempt at TSQL coding. Get a professional in to write it properly.

    β€œWrite the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • and chances this shouldn't be a SQLFunction at all, but a regular stored procedure.

    Keep in mind, if a function doesn't perform well, it will drag down the query using it ... and ALL queries using it !

    Johan

    Learn to play, play to learn !

    Dont drive faster than your guardian angel can fly ...
    but keeping both feet on the ground wont get you anywhere :w00t:

    - How to post Performance Problems
    - How to post data/code to get the best help[/url]

    - How to prevent a sore throat after hours of presenting ppt

    press F1 for solution, press shift+F1 for urgent solution πŸ˜€

    Need a bit of Powershell? How about this

    Who am I ? Sometimes this is me but most of the time this is me

  • ChrisM@Work (2/5/2013)


    No less than four levels of nested queries! Good Lord, no wonder it takes so long. Examining the code, none of it, even the few queries, look as if they've been written by an experienced SQL developer and there are mistakes - compare these two cursor definitions:

    -- CUR_WAREHOUSE

    SELECT WHSCODE -- @WAREHOUSE

    FROM OWHS

    WHERE ((WHSCODE >= @F_WAREHOUSE AND WHSCODE <= @T_WAREHOUSE)

    OR (@F_WAREHOUSE = '' AND @T_WAREHOUSE = ''))

    ORDER BY WHSCODE

    -- CUR_ITEMGROUP

    SELECT ITMSGRPCOD -- @ITEMGROUP

    FROM OITB

    WHERE ((ITMSGRPCOD = @F_ITEMGROUP AND ITMSGRPCOD <= @T_ITEMGROUP)

    OR (@F_ITEMGROUP = ''AND @T_ITEMGROUP = ''))

    ORDER BY ITMSGRPCOD

    The second definition is supposed to have a range in the WHERE clause.

    I'd suggest that if this function generates correct results, then it's by accident, not by design. However, it shouldn't be too tough to convert it to a proper set-based function. The four nested cursors can be combined something like this:

    SELECT w.WHSCODE, T6.ITEMCODE, T0.ItmsGrpCod,

    (CONVERT(NUMERIC(16,4), T6.INQTY)), -- @INQTY

    (CONVERT(NUMERIC(16,4), T6.OUTQTY)), -- @OUTQTY

    (CONVERT(NUMERIC(16,4), T6.TRANSVALUE)), -- @CALCPRICE

    T6.TRANSTYPE -- @TRANSTYPE

    FROM OINM T6

    INNER JOIN OWHS w

    ON w.WHSCODE = T6.WAREHOUSE

    INNER JOIN OITM T0

    ON T0.[ItemCode] = T6.ITEMCODE

    AND T0.ItmsGrpCod = @ITEMGROUP

    WHERE (w.WHSCODE >= @F_WAREHOUSE AND w.WHSCODE <= @T_WAREHOUSE)

    OR (@F_WAREHOUSE = '' AND @T_WAREHOUSE = '')

    The arithmetic appears to be fairly simple, I can't see any reason why it can't be done in the output.

    This is just the first phase. The output of this first chunk of the function is used to drive a second bank of nested cursors which perform more calculations.

    This looks like a VB programmer's first attempt at TSQL coding. Get a professional in to write it properly.

    +1, especially about this looking like a procedural programmers attempt at SQL, wheres JC when you really need him πŸ˜›

    _________________________________________________________________________
    SSC Guide to Posting and Best Practices

  • Just a generic observation...

    If this function is taking two hours to run--even with all of the cursors and subcursors--then you are probably accessing a LOT of data. I don't think a function is even appropriate. Is this being called by another procedure or query? If so the issues raised are multiplied by how many times the function itself is being called.

    You should write a procedure instead. Take the loops and concentrate on writing queries that don't use loops/cursors. Perhaps you break the queries into smaller pieces and insert the results into temp tables just to get the data someplace where you can query it efficiently. If you have a truly huge amount of data it would be even better to insert it into an actual table to take a load off tempdb. Once broken down into smaller pieces it will be a LOT easier to do your performance tuning as well.

    Also, it may help performance if all of your DDL (DECLARE statements, CREATE table, etc) are defined before you do any DML (the procedural "stuff"). Mixing up DDL and DML, especially in such a huge amount of code, can cause recompilation issues.

    I don't know what kind of results the pseudo-queries below will give you, but you should strive for something like these rather than so many cursors. I've used cursors and still do occasionally but I rarely nest cursors. Set based queries will always beat loops of any kind when it comes to performance. Sometimes performance isn't an issue due to small scale or infrequent runs etc. But clearly this function has evolved into a monster.

    --try joining instead of nesting loops

    SELECT

    OWHS.WHSCODE

    ,OITB.ITMSGRPCOD

    FROM

    OWHS

    INNER JOIN

    OITB

    ON OWHS.WHSCODE = OITB.WHSCODE

    WHERE

    1=1

    AND

    ((OWHS.WHSCODE >= @F_WAREHOUSE

    OWHS.AND WHSCODE <= @T_WAREHOUSE)

    OR (@F_WAREHOUSE = '' AND @T_WAREHOUSE = ''))

    AND

    ((OITB.ITMSGRPCOD = @F_ITEMGROUP

    AND OITB.ITMSGRPCOD <= @T_ITEMGROUP)

    OR (@F_ITEMGROUP = '' AND @T_ITEMGROUP = ''))

    ORDER BY

    OWHS.WHSCODE

    ,OITB.ITMSGRPCOD

    --OR

    SELECT

    WHSCODE

    ,ITMSGRPCOD

    FROM

    (

    SELECT

    OWHS.WHSCODE

    FROM

    OWHS

    WHERE

    ((OWHS.WHSCODE >= @F_WAREHOUSE

    OWHS.AND WHSCODE <= @T_WAREHOUSE)

    OR (@F_WAREHOUSE = '' AND @T_WAREHOUSE = ''))

    ) AS OWHS

    INNER JOIN

    (

    SELECT

    OITB.ITMSGRPCOD

    FROM

    OITB

    WHERE

    ((OITB.ITMSGRPCOD = @F_ITEMGROUP

    AND OITB.ITMSGRPCOD <= @T_ITEMGROUP)

    OR (@F_ITEMGROUP = '' AND @T_ITEMGROUP = ''))

    ) AS OITB

    ON OWHS.WHSCODE = OITB.WHSCODE

    ORDER BY

    OWHS.WHSCODE

    ,OITB.ITMSGRPCOD

  • Steven Willis (2/5/2013)


    Just a generic observation...

    If this function is taking two hours to run--even with all of the cursors and subcursors--then you are probably accessing a LOT of data. I don't think a function is even appropriate. Is this being called by another procedure or query? If so the issues raised are multiplied by how many times the function itself is being called.

    You should write a procedure instead. ..

    Not necessarily. A properly written iTVF inlines like a view. This function is just a 4-table query with a few sums in it. I can't see any reason why it shouldn't be written as an iTVF regardless of how many rows the base tables contain.

    β€œWrite the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • Just remember that the user defined muti-statement table valued function works off the construct of the table variable. the table variables one defining characteristic is it's lack of statistics. So, any query run against a multi-statement table valued function (or a table variable) that does any kind of filtering or joining or any operation that requires statistics, then the optimizer, having no statistics to work with, assumes one (1) row. This is fine if you're working with 1-10 rows, but as soon as those values grow, this method becomes an ENORMOUS performance bottleneck.

    "The credit belongs to the man who is actually in the arena, whose face is marred by dust and sweat and blood"
    - Theodore Roosevelt

    Author of:
    SQL Server Execution Plans
    SQL Server Query Performance Tuning

  • ChrisM@Work (2/5/2013)


    Steven Willis (2/5/2013)


    Just a generic observation...

    If this function is taking two hours to run--even with all of the cursors and subcursors--then you are probably accessing a LOT of data. I don't think a function is even appropriate. Is this being called by another procedure or query? If so the issues raised are multiplied by how many times the function itself is being called.

    You should write a procedure instead. ..

    Not necessarily. A properly written iTVF inlines like a view. This function is just a 4-table query with a few sums in it. I can't see any reason why it shouldn't be written as an iTVF regardless of how many rows the base tables contain.

    Can't argue with that. Frankly, I didn't even look at the code in detail because it was late and 600 lines of cursors had me overwhelmed. :crazy:

    If all of that code boils down to 4-tables then perhaps an iTVF would work fine--though I'm astonished really that such a case could wind up looking like the code that was posted. It would also validate the main point of my post in which I tried to show the OP a couple of pseudo-code examples of using JOINS rather than cursors.

    Β 

  • sanjaytiwari31 (2/4/2013)


    Can anybody tell me how to optimize this function or where is the problem.

    Yes I can but you're not going to like it. The code cannot be optimized no matter how much you play with it because of the nested cursors. It would be a far better thing sack the code entirely and rewrite it from the ground up without the cursors.

    Because of the length of the code, I've not done a serious deep dive on it but I agree with what has already been said... it looks like it could be resolved by a single relatively simple query across 4 tables. If no one in your shop can handle the conversion, your shop should get some hired help. πŸ˜›

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Try to create a primary key/unique key on your table variable, and it is always better to go as a stored proc using temp tables and while loop instead of cursors and try building a clustered index on the temp table which will definitely improve your performance.

  • k.srikalyan (2/6/2013)


    Try to create a primary key/unique key on your table variable, and it is always better to go as a stored proc using temp tables and while loop instead of cursors and try building a clustered index on the temp table which will definitely improve your performance.

    A while loop is a cursor. That's not a replacement for one.

    "The credit belongs to the man who is actually in the arena, whose face is marred by dust and sweat and blood"
    - Theodore Roosevelt

    Author of:
    SQL Server Execution Plans
    SQL Server Query Performance Tuning

  • Not really looking in detail but

    I think it should be a store procedure.

    And I quite sure we can eliminated the cursor using temp table and update and set variables in a single query.

  • k.srikalyan (2/6/2013)


    Try to create a primary key/unique key on your table variable, and it is always better to go as a stored proc using temp tables and while loop instead of cursors and try building a clustered index on the temp table which will definitely improve your performance.

    While loops are not all that better than cursors, it is still RBAR.

Viewing 15 posts - 1 through 15 (of 18 total)

You must be logged in to reply to this topic. Login to reply