SQL 查询,用于根据相邻记录插值时间戳



我使用Oracle,有下表:

create table test as
select to_date('02.05.2017 00:00', 'DD.MM.YYYY HH24:MI') as DT, 203.4 as VAL from dual union all
select to_date('02.05.2017 01:00', 'DD.MM.YYYY HH24:MI') as DT, 206.7 as VAL from dual union all
select to_date('02.05.2017 02:00', 'DD.MM.YYYY HH24:MI') as DT, 208.9 as VAL from dual union all
select to_date('02.05.2017 03:00', 'DD.MM.YYYY HH24:MI') as DT, 211.8 as VAL from dual union all
select to_date('02.05.2017 04:45', 'DD.MM.YYYY HH24:MI') as DT, 212.3 as VAL from dual union all
select to_date('02.05.2017 06:15', 'DD.MM.YYYY HH24:MI') as DT, 214.5 as VAL from dual union all
select to_date('02.05.2017 08:12', 'DD.MM.YYYY HH24:MI') as DT, 215 as VAL from dual
;

DT                   VAL
----------------------------
02.05.2017 00:00     203.4
02.05.2017 01:00     206.7
02.05.2017 02:00     208.9
02.05.2017 03:00     211.8
02.05.2017 04:45     212.3
02.05.2017 06:15     214.5
02.05.2017 08:12     215 

我需要编写SQL查询(或PL/SQL过程(,以便为任何时间戳(DT(插入值(VAL(,假设该值在表中的两个相邻记录之间不断增加(即线性插值(。

例:

  • 当我为时间戳'02.05.2017 00:00'选择值时,查询应该给我203.4(表中存在具有这种时间戳的记录(

  • 当我为时间戳'02.05.2017 00:30'选择值时,查询应该给我205.05(表中不存在具有这种时间戳的记录,所以我们在203.4和206.7之间取一个"中间",因为想要的时间戳在它们的时间戳之间(

  • 当我为时间戳"02.05.2017 00:15"选择值时,查询应该给我 204.225(203.4 和 206.7 之间的"第四部分"(

解决此类任务的最简单方法是什么?

我认为这个更紧凑,它避免了自加入:

WITH t AS 
(SELECT DT, VAL, 
LEAD(DT, 1, DT) OVER (ORDER BY DT) AS FOLLOWING_DT,
LEAD(VAL, 1, VAL) OVER (ORDER BY VAL) AS FOLLOWING_VAL
FROM TEST)
SELECT VAL + (FOLLOWING_VAL - VAL) * ( (:timestamp - DT) / (FOLLOWING_DT - DT) )
FROM t
WHERE :timestamp BETWEEN DT AND FOLLOWING_DT;

我认为最简单的方法是使用类似于以下内容的PL/SQL函数:

create or replace function get_val(dt in date) return number
is
cursor exact_cursor(dt in date) is
select t.val from t where t.dt = exact_cursor.dt;
cursor earlier_cursor(dt in date) is
select t.dt, t.val from t where t.dt < earlier_cursor.dt
order by t.dt desc;
cursor later_cursor(dt in date) is
select t.dt, t.val from t where t.dt > later_cursor.dt
order by t.dt asc;
result      number;
factor      number;
earlier_rec earlier_cursor%rowtype;
later_rec   later_cursor%rowtype;
begin
open exact_cursor(dt);
fetch exact_cursor into result;
close exact_cursor;
if result is not null then
return result;
end if;
-- No exact match. Perform linear interpolation between values
-- from earlier and later records.
open earlier_cursor(dt);
fetch earlier_cursor into earlier_rec;
close earlier_cursor;
open later_cursor(dt);
fetch later_cursor into later_rec;
close later_cursor;
-- Return NULL unless earlier and later records found
if earlier_rec.dt is null or later_rec.dt is null then
return null;
end if;
factor := (dt - earlier_rec.dt) / (later_rec.dt - earlier_rec.dt);
result := earlier_rec.val + factor * (later_rec.val - earlier_rec.val);
return result;
end;
/

您不需要游标。 您需要找到两个最接近的记录,一个在上面,一个在下面,然后取它们的平均值。 像这样:

select :timestamp,
(case when lower.val = upper.val then val
else lower.val + (upper.val - lower.val) * ( (:timestamp - lower.dt) / (upper.dt - lower.dt) )
end) as imputed_val
from (select *
from (select dt, val
from t
where dt <= :timestamp
order by dt desc
)
where rownum = 1
) lower cross join
(select *
from (select dt, val
from t
where dt >= :timestamp
order by dt asc
)
where rownum = 1
) upper;

您可以使用分析函数轻松实现此目的。希望这个贝洛片段有所帮助。

SELECT *
FROM
(SELECT c.*,
((lead(c.dt) over(order by 1) -c.dt)*24*60) lead_val,
(lead(c.val) over(order by 1 )-c.val) lead_val_diff,
lead(c.val) over(order by 1 ) - (((lead(c.dt) over(order by 1)- to_date('&enter_date','DD.MM.YYYY HH24:MI'))*24*60)/((lead(c.dt) over(order by 1) -c.dt)*24*60))*(lead(c.val) over(order by 1 )-c.val)polated_val
FROM
(SELECT so_test.*,
row_number() over(order by dt) rn1
FROM SO_TEST
)c
WHERE ((c.rn1) IN
(SELECT MAX(RN)
FROM
(SELECT ROW_NUMBER() OVER(ORDER BY A.DT ) RN,
A.*
FROM SO_TEST A
WHERE A.DT <= '&enter_date'
)B
))
OR (c.rn1       IN
(SELECT MAX(RN)+1
FROM
(SELECT ROW_NUMBER() OVER(ORDER BY A.DT ) RN,
A.*
FROM SO_TEST A
WHERE A.DT <= '&enter_date'
)B
))
)d
WHERE d.polated_val IS NOT NULL;

最新更新