Data setup first, main code follows:
IF OBJECT_ID('tempdb.dbo.#actions') IS NOT NULL
DROP TABLE #actions;
GO
CREATE TABLE #actions (
ActID int NOT NULL,
ActName varchar(100) NOT NULL,
Duration decimal(7, 2) NULL,
Predecessor smallint NULL,
StartDate datetime NULL,
EndDate datetime NULL
)
TRUNCATE TABLE #actions
INSERT INTO #actions VALUES
(1, 'Building Contract', 2, NULL, NULL, NULL),
(2, 'Land Survey', 2, 1, NULL, NULL),
(3, 'Soil Testing', 3, 2, NULL, NULL),
(4, 'Land Excavation', 5, 3, NULL, NULL),
/*Added: I wanted to allow actions for a single predecessor#, with diff. durations*/
(5, 'Sign Preparation', 3, 3, NULL, NULL),
(6, 'Land Drilling', 4, 4, NULL, NULL)
DECLARE @StartDate date
SET @StartDate = '20190311'
SELECT @StartDate = ISNULL(StartDate, @StartDate)
FROM #actions
WHERE (Predecessor IS NULL OR Predecessor = 0)
ORDER BY StartDate /*Min StartDate if multiple*/
--SELECT @StartDate
;WITH
cteTally10 AS (
SELECT * FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) AS numbers(number)
),
cteTally100 AS (
SELECT 0 AS number
FROM cteTally10 c1
CROSS JOIN cteTally10 c2
),
cteTally10K AS (
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1 AS number
FROM cteTally100 c1
CROSS JOIN cteTally100 c2
),
cte_Future_Dates AS (
SELECT DATEADD(DAY, t.number, @StartDate) AS Date,
ROW_NUMBER() OVER(ORDER BY DATEADD(DAY, t.number, @StartDate)) - 1 AS DaysDiff
FROM cteTally10K t
WHERE t.number BETWEEN 0 AND 365 * 3 + 1 AND
DATEDIFF(DAY, 0, DATEADD(DAY, t.number, @StartDate)) % 7 <> 6
--ORDER BY t.number
),
cte_max_predecessor_durations AS (
SELECT ISNULL(Predecessor, 0) AS Predecessor, MAX(Duration) AS Duration,
ROW_NUMBER() OVER(ORDER BY ISNULL(Predecessor, 0)) AS row_num,
MIN(StartDate) AS StartDate
FROM #actions
GROUP BY ISNULL(Predecessor, 0)
),
cte_activities AS (
SELECT Predecessor, Duration AS Total_Duration, Duration AS Max_Duration, row_num
FROM cte_max_predecessor_durations
WHERE row_num = 1
UNION ALL
SELECT cmpd.Predecessor, CAST(cmpd.Duration + ca.Total_Duration AS decimal(7, 2)),
cmpd.Duration, cmpd.row_num
FROM cte_activities ca
INNER JOIN cte_max_predecessor_durations cmpd ON cmpd.row_num = ca.row_num + 1
)
SELECT a.ActID, a.ActName, a.Duration, a.Predecessor,
--ca.Total_Duration,
cfd_start.Date AS StartDate, cfd_end.Date AS EndDate
FROM #actions a
INNER JOIN cte_activities ca ON ca.Predecessor = ISNULL(a.Predecessor, 0)
INNER JOIN cte_Future_Dates cfd_start ON cfd_start.DaysDiff = ca.Total_Duration - ca.Max_Duration
INNER JOIN cte_Future_Dates cfd_end ON cfd_end.DaysDiff = ca.Total_Duration -
(ca.Max_Duration - a.Duration)