Oracle SQL аналитический запрос - PullRequest
0 голосов
/ 05 февраля 2019
SQL> select LAST_UPDATED_DATE, ODOMETER from demo;

LAST_UPDA   ODOMETER
--------- ----------
05-OCT-18   47174.77
08-OCT-18
12-OCT-18   50246.37
15-OCT-18
19-OCT-18   53743.11
21-OCT-18
22-OCT-18
25-OCT-18   58789.22

8 rows selected.

Мне нужно определить значение одометра, где его ноль, и это должно быть сделано с помощью SQL.То, как я думал сделать это -

  1. получить предыдущее и следующее ненулевое значение одометра и разницу между днями, используя которые я могу рассчитать среднее расстояние, пройденное за день.

Например, в этом случае (50246,37 - 47174,77) / (12-ОКТ-18-05-ОКТ-18) = ~ 439

Теперь со средним значением за день вычислите разницу между днями и умножьте ее на среднее значение.

Например, (08-OCT-18 - 05-OCT-18) = 3 дня и в течение 3 дней 439 * 3 = 1317. Таким образом, значение для 08-Oct-18 может быть 47174,77 + 1317 = 48491,77

Теперь мне нужна помощь для написания кода SQL для этого.

Любая помощь будет принята с благодарностью.

Ответы [ 4 ]

0 голосов
/ 06 февраля 2019

Вот как бы я это сделал.Это может помочь в других подобных проблемах (линейная интерполяция), где нельзя предполагать, что «значение» увеличивается со временем.Для одометра это предположение имеет смысл, и решение Гордона Линоффа проще;Я предлагаю это решение для других приложений, где «количество» может уменьшаться, а также увеличиваться со временем.

with
  sample_data(last_updated_date, odometer) as (
    select to_date('05-OCT-18', 'dd-MON-rr'), 47174.77 from dual union all
    select to_date('08-OCT-18', 'dd-MON-rr'), null     from dual union all
    select to_date('12-OCT-18', 'dd-MON-rr'), 50246.37 from dual union all
    select to_date('15-OCT-18', 'dd-MON-rr'), null     from dual union all
    select to_date('19-OCT-18', 'dd-MON-rr'), 53743.11 from dual union all
    select to_date('21-OCT-18', 'dd-MON-rr'), null     from dual union all
    select to_date('22-OCT-18', 'dd-MON-rr'), null     from dual union all
    select to_date('25-OCT-18', 'dd-MON-rr'), 58789.22 from dual
  )
, prep(last_updated_date, odometer, prev_date, next_date, prev_odo, next_odo) as (
    select last_updated_date, odometer,
           case when odometer is null
                then max(nvl2(odometer, last_updated_date, null))
                     over (order by last_updated_date) end,
           case when odometer is null
                then min(nvl2(odometer, last_updated_date, null))
                     over (order by last_updated_date 
                     rows between 1 following and unbounded following) end,
           last_value(odometer ignore nulls) over (order by last_updated_date),
           first_value(odometer ignore nulls) over (order by last_updated_date 
                                rows between 1 following and unbounded following)
    from   sample_data
  )
select   last_updated_date,
         nvl( odometer,
              round(prev_odo + (next_odo - prev_odo) * 
                   (last_updated_date - prev_date) / (next_date - prev_date), 2)
            ) as odometer
from     prep
order by last_updated_date
;

ВЫХОД

LAST_UPDATED_DATE   ODOMETER
----------------- ----------
05-OCT-18           47174.77
08-OCT-18           48491.17
12-OCT-18           50246.37
15-OCT-18           51744.97
19-OCT-18           53743.11
21-OCT-18           55425.15
22-OCT-18           56266.17
25-OCT-18           58789.22
0 голосов
/ 06 февраля 2019

Вы можете получить предыдущий и следующий ряд, используя совокупные значения max и min (это предполагает, что одометр движется только в одном направлении).Остальное - просто арифметика для арифметической интерполяции:

select d.last_updated_date, d.odometer,
       (case when d.odometer is not null then d.odometer
             else prev_o + (next_o - prev_o) * (last_updated_date - prev_lud) / (next_lud - prev_lud)
        end)
