Link to home
Start Free TrialLog in
Avatar of BFanguy
BFanguyFlag for United States of America

asked on

Convert a Funtion using a cursor to a set based approach or CTE for better performance

I have a view that calls a function which takes a long time to complete.
Can anyone suggest how can I convert this cursor to a set based approach or CTE for better performance?

the view without the call to the function runs in seconds,  adding the call to the function kills the performance.

ALTER FUNCTION [dbo].[CT_OpsBetween] (@TYPE nchar(1), @BASE nvarchar(30), @LOT nvarchar(3), @SPLIT nvarchar(3), @SUB nvarchar(3), @eSeq smallint)
RETURNS nvarchar(255) 
AS
BEGIN

DECLARE @OpsBetween nvarchar(255);

DECLARE @MyCursor CURSOR;
DECLARE @SEQUENCE_NO smallint;
DECLARE @RESOURCE_ID nvarchar(15);
DECLARE @OPERATION_TYPE nvarchar(15);
DECLARE @SERVICE_ID nvarchar(15);

SET @OpsBetween = ''
SET @MyCursor = CURSOR FOR
     SELECT SEQUENCE_NO, RESOURCE_ID, OPERATION_TYPE, SERVICE_ID
     from dbo.OPERATION
     where TYPE = @TYPE AND BASE_ID = @BASE AND LOT_ID = @LOT and SPLIT_ID = @SPLIT AND SUB_ID = @SUB AND SEQUENCE_NO  < @eSeq
     ORDER BY SEQUENCE_NO
 
OPEN @MyCursor
FETCH NEXT FROM @MyCursor INTO @SEQUENCE_NO, @RESOURCE_ID, @OPERATION_TYPE, @SERVICE_ID
 
WHILE @@FETCH_STATUS = 0
   BEGIN
      IF @OpsBetween <> ''
         BEGIN
             set @OpsBetween = @OpsBetween + ', '
         END
      SET @OpsBetween = @OpsBetween + Cast(@SEQUENCE_NO as varchar(10)) + '-' + CASE WHEN @OPERATION_TYPE IS NOT NULL THEN @OPERATION_TYPE ELSE CASE WHEN @SERVICE_ID IS NOT NULL THEN @SERVICE_ID ELSE @RESOURCE_ID END END;
      
      FETCH NEXT FROM @MyCursor INTO @SEQUENCE_NO, @RESOURCE_ID, @OPERATION_TYPE, @SERVICE_ID      
   END; 
 
CLOSE @MyCursor ;
DEALLOCATE @MyCursor;

RETURN @OpsBetween

END

Open in new window


Data in the dbo.OPERATION table looks like this:
TYPE BASE_ID    LOT_ID    SPLIT_ID	SUB_ID	SEQUENCE_NO	RESOURCE_ID	SERVICE_ID	OPERATION_TYPE
W	CC182460	1	0	0	5	           QC	             NULL	TRV APPR
W	CC182460	1	0	0	10	           MISC	             NULL	PROCURE
W	CC182460	1	0	0	20	           QC	             NULL	QC_MTR
W	CC182460	1	0	0	30	           MISC	             NULL	MARK ASSY API
W	CC182460	1	0	0	40	           QC	             NULL	HARDNESS 029
W	CC182460	1	0	0	50	           QC	             NULL	UTPA 040
W	CC182460	1	0	0	60	           ASSY	             NULL	NULL
W	CC182460	1	0	0	70	           TEST	             NULL	TEST API
W	CC182460	1	0	0	80	           PAINT	     NULL	PAINT 028
W	CC182460	1	0	0	90	           TAG	             NULL	TAG
W	CC182460	1	0	0	100	           QC	             NULL	QC FINAL 10.0
W	CC182460	1	0	0	110	           MISC	             NULL	PACK/SHIP 027
W	CC182460	1	0	0	120	           FINAL 	     NULL	NULL
W	CC182461	1	0	0	10	           MISC	             NULL	PROCURE
W	CC182461	1	0	0	20	           ASSY	             NULL	NULL
W	CC182461	1	0	0	30	           LATHE	     NULL	NULL
W	CC182461	1	0	0	40	           MISC	             NULL	MARK BALL
W	CC182461	1	0	0	50	           XYLAN	     NULL	XYLAN IN-HOUSE
W	CC182461	1	0	0	60	           ASSY	             NULL	ASSY BALL (F)
W	CC182461	1	0	0	70	           TEST	             NULL	TEST API
W	CC182461	1	0	0	80	           TAG	             NULL	TAG
W	CC182461	1	0	0	90	           QC	             NULL	QC FINAL 10.0
W	CC182461	1	0	0	100	           MISC	             NULL	PACK/SHIP 027
W	CC182461	1	0	0	110	           FINAL            NULL	NULL
W	CC182471	5	0	0	10	           MISC	             NULL	PROCURE
W	CC182471	5	0	0	20	           MISC	             NULL	NULL
W	CC182471	5	0	0	30	           QC	             NULL	HARDNESS 029
W	CC182471	5	0	0	40	           QC	             NULL	QC FINAL 10.0
W	CC182471	5	0	0	50	           MISC	             NULL	PACK/SHIP 027
W	CC182471	5	0	0	60	           FINAL             NULL	NULL

