I need help solving a performance problem related to a recursive function in SQL Server. I have a table of tasks for items, each of which have a lead time. My function recursively calls itself to calculate the due date for each task, based on the sum of the preceding tasks (simplistically put..). The function performs slowly at large scale, I believe mainly because must recalculate the due date for each ancestor, for each subsequent task.
So I am wondering, is there a way to store a calculated value that could persist from function call to function call, that would last only the lifetime of the connection? Then my function could 'short-circuit' if it found a pre-calculated value, and avoid re-evaluating for each due date request. The basic schema is as below, with a crude representation of the function in question (This function could also be done with a cte, but the calculations are still repeating the same calculations):
Create Table Projects(id int, DueDate DateTime)
Create Table Items(id int, Parent int, Project int, Offset int)
Create Table Tasks (id int, Parent int, Leadtime Int, Sequence int)
insert into Projects Values
(100,'1/1/2021')
Insert into Items Values
(0,null, 100, 0)
,(1,12, null, 0)
,(2,15, null, 1)
Insert into Tasks Values
(10,0,1,1)
,(11,0,1,2)
,(12,0,2,3)
,(13,0,1,4)
,(14,1,1,1)
,(15,1,1,2)
,(16,2,2,1)
,(17,2,1,2);
CREATE FUNCTION GetDueDate(@TaskID int)
Returns DATETIME
AS BEGIN
Declare @retval DateTime = null
Declare @parent int = (Select Parent from Tasks where ID = @TaskID)
Declare @parentConsumingOp int = (select Parent from Items where ID = @parent)
Declare @parentOffset int = (select Offset from Items where ID = @parent)
Declare @seq int = (Select Sequence from Tasks where ID = @TaskID)
Declare @NextTaskID int = (select ID from Tasks where Parent = @parent and Sequence = @seq-1)
Declare @Due DateTime = (select DueDate from Projects where ID = (Select Project from Items where ID = (Select Parent from Tasks where ID = @TaskID)))
Declare @leadTime int = (Select LeadTime from Tasks where ID = @TaskID)
if @NextTaskID is not null
BEGIN
SET @retval = DateAdd(Day,@leadTime * -1,dbo.GetDueDate(@NextTaskID))
END ELSE IF @parentConsumingOp Is Not Null
BEGIN
SET @retval = DateAdd(Day,(@leadTime + @parentOffset)*-1,dbo.GetDueDate(@parentConsumingOp))
END ELSE SET @retval = DateAdd(Day,@parentOffset*-1,@Due)
Return @retval
END
EDIT: Sql Fiddle Here