I have a stored procedure that uses a FAST_FORWARD cursor to chronologically loop over a set of ~300k records and assigns them to declaration sets based on the state of a lot of running variables and flags, partly implemented as table variables. I have given a lot of thought on how to do this set-based and I just can't do it. So I am stuck with the cursor approach, and need to optimize this code.
What I have noticed is that the first 10% of progress is loaded and processed very quickly (2000 rows/sec), near 20% progress it has slowed to about 300 rows/sec and in the end it has slowed down to about 60 rows/sec.
IMO this can be due to 4 reasons:
- The cursor slows down, which I would think unlikely with a FAST_FORWARD cursor
- The processing slows down. For my "groups" I am using table variables in which I am inserting, updating and deleting. At any given moment there are max. about 10 rows in those variables.
- The inserting into target tables slows down. I don't see why this would be, I have no triggers defined on them and they are just ordinary tables.
- Evil magic
Either that or my percentage counter is broken:
SET @curprogress = @curprogress + 1
IF (@curprogress - ((@totprogress / 100) * (FLOOR(@curprogress * 100 / @totprogress)))) BETWEEN 0 AND 1 BEGIN
SET @msg = CAST(FLOOR(@curprogress * 100 / @totprogress) AS VARCHAR)
RAISERROR('%s%s', 0, 1, @msg, '%...') WITH NOWAIT;
END
Has anybody any clue what to look for and how to go on speeding up this query?
Symbolic exerpt of my code:
WHILE....
-- Fetch new record to be assigned to one of the open declaration sets
FETCH NEXT INTO @row_field1, @row_field2....
IF (@flag2 = 1) AND ((@flag1 = 0) OR (@row_field1 <> @prevrow_field1))
BEGIN
-- Logging info: we are closing a child declaration set
INSERT INTO @logtable SELECT '--> LOG MESSAGE'
INSERT INTO @logtable
SELECT format_message(@row_field1, @calc_field2, field3...)
FROM @runningtable_sub S LEFT JOIN @runningtable_main M ON S.MainID = M.ID
-- Update enddate of parent
UPDATE M SET M.enddate = DATEADD(day,365,S.enddate)
FROM @runningtable_sub S
LEFT JOIN @runningtable_main M
ON S.MainID = M.ID
-- close and save child
INSERT INTO outputtable_main
SELECT @field1, COALESCE(Z.Field1,'NULL'), S.startdate, S.enddate,
M.Startdate, M.Enddate
FROM @runningtable_sub S
LEFT JOIN @runningtable_main M ON S.MainID = M.ID
-- delete child from running table
DELETE FROM @runningtable_sub WHERE S.enddate < @curdate
END