Open in new window


if i call the function with dbo.OPERATION('W','CC182461','1','0','0',50)        I'd expect  this result: "10-PROCURE,20-ASSY,30-LATHE,40-MARK BALL"

Any help would be greatly appreciated.
Avatar of Scott Pletcher
Scott Pletcher
Flag of United States of America image

Initially let's try leaving it as a scalar function.

As to the sample data, what we really need is CREATE TABLE and INSERT statement(s) to load the table.  A picture/"splat" of data doesn't really help us with testing.

--
SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;
GO
ALTER FUNCTION [dbo].[CT_OpsBetween] (
    @TYPE nchar(1), @BASE nvarchar(30), @LOT nvarchar(3), 
    @SPLIT nvarchar(3), @SUB nvarchar(3), @eSeq smallint )
RETURNS nvarchar(255) 
AS
BEGIN
RETURN (
    SELECT CAST(STUFF((
        SELECT ',' + Cast(SEQUENCE_NO AS varchar(10)) + '-' + 
            COALESCE(OPERATION_TYPE, SERVICE_ID, RESOURCE_ID)
        FROM dbo.OPERATION
        WHERE TYPE = @TYPE AND BASE_ID = @BASE AND 
            LOT_ID = @LOT AND SPLIT_ID = @SPLIT AND 
            SUB_ID = @SUB AND SEQUENCE_NO < @eSeq
        ORDER BY SEQUENCE_NO
        FOR XML PATH(''), TYPE
    ), 1, 1, '') AS nvarchar(255)) AS return_value
)
/*end of function code*/
END /*FUNCTION*/
GO
--

Open in new window

Avatar of BFanguy

ASKER

Thanks,  but i get
"Argument data type xml is invalid for argument 1 of stuff function."
Avatar of BFanguy

ASKER

fixed it with
FOR XML PATH('')), 1, 1, '') AS nvarchar(255)) AS return_value

but it runs in the exact same amount of time  (no performance increase)
Hmm, how large is the dbo.OPERATION table and on what column(s) is it clustered?

We could also try an iTVF.  I'll work on that too.
Avatar of BFanguy

ASKER

966007 records in the table

Clustered index:
TYPE
BASE_ID
LOT_ID
SPLIT_ID
SUB_ID
SEQUENCE_NO
Very similar, just using iTVF style.  This will have to be called differently, using CROSS APPLY rather than just a scalar function in the SELECT.

--
SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;
GO
CREATE FUNCTION [dbo].[CT_OpsBetween2] --<--used a different function name so you can test w/o losing the original function
(
    @TYPE nchar(1), @BASE nvarchar(30), @LOT nvarchar(3), 
    @SPLIT nvarchar(3), @SUB nvarchar(3), @eSeq smallint )
RETURNS TABLE
AS
RETURN (
    SELECT STUFF(CAST((
        SELECT ',' + Cast(SEQUENCE_NO AS varchar(10)) + '-' + 
            COALESCE(OPERATION_TYPE, SERVICE_ID, RESOURCE_ID)
        FROM dbo.OPERATION
        WHERE TYPE = @TYPE AND BASE_ID = @BASE AND 
            LOT_ID = @LOT AND SPLIT_ID = @SPLIT AND 
            SUB_ID = @SUB AND SEQUENCE_NO < @eSeq
        ORDER BY SEQUENCE_NO
        FOR XML PATH(''), TYPE
    ) AS nvarchar(255)), 1, 1, '') AS return_value
)
/*end of function code*/
GO
--

Open in new window

Wow, that's the perfect index for that query.  That's as fast as that lookup can get, so that's not the issue.
Avatar of BFanguy

ASKER

Thanks,  how do i call the Inline view using CROSS APPLY from my view?
Avatar of BFanguy

ASKER

Got it to cross apply - 3x longer...
ASKER CERTIFIED SOLUTION
Avatar of Scott Pletcher
Scott Pletcher
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial