用于查找关键路径的递归SQL?

时间:2010-10-08 13:39:40

标签: sql sql-server-2005 tsql recursion

鉴于这两个表:

[dbo].[Task]
[Id]   [Duration]   [ScheduledStart]
int    int          Nullable DateTime

[dbo].[TaskDependencies]
[Id]   [PredecessorTaskId]   [TaskId]
int    FK_Task_Id            FK_Task_Id

我正在尝试找到Tasks前任的最新结束日期。对于Task表,ScheduledStart可以为空。这背后的想法是,如果它没有明确安排的启动,它可以从它的前辈派生(根级任务必须有一个ScheduledStart,所以计算可以从某处开始)。任务也可以有多个前辈。

我想出了一个假的递归函数(我认为)可以完成我正在寻找的东西。我想知道的是,如果有更有效的方法在SQL中编写它,因为我已经习惯了更多的程序编程。我应该只将Function_A作为存储过程并让它自己调用吗?有没有办法可以用WITH语句来完成(并且那些是一种更有效的递归查询方式,或者像这样的递归函数)?

DateTime Function_A(Task)
{
    var predecessorList = getPredecessors(Task)
    var latestEndDate;
    var currentPredecessorLatestEndDate;

    ForEach(Predecessor in predecessorList)
    {
        if(Predecessor.ScheduledStart != null)
        {
            if(latestEndDate != null)
            {
                if(Predecessor.StartDate + Predecessor.Duration > latestEndDate)
                {
                    latestEndDate = Predecessor.StartDate + Predecessor.Duration;
                }
            }
            else
            {
                latestEndDate = Predecessor.StartDate + Predecessor.Duration;
            }

        }
        else
        {
            currentPredecessorLatestEndDate = Function_A(Predecessor.Id);

            if(latestEndDate != null)
            {
                if(currentPredecessorEndDate > latestEndDate)
                {
                    latestEndDate = currentPredecessorEndDate;
                }
            }
            else
            {
                latestEndDate = currentPredecessorEndDate;              
            }
        }
    }

    return latestEndDate;
}

感谢您的帮助!

1 个答案:

答案 0 :(得分:1)

您可以使用递归CTE查找所有上游任务。然后,您可以根据上一个结束子任务计算第一个可用的开始日期。如果任务具有本身具有未知开始日期的子任务,则需要多次传递。

使用递归CTE的示例代码:

;with Predecessors as
(
select  Id as RootId
,       null as ChildId
from    @Task
union all
select  p.RootId
,       cd.PredecessorTaskId as ChildId
from    @TaskDependencies cd
join    Predecessors p
on      cd.TaskId = ISNULL(p.ChildId, p.RootId)
)
select  RootId
,       max(dateadd(day, c.Duration+1, c.ScheduledStart))
from    Predecessors p
join    @Task c
on      p.ChildId = c.Id
        -- Filter out tasks with child tasks that themselves have
        -- an unknown start date.
where   not exists
        (
        select  *
        from    Predecessors p2
        join    @Task c2
        on      p2.ChildId = c2.Id
        where   p2.RootId = p.RootId
                and c2.ScheduledStart is null
        )
group by
        RootId

测试数据:

declare @Task table (Id int, Duration int, ScheduledStart datetime)
insert @Task 
          select 1, 3, '2010-01-01'
union all select 2, 3, '2010-01-03'
union all select 3, 3, null
union all select 4, 3, '2010-01-01'
union all select 5, 3, null

declare @TaskDependencies table (PredecessorTaskId int, TaskId int)
insert @TaskDependencies
          select 1, 3
union all select 2, 3
union all select 4, 5
union all select 3, 5

打印:

3    2010-01-07

它过滤掉任务5,其中包含具有未知开始日期的子任务。如果输入任务3的计算开始日期,则可以计算任务5的开始日期。