Since only the estimated plan is present, these are going to be some guesses.
This part
WHERE j.Status >= 60 AND (j.Status <= 70 OR (dsp.BatchNumber > 1 AND x.CabinetCount IS NULL))
Creates a filter far in the execution plan:
on this part:
(j.Status <= 70 OR (dsp.BatchNumber > 1 AND x.CabinetCount IS NULL))
You could try adding a union to split them up, but due to the amount of joins I would try using a temp table to split some logic in two pieces.
I don't know your datatypes so I am going to use a SELECT ... INTO
. Consider changing this to a INSERT ... SELECT
.
Temp table insert:
SELECT j.JobId,
j.OrderId,
j.OrderType,
j.ShopName,
j.ShipDate,
j.CompletionDate,
j.InstallDate,
j.DayPriority,
j.ShipDateConfirmed,
j.IsAddon,
j.CashOnDelivery,
j.FinalMeasure,
ds.ShopLabel,
ds.DesignSetId,
--dsp.DesignSetProcessId,
j.PaintBottleneck,
j.DoorBottleNeck,
j.AssemblyBottleNeck,
--Schedule.Workday(j.CompletionDate, -p.DaysDueBeforeCompleteDate) AS DesignSetProcessDueDate,
-- dsp.DatetimeStarted,
--dsp.DatetimeComplete,
--dsp.BatchNumber,
--p.Name AS ProcessName,
--p.ProcessId,
--p.ZoneId,
--X.CabinetCount,
j.InstallScheduled,
j.RedTicket,
j.InstallDateRange,
--V.VitalItems,
--NV.NonVitalItems,
--Pj.ParentJobInstalled,
Schedule.WorkdaysByDate(j.ShipDate, j.InstallDate) AS ShipInstallDiff,
dss.CatalogId AS SpeciesId,
dsld.CatalogId AS LowerDoorId,
dsupd.CatalogId AS UpperDoorId,
dsf.CatalogId AS FrontId,
dsfs.CatalogId AS FinishId,
dsg.CatalogId AS GlazeId,
dsfo.CatalogId AS FinishOptionId,
dsds.CatalogId AS DistressId,
dssh.CatalogId AS SheenId,
dsi.CatalogId AS InteriorId,
dsh.CatalogId AS HingeId,
dsd.CatalogId AS DrawerId,
dssl.CatalogId AS SlideId,
j.ParentJobId --added for outer apply
INTO #temp
FROM Jobs j
JOIN DesignSets ds ON j.JobId = ds.JobId
JOIN Species dss ON ds.DesignSetId = dss.DesignSetId
JOIN LowerDoors dsld ON ds.DesignSetId = dsld.DesignSetId
JOIN UpperDoors dsupd ON ds.DesignSetId = dsupd.DesignSetId
JOIN Fronts dsf ON ds.DesignSetId = dsf.DesignSetId
JOIN Finishes dsfs ON ds.DesignSetId = dsfs.DesignSetId
JOIN Glazes dsg ON ds.DesignSetId = dsg.DesignSetId
JOIN FinishOptions dsfo ON ds.DesignSetId = dsfo.DesignSetId
JOIN Distresses dsds ON ds.DesignSetId = dsds.DesignSetId
JOIN Sheens dssh ON ds.DesignSetId = dssh.DesignSetId
JOIN Interiors dsi ON ds.DesignSetId = dsi.DesignSetId
JOIN Hinges dsh ON ds.DesignSetId = dsh.DesignSetId
JOIN Drawers dsd ON ds.DesignSetId = dsd.DesignSetId
JOIN Slides dssl ON ds.DesignSetId = dssl.DesignSetId
WHERE j.Status >= 60;
You could then change the query to this
SELECT temp.JobId,
temp.OrderId,
temp.OrderType,
temp.ShopName,
temp.ShipDate,
temp.CompletionDate,
temp.InstallDate,
temp.DayPriority,
temp.ShipDateConfirmed,
temp.IsAddon,
temp.CashOnDelivery,
temp.FinalMeasure,
temp.ShopLabel,
temp.DesignSetId,
dsp.DesignSetProcessId,
temp.PaintBottleneck,
temp.DoorBottleNeck,
temp.AssemblyBottleNeck,
Schedule.Workday(j.CompletionDate, -p.DaysDueBeforeCompleteDate) AS DesignSetProcessDueDate,
-- dsp.DatetimeStarted,
dsp.DatetimeComplete,
dsp.BatchNumber,
p.Name AS ProcessName,
p.ProcessId,
p.ZoneId,
X.CabinetCount,
temp.InstallScheduled,
temp.RedTicket,
temp.InstallDateRange,
V.VitalItems,
NV.NonVitalItems,
Pj.ParentJobInstalled,
temp.ShipInstallDiff,
temp.CatalogId AS SpeciesId,
temp.CatalogId AS LowerDoorId,
temp.CatalogId AS UpperDoorId,
temp.CatalogId AS FrontId,
temp.CatalogId AS FinishId,
temp.CatalogId AS GlazeId,
temp.CatalogId AS FinishOptionId,
temp.CatalogId AS DistressId,
temp.CatalogId AS SheenId,
temp.CatalogId AS InteriorId,
temp.CatalogId AS HingeId,
temp.CatalogId AS DrawerId,
temp.CatalogId AS SlideId
FROM #temp temp
LEFT OUTER JOIN Schedule.DesignSetProcesses dsp ON temp.DesignSetId = dsp.DesignSetId
LEFT OUTER JOIN Schedule.Processes p ON dsp.ProcessId = p.ProcessId
OUTER APPLY(Select SUM(pr.Quantity * pr.DeliveryCnt) AS CabinetCount From Products pr Where temp.DesignSetId = pr.DesignSetId AND
(dsp.BatchNumber = pr.BatchNumber OR dsp.DesignSetId IS NULL)) AS X
OUTER APPLY(SELECT COUNT(pur.Vital) AS 'VitalItems'
FROM Orders ord JOIN PurchaseItems pur ON ord.ItemId = pur.ItemId AND pur.Vital = 1
WHERE ord.JobId = j.JobId AND ord.Received = 0) AS V
OUTER APPLY(SELECT COUNT(pur.ItemId) AS 'NonVitalItems'
FROM Orders ord JOIN PurchaseItems pur ON ord.ItemId = pur.ItemId AND pur.Vital = 0
WHERE ord.JobId = j.JobId AND ord.Received = 0) AS NV
OUTER APPLY(SELECT Schedule.WorkdaysByDate(job.InstallDate, Schedule.GetUtahDate()) AS 'ParentJobInstalled'
FROM Jobs job
WHERE job.JobId = temp.ParentJobId) AS Pj
WHERE (j.Status <= 70 OR (dsp.BatchNumber > 1 AND x.CabinetCount IS NULL))
Apart from this your functions are also going to hurt, depending on how much data is returned.
You could add this index:
CREATE INDEX IX_Calendar_DateId_filtered
ON Schedule.Calendar(DateId)
INCLUDE(IsWorkDay)
WHERE IsWorkDay = 1;
The rest of the indexing is hard to say with an estimated plan, there are index posbilities such as:
CREATE INDEX IX_Jobs_Status_JobID
ON Jobs(Status,JobId)
INCLUDE(
OrderId,
OrderType,
ShopName,
ShipDate,
CompletionDate,
InstallDate,
DayPriority,
ShipDateConfirmed,
IsAddon,
CashOnDelivery,
FinalMeasure,
PaintBottleneck,
DoorBottleNeck,
AssemblyBottleNeck,
InstallScheduled,
RedTicket,
InstallDateRange,
ParentJobId
);
CREATE INDEX IX_DesignSets
ON DesignSets(JobId,DesignSetId)
INCLUDE(ShopLabel);
But I think that getting the query to apply predicates earlier and/or splitting up the work will have bigger results at the moment.
EDIT
You could try changing the scalar functions to APPLY's. Since you have many apply's already you could add the first function as two apply's on the temp table insert:
SELECT j.JobId,
j.OrderId,
j.OrderType,
j.ShopName,
j.ShipDate,
j.CompletionDate,
j.InstallDate,
j.DayPriority,
j.ShipDateConfirmed,
j.IsAddon,
j.CashOnDelivery,
j.FinalMeasure,
ds.ShopLabel,
ds.DesignSetId,
--dsp.DesignSetProcessId,
j.PaintBottleneck,
j.DoorBottleNeck,
j.AssemblyBottleNeck,
--Schedule.Workday(j.CompletionDate, -p.DaysDueBeforeCompleteDate) AS DesignSetProcessDueDate,
-- dsp.DatetimeStarted,
--dsp.DatetimeComplete,
--dsp.BatchNumber,
--p.Name AS ProcessName,
--p.ProcessId,
--p.ZoneId,
--X.CabinetCount,
j.InstallScheduled,
j.RedTicket,
j.InstallDateRange,
--V.VitalItems,
--NV.NonVitalItems,
--Pj.ParentJobInstalled,
ISNULL([WorkdaysByDate1],[WorkdaysByDate2])
dss.CatalogId AS SpeciesId,
dsld.CatalogId AS LowerDoorId,
dsupd.CatalogId AS UpperDoorId,
dsf.CatalogId AS FrontId,
dsfs.CatalogId AS FinishId,
dsg.CatalogId AS GlazeId,
dsfo.CatalogId AS FinishOptionId,
dsds.CatalogId AS DistressId,
dssh.CatalogId AS SheenId,
dsi.CatalogId AS InteriorId,
dsh.CatalogId AS HingeId,
dsd.CatalogId AS DrawerId,
dssl.CatalogId AS SlideId,
j.ParentJobId --added for outer apply
INTO #temp
FROM Jobs j
JOIN DesignSets ds ON j.JobId = ds.JobId
JOIN Species dss ON ds.DesignSetId = dss.DesignSetId
JOIN LowerDoors dsld ON ds.DesignSetId = dsld.DesignSetId
JOIN UpperDoors dsupd ON ds.DesignSetId = dsupd.DesignSetId
JOIN Fronts dsf ON ds.DesignSetId = dsf.DesignSetId
JOIN Finishes dsfs ON ds.DesignSetId = dsfs.DesignSetId
JOIN Glazes dsg ON ds.DesignSetId = dsg.DesignSetId
JOIN FinishOptions dsfo ON ds.DesignSetId = dsfo.DesignSetId
JOIN Distresses dsds ON ds.DesignSetId = dsds.DesignSetId
JOIN Sheens dssh ON ds.DesignSetId = dssh.DesignSetId
JOIN Interiors dsi ON ds.DesignSetId = dsi.DesignSetId
JOIN Hinges dsh ON ds.DesignSetId = dsh.DesignSetId
JOIN Drawers dsd ON ds.DesignSetId = dsd.DesignSetId
JOIN Slides dssl ON ds.DesignSetId = dssl.DesignSetId
OUTER APPLY (SELECT SUM(IsWorkDay) AS Workdays
From Schedule.Calendar
Where DateId >= j.ShipDate AND DateId <= j.InstallDate) as [WorkdaysByDate1]
OUTER APPLY (Select SUM(IsWorkDay) AS Workdays
From Schedule.Calendar
Where DateId <= j.ShipDate AND DateId >= j.InstallDate) as [WorkdaysByDate2]
WHERE j.Status >= 60;
EDIT 2
So, when I remove the scalar functions, on my SELECT INTO query my
temp table execution time is CPU time = 281 ms, elapsed time = 293 ms.
and my query execution time is CPU time = 1969 ms, elapsed time = 3694
ms. On my INSERT INTO query my temp table execution time is CPU time
282 ms, elapsed time = 442 ms. and my query execution time is CPU time
1984 ms, elapsed time = 3883 ms.
Most of the execution time is on the second part, what happens when you write the second part of the query like this?
SELECT temp.JobId,
temp.OrderId,
temp.OrderType,
temp.ShopName,
temp.ShipDate,
temp.CompletionDate,
temp.InstallDate,
temp.DayPriority,
temp.ShipDateConfirmed,
temp.IsAddon,
temp.CashOnDelivery,
temp.FinalMeasure,
temp.ShopLabel,
temp.DesignSetId,
dsp.DesignSetProcessId,
temp.PaintBottleneck,
temp.DoorBottleNeck,
temp.AssemblyBottleNeck,
Schedule.Workday(j.CompletionDate, -p.DaysDueBeforeCompleteDate) AS DesignSetProcessDueDate,
-- dsp.DatetimeStarted,
dsp.DatetimeComplete,
dsp.BatchNumber,
p.Name AS ProcessName,
p.ProcessId,
p.ZoneId,
X.CabinetCount,
temp.InstallScheduled,
temp.RedTicket,
temp.InstallDateRange,
V.VitalItems,
NV.NonVitalItems,
Pj.ParentJobInstalled,
temp.ShipInstallDiff,
temp.CatalogId AS SpeciesId,
temp.CatalogId AS LowerDoorId,
temp.CatalogId AS UpperDoorId,
temp.CatalogId AS FrontId,
temp.CatalogId AS FinishId,
temp.CatalogId AS GlazeId,
temp.CatalogId AS FinishOptionId,
temp.CatalogId AS DistressId,
temp.CatalogId AS SheenId,
temp.CatalogId AS InteriorId,
temp.CatalogId AS HingeId,
temp.CatalogId AS DrawerId,
temp.CatalogId AS SlideId
FROM #temp temp
LEFT OUTER JOIN Schedule.DesignSetProcesses dsp ON temp.DesignSetId = dsp.DesignSetId
LEFT OUTER JOIN Schedule.Processes p ON dsp.ProcessId = p.ProcessId
OUTER APPLY(Select SUM(pr.Quantity * pr.DeliveryCnt) AS CabinetCount From Products pr Where temp.DesignSetId = pr.DesignSetId AND
(dsp.BatchNumber = pr.BatchNumber OR dsp.DesignSetId IS NULL)) AS X
OUTER APPLY(SELECT COUNT(pur.Vital) AS 'VitalItems'
FROM Orders ord JOIN PurchaseItems pur ON ord.ItemId = pur.ItemId AND pur.Vital = 1
WHERE ord.JobId = j.JobId AND ord.Received = 0) AS V
OUTER APPLY(SELECT COUNT(pur.ItemId) AS 'NonVitalItems'
FROM Orders ord JOIN PurchaseItems pur ON ord.ItemId = pur.ItemId AND pur.Vital = 0
WHERE ord.JobId = j.JobId AND ord.Received = 0) AS NV
OUTER APPLY(SELECT Schedule.WorkdaysByDate(job.InstallDate, Schedule.GetUtahDate()) AS 'ParentJobInstalled'
FROM Jobs job
WHERE job.JobId = temp.ParentJobId) AS Pj
WHERE (dsp.BatchNumber > 1 AND x.CabinetCount IS NULL))
UNION
SELECT temp.JobId,
temp.OrderId,
temp.OrderType,
temp.ShopName,
temp.ShipDate,
temp.CompletionDate,
temp.InstallDate,
temp.DayPriority,
temp.ShipDateConfirmed,
temp.IsAddon,
temp.CashOnDelivery,
temp.FinalMeasure,
temp.ShopLabel,
temp.DesignSetId,
dsp.DesignSetProcessId,
temp.PaintBottleneck,
temp.DoorBottleNeck,
temp.AssemblyBottleNeck,
Schedule.Workday(j.CompletionDate, -p.DaysDueBeforeCompleteDate) AS DesignSetProcessDueDate,
-- dsp.DatetimeStarted,
dsp.DatetimeComplete,
dsp.BatchNumber,
p.Name AS ProcessName,
p.ProcessId,
p.ZoneId,
X.CabinetCount,
temp.InstallScheduled,
temp.RedTicket,
temp.InstallDateRange,
V.VitalItems,
NV.NonVitalItems,
Pj.ParentJobInstalled,
temp.ShipInstallDiff,
temp.CatalogId AS SpeciesId,
temp.CatalogId AS LowerDoorId,
temp.CatalogId AS UpperDoorId,
temp.CatalogId AS FrontId,
temp.CatalogId AS FinishId,
temp.CatalogId AS GlazeId,
temp.CatalogId AS FinishOptionId,
temp.CatalogId AS DistressId,
temp.CatalogId AS SheenId,
temp.CatalogId AS InteriorId,
temp.CatalogId AS HingeId,
temp.CatalogId AS DrawerId,
temp.CatalogId AS SlideId
FROM #temp temp
LEFT OUTER JOIN Schedule.DesignSetProcesses dsp ON temp.DesignSetId = dsp.DesignSetId
LEFT OUTER JOIN Schedule.Processes p ON dsp.ProcessId = p.ProcessId
OUTER APPLY(Select SUM(pr.Quantity * pr.DeliveryCnt) AS CabinetCount From Products pr Where temp.DesignSetId = pr.DesignSetId AND
(dsp.BatchNumber = pr.BatchNumber OR dsp.DesignSetId IS NULL)) AS X
OUTER APPLY(SELECT COUNT(pur.Vital) AS 'VitalItems'
FROM Orders ord JOIN PurchaseItems pur ON ord.ItemId = pur.ItemId AND pur.Vital = 1
WHERE ord.JobId = j.JobId AND ord.Received = 0) AS V
OUTER APPLY(SELECT COUNT(pur.ItemId) AS 'NonVitalItems'
FROM Orders ord JOIN PurchaseItems pur ON ord.ItemId = pur.ItemId AND pur.Vital = 0
WHERE ord.JobId = j.JobId AND ord.Received = 0) AS NV
OUTER APPLY(SELECT Schedule.WorkdaysByDate(job.InstallDate, Schedule.GetUtahDate()) AS 'ParentJobInstalled'
FROM Jobs job
WHERE job.JobId = temp.ParentJobId) AS Pj
WHERE (j.Status <= 70)