from (select d.*,
             max(case when odometer is not null then last_updated_date end) over (order by last_updated_date) as prev_lud,
             max(odometer) over (order by last_updated_date) as prev_o,
             min(case when odometer is not null then last_updated_date end) over (order by last_updated_date desc) as next_lud,
             min(odometer) over (order by last_updated_date desc) as next_o
      from demo d
     ) d;
0 голосов
/ 06 февраля 2019

Вот запрос, который даст вам пропущенные значения.Он использует два регулярных соединения, чтобы найти предыдущую и следующую запись, где доступно значение одометра.

SELECT
    d.last_update_date,
    d0.odometer 
        + (d1.odometer - d0.odometer) * ( d.last_update_date - d0.last_update_date ) 
        / ( d1.last_update_date - d0.last_update_date ) odometer
FROM
    demo d
    INNER JOIN demo d0 ON d0.last_update_date = (
        SELECT MAX(last_update_date) 
        FROM demo 
        WHERE odometer IS NOT NULL AND last_update_date < d.last_update_date
    )
    INNER JOIN demo d1 ON d1.last_update_date = (
        SELECT MIN(last_update_date) 
        FROM demo 
        WHERE odometer IS NOT NULL AND last_update_date > d.last_update_date
    )
WHERE d.odometer IS NULL;

Это Демонстрация DB Fiddle возвращает:

 LAST_UPDATE_DATE | ODOMETER
 :--------------- | ----------:
 08-OCT-18        | 48491.17
 15-OCT-18        | 51744.97
 21-OCT-18        | 55425.15
 22-OCT-18        | 56266.17

Значение 8 октября, похоже, именно то, что вы ожидаете.


Если вы хотите обновить таблицу, чтобы добавить пропущенные значения, вы можете использовать синтаксис Oracle MERGE, как показано на рисунке this db fiddle :

MERGE INTO demo target 
USING (
    SELECT
        d.last_update_date,
        d0.odometer 
            + (d1.odometer - d0.odometer) * ( d.last_update_date - d0.last_update_date ) 
            / ( d1.last_update_date - d0.last_update_date ) odometer
    FROM
        demo d
        INNER JOIN demo d0 ON d0.last_update_date = (
            SELECT MAX(last_update_date) 
            FROM demo 
            WHERE odometer IS NOT NULL AND last_update_date < d.last_update_date
        )
        INNER JOIN demo d1 ON d1.last_update_date = (
            SELECT MIN(last_update_date) 
            FROM demo 
            WHERE odometer IS NOT NULL AND last_update_date > d.last_update_date
        )
    WHERE d.odometer IS NULL
) src ON (src.last_update_date = target.last_update_date)
WHEN MATCHED THEN UPDATE SET target.odometer = src.odometer;
0 голосов
/ 06 февраля 2019

Вы можете использовать OUTER APPLY (начиная с Oracle 12c), чтобы получить предыдущую и следующую строку с ODOMETER.Затем используйте формулу для линейной интерполяции.

select
  demo.last_updated_date,
  coalesce
  (
    demo.odometer,
    prev.odometer + ( (next.odometer - prev.odometer) *
                      (demo.last_updated_date - prev.last_updated_date) /
                      (next.last_updated_date - prev.last_updated_date) )
  ) as odometer
from demo
outer apply
(
  select *
  from demo d
  where d.last_updated_date < demo.last_updated_date
  and d.odometer is not null
  order by d.last_updated_date desc
  fetch first row only
) prev
outer apply
(
  select *
  from demo d
  where d.last_updated_date > demo.last_updated_date
  and d.odometer is not null
  order by d.last_updated_date desc
  fetch first row only
) next
order by demo.last_updated_date;

Результат (округленный):

LAST_UPDATED_DATE | ODOMETER
------------------+---------
05-OCT-18         | 47174.77
08-OCT-18         | 48916.94
12-OCT-18         | 50246.37
15-OCT-18         | 52217.80
19-OCT-18         | 53743.11
21-OCT-18         | 55425.15
22-OCT-18         | 56266.17
25-OCT-18         | 58789.22

Демо: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=54cc9d4b7dd5793e1c0025627fd929de

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...