【问题标题】:Recursive SQL to Find Critical Path?递归 SQL 查找关键路径?
【发布时间】:2011-04-22 21:49:42
【问题描述】:

鉴于这两个表:

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

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

我正在尝试查找任务直接前任的最新结束日期。对于 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;
}

感谢您的帮助!

【问题讨论】:

    标签: sql sql-server-2005 tsql recursion


    【解决方案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 的开始日期。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-10
      • 2017-09-24
      • 1970-01-01
      • 2013-03-14
      • 1970-01-01
      相关资源
      最近更新 更多