0

假设您有一个简单的表,它表示由 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 中做到这一点。唯一想到的是笛卡尔积,不用说,这不是很有效。

4

3 回答 3

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 ONLY,hack 但它的性能非常好。

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

于 2012-10-02T00:47:35.567 回答
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

这是一个现场演示

于 2012-10-02T00:58:41.777 回答
0

这与这个问题非常相似:SQL 根据日期和另一列减去两行

那里有很多解决方案。

于 2012-10-02T01:01:58.747 回答