SQL 查询基于相邻记录插入时间戳
SQL query to interpolate timestamp basing on adjacent records
我使用 Oracle 并具有以下 table:
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),假设两个相邻记录之间的值不断增加在 table(即 linear interpolation)。
示例:
当我 select 时间戳 '02.05.2017 00:00' 的值时,查询应该给我 203.4(table 中存在具有此类时间戳的记录)
当我 select 时间戳 '02.05.2017 00:30' 的值时,查询应该给我 205.05([=45= 中不存在具有此类时间戳的记录],所以我们在 203.4 和 206.7 之间取一个 'middle',因为想要的时间戳在它们的时间戳中间)
当我 select 时间戳 '02.05.2017 00:15' 的值时,查询应该给我 204.225(203.4 和 206.7 之间的 'fourth part')
解决此类任务的最简单方法是什么?
我认为完成此操作的最简单方法是使用类似于以下内容的 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;
您可以使用分析函数轻松实现这一点。希望这个 belo 片段对您有所帮助。
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;
我觉得这个更紧凑,它避免了自连接:
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;
我使用 Oracle 并具有以下 table:
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),假设两个相邻记录之间的值不断增加在 table(即 linear interpolation)。
示例:
当我 select 时间戳 '02.05.2017 00:00' 的值时,查询应该给我 203.4(table 中存在具有此类时间戳的记录)
当我 select 时间戳 '02.05.2017 00:30' 的值时,查询应该给我 205.05([=45= 中不存在具有此类时间戳的记录],所以我们在 203.4 和 206.7 之间取一个 'middle',因为想要的时间戳在它们的时间戳中间)
当我 select 时间戳 '02.05.2017 00:15' 的值时,查询应该给我 204.225(203.4 和 206.7 之间的 'fourth part')
解决此类任务的最简单方法是什么?
我认为完成此操作的最简单方法是使用类似于以下内容的 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;
您可以使用分析函数轻松实现这一点。希望这个 belo 片段对您有所帮助。
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;
我觉得这个更紧凑,它避免了自连接:
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;