Как выполнить линейную интерполяцию с помощью Oracle SQL?

Я пытаюсь использовать Oracle 11g (11.1 в dev, 11.2 в производстве) для числового анализа, в частности линейной интерполяции в таблице, в которой есть три интересующих столбца: временная метка, deviceid и значение.

Столбцы значений содержат данные с устройства (с идентификатором 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. Нулевое значение возникает, потому что у меня нет двух отметок времени для устройства 002 по обе стороны от 01: 00: 00.000, и я не хочу экстраполировать значение.

Насколько я понимаю, для этого я могу использовать percentile_cont, но я не понимаю примеров, которые видел в Интернете. Например, откуда берется процентиль, используемый percentile_cont?

Заранее спасибо за помощь!


person GLaDOS    schedule 09.09.2011    source источник
comment
Спасибо за ответ. Я новичок в аналитических функциях Oracle, и мои данные не так аккуратны, как в приведенном мной примере. Например, device1 имеет несколько значений для каждого значения на device2. Значения увеличиваются примерно с десятыми долями секунды для одного устройства и третями секунды для другого устройства. Редактируем вопрос, чтобы дать более подробную информацию ...   -  person GLaDOS    schedule 14.09.2011


Ответы (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.)

Если вы используете DATEs вместо TIMESTAMPs, функция вам не нужна: вы можете просто вычесть даты.

person Luke Woodward    schedule 09.09.2011
comment
Альтернативой написанию функции является использование CAST(timestamp_column AS DATE). Это позволяет избежать значительного количества переключений контекста с SQL на PL / SQL и обратно. - person Adam Musch; 12.09.2011
comment
... и нет, я не могу. Я теряю данные о долях секунд, если использую текущее время. - person GLaDOS; 14.09.2011
comment
Я работал над попыткой преобразовать метку времени во что-то вроде количества миллисекунд с 1 января 1970 года; На самом деле у меня нет разрешения на создание функций в базе данных. - person GLaDOS; 15.09.2011
comment
@GLaDOS: Я использовал функцию, чтобы запрос выглядел немного аккуратнее. Если у вас нет разрешения на создание функции, вы можете вместо этого заменить вызовы функции различными вызовами EXTRACT. - person Luke Woodward; 15.09.2011
comment
@GLaDOS: я использовал LEFT OUTER JOIN, чтобы сгенерировать нулевое значение, как в первой строке ваших ожидаемых выходных данных, поскольку я предположил, что это то, что вы хотели. - person Luke Woodward; 15.09.2011
comment
@ Люк Вудворд: Мне нужны нулевые значения, а не экстраполяция данных, когда для устройства 2 нет времени конечной точки. Но когда я запускаю это, все вычисления для внутреннего выбора равны нулю, а не только те, которые выходят за пределы конечных точек. - person GLaDOS; 16.09.2011
comment
Наконец понял, в чем была проблема. Не знал, что существует проблема, пока в коде с помощью запроса не была обнаружена ошибка. Мое окончательное решение см. В ответе ниже. Диапазон дат не был включен в исходный вопрос; и в этом и заключалась проблема. - person GLaDOS; 19.10.2011

Я использую модифицированную версию запроса @Luke Woodward:

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 за пределами конечных точек для устройства 2 (интерполированное устройство в q2). Предложения?

person GLaDOS    schedule 16.09.2011
comment
Обратите внимание, см. Окончательное решение выше. Еще раз спасибо @Luke Woodward - person GLaDOS; 19.10.2011
comment
Окончательное решение ниже ... порядок был изменен ... может снова переключиться ... итак, последнее решение, которое я использовал, - это то, которое гласит: Окончательное решение ... - person GLaDOS; 20.10.2011

Окончательное решение с диапазоном дат:

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';
person GLaDOS    schedule 19.10.2011