LAG 和 LEAD 函数

Posted

技术标签:

【中文标题】LAG 和 LEAD 函数【英文标题】:LAG and LEAD functions 【发布时间】:2012-10-18 11:09:15 【问题描述】:

在 SQL Server 2012 中使用新的 有哪些优势? 仅仅是更容易编写和更容易调试查询,还是还有性能改进?

这对我来说很重要,因为我们经常需要这种类型的功能,我需要知道我们是否应该在不久的将来推荐升级。 如果只是更简单的查询,那么升级的麻烦(和成本)就不值得了。

【问题讨论】:

你能澄清一下这个问题吗?您说您已经需要这种类型的功能,但您想知道是否需要这些功能。据推测,这意味着您正在使用替代解决方案来解决您的需求,在这种情况下,它们是什么? 我使用 LAG 重写了一个自连接查询(参见 my question),除了更小更简单之外,查询时间从 2.6 秒下降到 1 秒。 (或者如果您计算查询优化器的不良行为,则从 40 秒到 1 秒)。显然这只是一则轶事,但性能差异令我震惊且极具说服力。 【参考方案1】:

为了展示执行计划的不同,我使用了 Dave 的 SQL 权威博客中的获胜解决方案:

Solution to Puzzle – Simulate LEAD() and LAG() without Using SQL Server 2012 Analytic Function
;WITH T1
AS (SELECT row_number() OVER (ORDER BY SalesOrderDetailID) N
         , s.SalesOrderID
         , s.SalesOrderDetailID
    FROM
        TempDB.dbo.LAG s
    WHERE
        SalesOrderID IN (20120303, 20120515, 20120824, 20121031))
SELECT SalesOrderID
     , SalesOrderDetailID AS CurrentSalesOrderDetailID
/*   , CASE
           WHEN N % 2 = 1 THEN
               max(CASE
                   WHEN N % 2 = 0 THEN
                       SalesOrderDetailID
               END) OVER (PARTITION BY (N + 1) / 2)
           ELSE
               max(CASE
                   WHEN N % 2 = 1 THEN
                       SalesOrderDetailID
               END) OVER (PARTITION BY N / 2)
       END LeadVal */
     , CASE
           WHEN N % 2 = 1 THEN
               max(CASE
                   WHEN N % 2 = 0 THEN
                       SalesOrderDetailID
               END) OVER (PARTITION BY N / 2)
           ELSE
               max(CASE
                   WHEN N % 2 = 1 THEN
                       SalesOrderDetailID
               END) OVER (PARTITION BY (N + 1) / 2)
       END PreviousSalesOrderDetailID
FROM
    T1
ORDER BY
    SalesOrderID
  , SalesOrderDetailID;



SELECT SalesOrderID
     , SalesOrderDetailID AS CurrentSalesOrderDetailID
     , LAG(SalesOrderDetailID, 1, 0) OVER (ORDER BY SalesOrderID, SalesOrderDetailID) AS PreviousSalesOrderDetailID
FROM TempDB.dbo.LAG
WHERE SalesOrderID  IN (20120303, 20120515, 20120824, 20121031);





Warning: Null value is eliminated by an aggregate or other SET operation.

(10204 row(s) affected)
Table 'Worktable'. Scan count 6, logical reads 81638, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'LAG'. Scan count 4, logical reads 48, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:   CPU time = 297 ms,  elapsed time = 332 ms.

--- versus ---

(10204 row(s) affected)
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'LAG'. Scan count 4, logical reads 48, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:   CPU time = 78 ms,  elapsed time = 113 ms.

除了更优雅之外,它消耗的资源也更少。

以下是图形执行计划的比较:

在这种特定情况下,执行计划显示出明显的赢家。 Dave 的页面有许多可能的不同方式来获得 LEAD/LAG 功能。也许其中一些会击败 SQL Server 的内部解决方案。或者,也许不是。

【讨论】:

【参考方案2】:

我不能对 MS SQL Server 2012 发表太多评论,但从 PostgreSQL 的角度来看,这些功能自版本 8.4 起就已可用。

一般来说,它们非常便于检测变化(通常在时间序列中,与ORDER BY 结合使用)。通常:

WITH shifted_timeseries AS (
    SELECT event_time,
           value,
           LAG(value) OVER (ORDER BY event_time) AS lagged_value
        FROM timeseries
)
SELECT event_time AS change_time, value AS new_value
FROM shifted_timeseries
    WHERE value != lagged_value;

对于这类事情,仅就清晰度而言,它们是值得的(尽管这可能是主观的)。

对于更复杂的操作,例如,如果你想要连续值的时间段,this answer 是解决这个问题的一个非常优雅的解决方案。根据this SQLFiddle,它似乎在 SQL Server 2012 中运行良好。

这两个博客条目还显示了使用 LEAD/LAG 与不使用相同查询的比较:

Write T-SQL Self Join Without Using LEAD and LAG Solution to Puzzle – Simulate LEAD() and LAG() without Using SQL Server 2012 Analytic Function

(比较执行计划会很有趣。)

【讨论】:

以上是关于LAG 和 LEAD 函数的主要内容,如果未能解决你的问题,请参考以下文章

Hive分析函数LAG和LEAD详解

ORACLE 偏移分析函数 lag()与lead() 用法

ORACLE 偏移分析函数 lag()与lead() 用法

如何为不同的观察组使用 LAG 和 LEAD 窗口函数

LAG 和 LEAD 函数

lag与lead函数 oracle_11g