假设您有一个简单的表,表示由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中完成它。唯一想到的是笛卡尔积,不用说,效率不高。
答案 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
答案 2 :(得分:0)
这与此问题非常相似:SQL subtract two rows based on date and another column
那里有很多解决方案。