我采用了 Sean 的出色解决方案并对其进行了调整以避免排序。我正在使用一个临时表,以便我可以从我即将发布的执行计划中排除临时变量的创建/填充。
-- Temp table with sample data
IF OBJECT_ID('tempdb..#Something') IS NOT NULL DROP TABLE #Something;
CREATE TABLE #Something
( TicketNo INT
, ActionDate DATETIME
, OldStatus VARCHAR(50)
, NewStatus VARCHAR(50)
);
INSERT #Something VALUES
(1001, '2014-02-14 10:17:05.000', 'Assigned', 'InProgress')
, (1001, '2014-03-05 02:03:44.000', 'InProgress', 'Reply')
, (1001, '2014-03-11 10:00:14.000', 'Reply', 'Resolved')
, (1002, '2015-03-20 04:44:14.000', 'InProgress', 'Reply')
, (1002, '2015-03-21 05:40:02.000', 'Reply', 'Resolved')
-- TOP (1) Solution
SELECT s.TicketNo, s.ActionDate, s.OldStatus, s.NewStatus,
CycleTimeSeconds = DATEDIFF(SECOND, MyLag.ActionDate, s.ActionDate)
FROM #Something AS s
OUTER APPLY
(
SELECT TOP (1) ActionDate
FROM #Something s2
WHERE s2.TicketNo = s.TicketNo
AND s2.ActionDate < s.ActionDate
ORDER BY s2.ActionDate DESC
) AS MyLag;
-- Using MAX instead of TOP (1) to avoid a DESC sort operation
SELECT s.TicketNo, s.ActionDate, s.OldStatus, s.NewStatus,
CycleTimeSeconds = DATEDIFF(SECOND, MyLag.ActionDate, s.ActionDate)
FROM #Something AS s
CROSS APPLY
(
SELECT ActionDate = MAX(ActionDate)
FROM #Something s2
WHERE s2.TicketNo = s.TicketNo
AND s2.ActionDate < s.ActionDate
) AS MyLag;
由于子查询只评估一列,我们可以利用没有GROUP BY 的聚合函数。因为我使用的是聚合 (MAX),所以我总是会返回一行,这就是我将 OUTER APPLY 更改为 CROSS APPLY 的原因。 OUTER APPLY 没有任何问题,但更改它会从该执行计划中删除标量运算符 - 没有性能提升,只是一个更清晰的执行计划。
这种方法还有一个非常大的额外好处:它不仅避免了排序,而且还避免了 DESCending 排序。如果ActionDate 上有索引,则优化器可以利用它通过执行 *ordered-backward 扫描来避免 DESCending 排序。将 ActionDate 上的唯一约束添加到原始临时变量(我不建议这样做,但它适用于本示例)运行由 ActionDate DESC 排序的TOP (1) 查询。
declare @Something table
(
TicketNo int
, ActionDate datetime UNIQUE
, OldStatus varchar(50)
, NewStatus varchar(50)
)
insert @Something values
(1001, '2014-02-14 10:17:05.000', 'Assigned', 'InProgress')
, (1001, '2014-03-05 02:03:44.000', 'InProgress', 'Reply')
, (1001, '2014-03-11 10:00:14.000', 'Reply', 'Resolved')
, (1002, '2015-03-20 04:44:14.000', 'InProgress', 'Reply')
, (1002, '2015-03-21 05:40:02.000', 'Reply', 'Resolved')
SELECT TOP (1) ActionDate
FROM @Something AS s2
ORDER BY s2.ActionDate DESC;
注意向后扫描:
向后扫描会杀死并行性。 Itzik Ben-Gan 在这里讨论:Avoiding a Sort with Descending Order。
有序前向扫描和有序后向扫描之间的一个区别是
前者可以潜在地使用并行性,而后者
目前在
存储引擎
这里的 APPLY 允许我们在需要时在优化器的后袋中进行无排序操作与并行处理。只是更多的证据表明 APPLY 很棒...不需要任何进一步的证据;)