TSQL最后记录效率游标,子查询或CTE

时间:2013-07-30 17:00:14

标签: sql sql-server common-table-expression performance

考虑以下问题......

SELECT
     *
    ,CAST(
            (CurrentSampleDateTime - PreviousSampleDateTime) AS FLOAT
        ) * 24.0 * 60.0 AS DeltaMinutes
FROM    
(   
    SELECT
         C.SampleDateTime AS CurrentSampleDateTime
        ,C.Location
        ,C.CurrentValue
        ,(
            SELECT TOP 1 
                Previous.SampleDateTime
            FROM Samples AS Previous
            WHERE 
                    Previous.Location = C.Location
                AND Previous.SampleDateTime < C.SampleDateTime
            ORDER BY Previous.SampleDateTime DESC       
        ) AS PreviousSampleDateTime
    FROM Samples AS C
) AS TempResults

假设所有事情都是平等的,例如索引等,这是实现上述结果的最有效方法吗?那是使用SubQuery来检索最后一条记录吗?

创建一个按Location,SampleDateTime排序并为CurrentSampleDateTime和PreviousSampleDateTime设置变量的游标会更好吗...在while循环的底部设置Previous到Current?

我对CTE的不太满意这是用CTE可以更有效地完成的事情吗?如果是这样的话会是什么样的?

我可能需要检索PreviousValue和Previous SampleDateTime以获得两者的平均值。这会改变结果吗。

长话短说如果您需要在当前记录的计算中使用这些值,那么保持前一记录值的最佳/最有效方法是什么?

---- UPDATE 我应该注意到我在Location,SampleDateTime,CurrentValue上有一个聚集索引,所以可能这对结果的影响最大。

有5,591,571条记录我的查询(上面的一条)平均需要3分20秒

Joachim Isaksson平均下面的CTE需要5分15秒。

也许它需要更长的时间,因为它没有使用聚集索引,而是使用rownumber进行连接?

我开始测试光标方法,但它已经在10分钟了......所以不要继续那个。

我会给它一天左右的时间,但我想我会接受Joachim Isaksson提供的CTE答案,因为我找到了获得最后一行的新方法。

有人可以同意,它是使Location子查询方法更快的Location,SampleDateTime,CurrentValue的索引吗?

我没有SQL Server 2012,因此无法测试LEAD / LAG方法。我敢打赌,这比我尝试的任何事情都要快,因为微软有效地实施了这一点。可能只需将指针交换到每行末尾的内存引用。

2 个答案:

答案 0 :(得分:1)

与往常一样,使用您的真实数据进行测试是最重要的。

这是一个CTE版本,显示每个位置的样本,其中包含前一个样本的时间增量。它使用OVER排名,与子查询相比,它通常可以很好地解决同样的问题。

WITH cte AS (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY Location 
                               ORDER BY SampleDateTime DESC) rn
  FROM Samples
)
SELECT a.*,CAST((a.SampleDateTime - b.SampleDateTime) AS FLOAT) 
                 * 24.0 * 60.0 AS DeltaMinutes
FROM cte a
LEFT JOIN cte b ON a.Location = b.Location AND b.rn = a.rn +1

An SQLfiddle to test with

答案 1 :(得分:1)

如果您使用的是SQL Server 2012,则可以使用LAG窗口函数来检索上一行中指定列的值。如果没有前一行,则返回null。

SELECT 
 a.*,
 CAST((a.SampleDateTime - LAG(a.SampleDateTime) OVER(PARTITION BY a.location ORDER BY a.SampleDateTime ASC)) AS FLOAT) 
             * 24.0 * 60.0 AS DeltaMinutes
FROM samples a
ORDER BY
 a.location,
 a.SampleDateTime

你必须运行一些测试才能看出它是否更快。如果你没有使用SQL Server 2012那么至少这可能让其他人知道如何用2012完成它。我喜欢@Joachim Isaksson的答案,使用带有Row_Number()/ Partition By的CTE 2008和2005

SQL Fiddle

您是否考虑过创建临时表来代替CTE或子查询?您可以在临时表上创建更适合RowNumber上的连接的索引。

CREATE TABLE #tmp (
  RowNumber INT,
  Location INT,
  SampleDateTime DATETIME,
  CurrentValue INT)
;

INSERT INTO #tmp
 SELECT 
  ROW_NUMBER() OVER (PARTITION BY Location 
                           ORDER BY SampleDateTime DESC) rn,
  Location,
  SampleDateTime,
  CurrentValue
 FROM Samples
;

CREATE INDEX idx_location_row ON #tmp(Location,RowNumber) INCLUDE (SampleDateTime,CurrentValue);

SELECT 
 a.Location,
 a.SampleDateTime,
 a.CurrentValue,
 CAST((a.SampleDateTime - b.SampleDateTime) AS FLOAT) * 24.0 * 60.0 AS DeltaMinutes
FROM #tmp a
LEFT JOIN #tmp b ON 
 a.Location = b.Location 
 AND b.RowNumber = a.RowNumber +1  
ORDER BY
 a.Location, 
 a.SampleDateTime

SQL Fiddle #2