如何最好地检索每条记录的最新记录

时间:2012-10-02 00:39:51

标签: sql

假设您有一个简单的表,表示由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和最新的某个ID的前一行?

E.g。如果数据是

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

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

但我想知道是否有一种快速的方法可以在SQL中完成它。唯一想到的是笛卡尔积,不用说,效率不高。

3 个答案:

答案 0 :(得分:2)

对于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 ,黑客但它的表现非常好。

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列移到其他列之前。

答案 1 :(得分:1)

既然你说你使用的是什么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

答案 2 :(得分:0)

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

那里有很多解决方案。