【问题标题】:How to best retrieve the most recent previous record for each record如何最好地检索每条记录的最近的先前记录
【发布时间】:2012-10-02 00:39:51
【问题描述】:

假设您有一个简单的表,该表表示由 someID 标识的另一个实体的时间序列。每行由 someID 和时间戳标识,时间戳不受任何规则增量的影响,即间隔可以变化:

CREATE TABLE someSeries
(
   someID int not null,
   rowTS datetime not null,
   val int not null
);
ALTER TABLE someSeries 
ADD CONSTRAINT PK_someSeries(someID, rowTS);

是否有一种优雅而有效的方式(因此不使用笛卡尔积)返回所有行并显示该行的 rowTS 和该 someID 的最近的先前 rowTS?

例如如果数据是

someID        rowTS            val
------------------------------------
1             9/1/2012         2
1             9/2/2012         3
1             9/5/2012         5
2             9/2/2012         1
2             9/4/2012         6
3             9/5/2012         7
3             9/7/2012         9
3             9/10/2012        2

该查询应该返回

someID        rowTS            prevRowTS          val          prevVal
------------------------------------------------------------------------
1             9/1/2012         null               2            null
1             9/2/2012         9/1/2012           3            2 
1             9/5/2012         9/2/2012           5            3
2             9/2/2012         null               1            null
2             9/4/2012         9/2/2012           6            1
3             9/5/2012         null               7            null
3             9/7/2012         9/5/2012           9            7
3             9/10/2012        9/7/2012           2            9

目前,我在我的应用程序中需要这样的东西,我这样做的方式是在应用程序层中,基本上我将最后一个 rowTS 存储在 someID 主表中,它是 PK,然后,在插入时间序列时,我从主表中获取该值并查找最近的先前记录,并进行一些计算(例如比较 val 和 prevVal)并将其插入时间序列表中。

但我想知道是否有一种仅使用 SQL 的快速方法。唯一想到的是笛卡尔积,不用说,这不是很有效。

【问题讨论】:

  • 没关系,马哈茂德。是吗?
  • 我认为任何 SQL 实现的行为都应该相同。在我的例子中,使用了 MySQL,但查询的格式应该在所有 RDBMS 中保持相同
  • 为了比较,让我们尝试为 MySQL 和 Oracle 解决它。并且不使用游标,只需一个查询

标签: sql


【解决方案1】:

对于 SQL Server、Oracle 和 PostgreSQL - 使用窗口函数

;with cte as (
select *, rn=row_number() over (partition by someid order by rowTS)
from someSeries
)
select a.someID, a.rowTS, b.rowTS prevRowTS, a.val, b.val prevVal
from cte a
left join cte b on a.someid = b.someID and b.rn = a.rn-1
order by a.someID, a.rowts

对于 SQL Server 2012 和 Oracle,使用 LAG 函数可以轻松超越上述。

select
    someid,
    rowts,
    lag(rowts) over (partition by someid order by rowts) prevrowts,
    val,
    lag(val) over (partition by someid order by rowts) prevval
from someSeries
order by someid, rowts

对于 MySQL ONLY,可以破解,但性能非常好。

select
  @ts:=rowts rowts,
  if(@s=someID,@ts,null) prevrowts,
  @v:=val val,
  if(@s=someID,@v,null) prevval,
  @s:=someID someID
from (select @s:=null) a, someSeries
order by someID, rowts

注意:虽然您可能会受到诱惑,但不要将 someID 列移到其他列之前。

【讨论】:

    【解决方案2】:

    既然你说你使用什么 RDBMS 并不重要,下面是你在 SQL Server 中的做法:

    ;WITH cte
    AS
    (
        SELECT *, ROW_NUMBER() OVER(Partition BY someID ORDER BY someID, rowTS) row_num
        FROM @Temp
    )
    SELECT c1.someID, c1.rowTS, 
      (SELECT MAX(c2.rowTS) 
       FROM cte c2 
       WHERE c2.someID = c1.someID AND c2.row_num < c1.row_num) AS prevRowTS,
      c1.val,
      (SELECT MAX(c2.val) 
       FROM cte c2 
       WHERE c2.someID = c1.someID AND c2.row_num < c1.row_num) AS prevVal
    FROM cte c1
    

    Here is a live demo

    【讨论】:

    • @foampile 这个接受的答案在 MySQL 中不起作用......你评论了两次
    【解决方案3】:

    这与这个问题非常相似:SQL subtract two rows based on date and another column

    那里有许多解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-01
      • 1970-01-01
      相关资源
      最近更新 更多