2017-08-13 4 views
1

-je utiliser Oracle et ont le tableau suivant:requête SQL pour interpoler Baser d'horodatage sur les dossiers adjacents

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 

je dois écrire pour interpoler la valeur requête SQL (ou Procédure PL/SQL) (VAL) pour tout horodatage (DT), en supposant que la valeur augmente constamment entre deux enregistrements voisins dans la table (c'est-à-dire linear interpolation).

Exemple:

  • Lorsque je sélectionne la valeur pour l'horodatage '02 .05.2017 00:00' , requête devrait me donner 203,4 (enregistrement avec un tel horodatage existe dans le tableau)

  • Lorsque je sélectionne valeur pour timestamp '02 .05.2017 00:30 ', la requête devrait me donner 205.05 (enregistrement avec tel horodatage n'existe pas dans la table, donc nous prenons un' middle 'entre 203.4 et 206.7, parce que l'horodatage voulu est au milieu entre leur horodateurs)

  • Lorsque je sélectionne la valeur pour l'horodatage '02 .05.2017 0h15' , requête devrait me donner 204,225 (une « quatrième partie » entre 203,4 et 206,7)

Quelle est la façon la plus simple de résoudre cette tâche?

Répondre

1

Je pense que celui-ci est encore plus compact, il évite le autojointure:

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; 
0

Je pense que la meilleure façon d'y parvenir serait d'une fonction PL/SQL similaire à ce qui suit:

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; 
/
0

Vous n'avez pas besoin pour cela des curseurs. Vous devez trouver les deux dossiers les plus proches, un au-dessus et un en dessous, puis prendre leur moyenne. Quelque chose comme ceci:

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; 
0

Vous pouvez facilement y parvenir en utilisant les fonctions analytiques. J'espère que cet extrait de Belo aide.

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;