2011-09-09 296 views
3

我正在尝试使用Oracle 11g(dev中为11.1,生产中为11.2)进行数值分析,特别是对有三列感兴趣的表格(时间戳,设备ID和值)的线性插值。如何使用Oracle SQL执行线性插值?

值列保存来自设备的数据(ID为deviceid),取自时间戳中给出的时间。例如,这是虚假的数据,但它给人的想法:

 time  | deviceid | value 
----------------|------------|----------- 
01:00:00.000 | 001  | 1.000 
01:00:01.000 | 001  | 1.030 
01:00:02.000 | 001  | 1.063 
01:00:00.050 | 002  | 553.10 
01:00:01.355 | 002  | 552.30 
01:00:02.155 | 002  | 552.43 

从设备001的时间戳不匹配装置002的时间戳,但我需要从两个设备001和002在一个有值行,有一个时间戳,匹配的设备001的时间戳是什么我想落得这样的:

 time  | device 001 | device 002 
----------------|--------------|------------ 
01:00:00.000 | 1.000  | null 
01:00:01.000 | 1.030  | 552.520 
01:00:02.000 | 1.063  | 552.405 

凡装置002的值线性插值基础上,值在收集装置002设备001的每个时间戳任一侧的两个最接近的时间戳。 发生null是因为我没有两次设备002在01:00:00.000任意一侧出现篡改,我不想推断该值。

从我的理解我可以使用percentile_cont来做到这一点,但我不明白我在网上看到的例子。例如,percentile_cont使用的百分位数是从哪里来的?

在此先感谢您的帮助!

+0

感谢您的回答至今。我对Oracle分析函数非常陌生,我的数据并不像我给出的例子那样整洁。例如,device1对device2上的每个值都有几个值。对于一个设备,这些值的增量大约为十分之一秒,而对于另一个设备则为三分之一秒。编辑问题以提供更多详细信息... – GLaDOS

回答

3

我不确定你如何使用PERCENTILE_CONT来做你想要的插值,但借助不同的分析函数,你可以实现你想要的。

首先,我们将创建下列函数,它INTERVAL DAY TO SECOND值转换成秒:

CREATE OR REPLACE FUNCTION intvl_to_seconds(
    p_interval INTERVAL DAY TO SECOND 
) RETURN NUMBER DETERMINISTIC 
AS 
BEGIN 
    RETURN EXTRACT(DAY FROM p_interval) * 24*60*60 
     + EXTRACT(HOUR FROM p_interval) * 60*60 
     + EXTRACT(MINUTE FROM p_interval) * 60 
     + EXTRACT(SECOND FROM p_interval); 
END; 
/

有了这个功能,我们可以使用查询,如下列:

SELECT d1.time, 
     d1.value AS value1, 
     q2.prev_value + intvl_to_seconds(d1.time - q2.prev_time) * (q2.next_value - q2.prev_value)/intvl_to_seconds(q2.next_time - q2.prev_time) AS value2 
    FROM devices d1 
    LEFT OUTER JOIN (SELECT d2.time AS prev_time, 
          d2.value AS prev_value, 
          LEAD(d2.time, 1) OVER (ORDER BY d2.time) AS next_time, 
          LEAD(d2.value, 1) OVER (ORDER BY d2.time) AS next_value 
        FROM devices d2 
        WHERE d2.deviceid = 2) q2 
       ON d1.time BETWEEN q2.prev_time AND q2.next_time 
WHERE d1.deviceid = 1; 

我把你上面的数据,把时间戳的日期组件设置为今天,当我运行上面的查询时,我得到了以下结果:

 
TO_CHAR(D1.TIME)       VALUE1  VALUE2 
------------------------------------- ---------- ---------- 
09-SEP-11 01.00.00.000000      1 
09-SEP-11 01.00.01.000000     1.03 552.517625 
09-SEP-11 01.00.02.000000     1.063 552.404813 

(我加了一个TO_CHAR各地d1.time以减少在SQL * Plus过多空白。)

如果您使用DATE!而非TIMESTAMP S,你不需要的功能:你可以减日期。

+0

编写函数的另一种方法是使用CAST(timestamp_column AS DATE)'。这可以避免大量的SQL到PL/SQL和后台上下文切换。 –

+0

...不,我不能。如果我施放了日期,我会丢失小数秒数据。 – GLaDOS

+0

我一直在努力将时间戳转换为1970年1月1日以来的毫秒数;我实际上没有权限在数据库创建函数。 – GLaDOS

0

我使用@Luke伍德沃德的查询的修改版本:

SELECT d1.time, 
    d1.value AS value1, 
    q2.prev_value + 
    (EXTRACT(SECOND FROM (d1.time - q2.prev_time)) + 
    EXTRACT(MINUTE FROM (d1.time - q2.prev_time)) * 60) 
    * (q2.next_value - q2.prev_value)/ 
     (EXTRACT (SECOND FROM (q2.next_time - q2.prev_time)) + 
     EXTRACT (MINUTE FROM (q2.next_time - q2.prev_time)) * 60) AS value2 
FROM devices d1 
LEFT OUTER JOIN (SELECT d2.time AS prev_time, 
         d2.value AS prev_value, 
         LEAD(d2.time, 1) OVER (ORDER BY d2.time) AS next_time, 
         LEAD(d2.value, 1) OVER (ORDER BY d2.time) AS next_value 
       FROM devices d2 
       WHERE d2.deviceid = 2 
         and time between '20100914 000000' and '20100915 000000' 
       ) q2 
      ON d1.time BETWEEN q2.prev_time AND q2.next_time 
WHERE d1.deviceid = 1; 

但插值总是上来为空,即使是在日期范围装置2的数据。

请注意,我不得不在q2中添加查询的日期范围,这也许是为什么普通连接会丢失外部数据。

如果我使用正常连接,但是在使用正常连接时,我不会得到插值数据的空值,但是我丢失了设备1的端点之外的设备1的数据(q2中的插值设备) 。建议?

+0

请注意,请参阅上面的最终解决方案。再次感谢@Luke Woodward – GLaDOS

+0

下面的最终解决方案......订单被切换了......可能再次切换......所以我使用的最终解决方案是“最终解决方案...” – GLaDOS

0

的日期范围的最终解决方案:

SELECT 
    d1.time, 
    d1.value AS value1, 
    q2.prev_value + 
    (EXTRACT(SECOND FROM (d1.time - q2.prev_time)) + 
    EXTRACT(MINUTE FROM (d1.time - q2.prev_time)) * 60) 
    * (q2.next_value - q2.prev_value)/ 
     (EXTRACT (SECOND FROM (q2.next_time - q2.prev_time)) + 
     EXTRACT (MINUTE FROM (q2.next_time - q2.prev_time)) * 60 
    ) AS value2 
FROM devices d1 
LEFT OUTER JOIN (
    SELECT d2.time AS prev_time, 
      d2.value AS prev_value, 
      LEAD(d2.time, 1) OVER (ORDER BY d2.time) AS next_time, 
      LEAD(d2.value, 1) OVER (ORDER BY d2.time) AS next_value 
    FROM devices d2 
    WHERE d2.deviceid = 2 
    AND time BETWEEN '20100914 000000' AND '20100915 000000' 
) q2 
ON d1.time BETWEEN q2.prev_time AND q2.next_time 
WHERE d1.deviceid = 1 
AND time BETWEEN '20100914 000000' AND '20100915 000000';