【问题标题】:TSQL Last Record Efficiency Cursor, SubQuery, or CTETSQL 最后记录效率游标、子查询或 CTE
【发布时间】:2013-07-30 17:00:13
【问题描述】:

考虑以下查询...

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

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

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

我不太擅长 CTE,这是否可以通过 CTE 更有效地完成?如果是这样,那会是什么样子?

我可能必须检索 PreviousValue 和 Previous SampleDateTime 才能获得两者的平均值。这会改变结果吗?

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

----更新 我应该注意,我在 Location、SampleDateTime、CurrentValue 上有一个聚集索引,所以也许这对结果的影响比什么都大。

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

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

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

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

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

谁能同意是 Location、SampleDateTime、CurrentValue 上的索引使子查询方法更快?

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

【问题讨论】:

  • 我至少会使用 MIN 而不是 TOP 1 x... ORDER BY x (除非该构造是先对 null 进行排序)
  • @JoachimIsaksson 但他需要MAX
  • 您使用的是哪个版本的 SQL Server?
  • @Lamak 是的,MAX 是有意义的,只需翻译当前查询(可能是错误的)MIN
  • 我问是因为如果使用 2012,您将可以访问 LEADLAG

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


【解决方案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

【讨论】:

  • 4:10 分钟我的结果集...比 CTE 快,但仍然比我的原始速度慢。感谢您提供有关临时表索引的额外信息。我猜如果我删除了我当前的索引,临时表方法会比我的更快,很抱歉我无法在我的 2008 服务器上测试 LEAD/LAG 方法。
  • 您是否尝试使用“ORDER BY”?如果您不需要 ORDER BY,那么您可以将其从最终选择中删除,因为您正在处理大型数据集并且排序会占用大量 CPU。这可能会加快速度。您还可以在临时表的位置、行号、采样日期时间、当前值上创建一个集群索引,而不是使用包含的常规索引。
  • 实际上通过将 order by 更改为“ORDER BY Location, SampleDateTime DESC”将您的时间缩短到 3:51...基本上将 Location 放在它的前面。我认为它提高了性能,因为这是定义原始索引的原因。
  • 由于您的路线比 CTE 路线更快,并且您引入了一个我没有考虑过的新选项,因此我将接受您的。无论如何我都可以接受?
【解决方案2】:

与往常一样,使用真实数据进行测试才是王道。

这是一个 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.

【讨论】:

  • 在您的示例中,表中有 5 次插入...结果中应该有 5 条记录而不是 3 条。对于每个位置的第一个条目,DeltaMinutes 将为空,因为它没有以前的样本日期时间。假设我有一个位置每周采样 7 次,结果集应该有该位置的 7 条记录。我不是在减少/压缩记录集,而是根据当前记录和以前的记录添加计算信息。
  • 在您的 SQLFiddle 示例中执行我的查询以查看我正在寻找的结果。也不一定是如何将 SQL 转换为 CTE 的问题,虽然它会很好......但更重要的是,哪种方法、子查询、游标、cte 会给我最佳性能。我当前的查询很慢。
  • @DanP 排名函数(在这种情况下为 ROW_NUMBER/PARTITION)在分析多个数据点(位置)的事件序列时通常非常快。我不能代表您的数据集或索引,但我发现它们很难被击败。
  • 是的,新小提琴检索结果,结果集与我的原始查询相同,但现在哪个具有更好的性能,我最好使用带有临时变量的游标来解决这个问题?
  • @DanP 我想不出一种在不增加时间复杂度的情况下使用游标的方法,这会对大型数据集造成伤害,但其他人可能会。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-19
  • 1970-01-01
  • 2011-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多