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

基于相邻记录插入时间戳的SQL查询,sql,oracle,plsql,linear-interpolation,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.

我使用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,假设值在表ie中的两个相邻记录之间不断增加

例如:

当我为时间戳'02.05.2017 00:00'选择值时,查询应给出203.4条记录,该时间戳存在于表中

当我为时间戳'02.05.201700 30'选择值时,查询应该给我205.05个记录,这样的时间戳不存在于表中,所以我们在203.4到206.7之间取一个“中间”,因为想要的时间戳在它们的时间戳

之间。 当我为时间戳'02.05.2017 00:15'选择值时,查询应为我提供203.4和206.7之间的204.225“第四部分”


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

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

我认为这个更紧凑,它避免了自连接:

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;