【问题标题】:Get first value outside where window with lag function使用滞后函数获取窗口外的第一个值
【发布时间】:2021-08-27 03:31:30
【问题描述】:

在 SQL Server 中对时间序列使用 lag 函数时,我总是与时间序列中的 first 值作斗争。 假设这个简单的例子

CREATE TABLE demo
    ([id] int, [time] date, [content] int)
;
INSERT INTO demo (id, time, content) VALUES
  (1, '2021-05-31', cast(rand()*1000 as int)),
  (2, '2021-06-01', cast(rand()*1000 as int)),
  (3, '2021-06-02',cast(rand()*1000 as int)),
  (4, '2021-06-03', cast(rand()*1000 as int)),
(5, '2021-06-04', cast(rand()*1000 as int)),
(6, '2021-06-05', cast(rand()*1000 as int)),
(7, '2021-06-06', cast(rand()*1000 as int)),
(8, '2021-06-07', cast(rand()*1000 as int)),
(9, '2021-06-08', cast(rand()*1000 as int));

我想在六月获取所有值及其之前的值,所以像这样

select content, lag(content, 1, null) over (order by time)
from demo
where time >= '2021-06-01'

到目前为止一切都很好,但是,第一个条目将导致前一个值的 null。

当然有很多关于如何填充空值的解决方案,例如子选择更大的范围等,但对于非常大的表,我不知何故认为应该有一个优雅的解决方案。

有时我会做这样的事情

select content, lag(content, 1, 
(select content from demo d1 join 
(select max(time) maxtime from demo where time < '2021-06-01') d2 on d1.time = d2.maxtime
)) over (order by time)
from demo
where time >= '2021-06-01'

有没有更有效的方法? (注意:当然对于这个简单的例子我没有什么不同,但是对于具有分区和 500'000'000 个条目的表,应该找到最有效的解决方案)

查看fiddle

【问题讨论】:

  • 那么你想为第一个值显示什么值?
  • 第一行的日期为2021-06-01,前一个值为2021-5-31,内容为1,所以为1

标签: sql sql-server tsql lag


【解决方案1】:

关键思想是使用子查询:

select t.*
from (select content, lag(content) over (order by time)
      from demo d
     ) d
where time >= '2021-06-01';

这可能会扫描整个表。但是,您可以创建索引demo(time, content) 来帮助lag()。

接下来,如果您有合理的回溯期,您可以对此进行优化。例如,如果每个月都有记录,则在子查询中回溯一个月:

select t.*
from (select content, lag(content) over (order by time)
      from demo d
      where time >= '2021-05-01'
     ) d
where time >= '2021-06-01';

如果您的数据是分区的,这一点也非常重要——因为大表不会如此。

【讨论】:

  • 所以,这基本上是我一直在做的事情......似乎不是更有效的事情
【解决方案2】:

对于这种特殊情况,通过您的 cmets,您可能首先计算整个未过滤表的延迟,然后根据日期进行子查询:

WITH cte AS (
    SELECT time, content, LAG(content) OVER (ORDER BY time) lag_content
    FROM demo
)

SELECT content, lag_content
FROM cte
WHERE time >= '2021-06-01';

【讨论】:

  • 我在我的 big 表上尝试了这个,有 50 亿个条目,它的速度快得离谱。这怎么可能?我会认为 cte 将所有数据复制到内存中?
  • @rst 我很惊讶它在这么大的表上运行得这么快,但是在 SQL Server 中,CTE 只是被内联了。所以,我的回答就是SELECT content, lag_content FROM ( ... cte here ...) t WHERE time &gt;= '2021-06-01'
  • 对不起,这是我这边的一个错误。执行持续了几分钟。使用子查询等明显更快。
【解决方案3】:

您希望空值是什么?在下面的示例中,我将它们设置为 0。

SELECT
    content,
    coalesce(LAG(content, 1, NULL) OVER(
        ORDER BY
            time
    ), content-1) lag_content
FROM
    demo
WHERE
    time >= '2021-06-01'

输出:

content lag_content
-------------------
      2           1
      3           2
      4           3
      5           4
      6           5
      7           6
      8           7
      9           8

在这里试试:dbfiddle

【讨论】:

  • 上例中的 null 值实际上应该是 1,即时间为 2021-05-31 的值,这是实际的滞后值,但不是 where 子句的一部分
  • 我已将合并默认值更改为 1,但这是否适合其他示例?
  • 嗯,不,1 只是一个例子,但如果不搜索它,我不知道该行中的值。除此之外,您可以简单地回避您的声明以包括 1 其中 null 代表并删除合并
  • @rst 我刚刚修改了我的答案。请检查一下,如果它满足您的需求,请告诉我。
  • 我看不到小提琴更新。你能查一下吗?
猜你喜欢
  • 2018-07-24
  • 1970-01-01
  • 2017-04-30
  • 1970-01-01
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
  • 2015-02-16
相关资源
最近更新 更多