SQL查询以基于相邻记录插入时间戳

时间:2017-08-13 20:14:34

标签: sql oracle plsql linear-interpolation

我使用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),假设值在表中的两个邻居记录之间不断增加(即{{3 }})。

示例:

  • 当我选择时间戳' 02.05.2017 00:00'的值时,查询应该给我203.4(表中存在此类时间戳的记录)

  • 当我选择时间戳' 02.05.2017 00:30'时,查询应该给我205.05(表中没有这样的时间戳的记录,所以我们采取& #39;中间'在203.4和206.7之间,因为想要的时间戳在它们的时间戳之间的中间位置)

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

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

4 个答案:

答案 0 :(得分:2)

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

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;

答案 1 :(得分:0)

我认为实现这一目标的最简单方法是使用类似于以下内容的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;
/

答案 2 :(得分:0)

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

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;

答案 3 :(得分:0)

您可以使用分析功能轻松实现此目的。希望这个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;