Oracle SQL - Как рассчитать нулевые значения на основе предыдущей ненулевой записи - PullRequest
0 голосов
/ 22 марта 2020

У меня есть несколько столбцов в таблице, подобных этой:

id | date      | change | end_value 
 1 | 03-JAN-20 | -9     |       
 2 | 04-JAN-20 | 12     |           
 3 | 05-JAN-20 | -43    | 523       
 4 | 06-JAN-20 | 0      |           
 5 | 07-JAN-20 | 5      |           
 6 | 08-JAN-20 | 10     |           
 7 | 09-JAN-20 | 3      | 505       
 8 | 10-JAN-20 | 4      |           
 9 | 11-JAN-20 | -3     |           
 10| 12-JAN-20 | 1      | 503       
 11| 13-JAN-20 | -6     |           

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

Результат будет примерно таким:

id | date      | change | end_value | result
 1 | 03-JAN-20 | -9     |           | 492 (=523 - 43 + 12)
 2 | 04-JAN-20 | 12     |           | 480 (=523 - 43)
 3 | 05-JAN-20 | -43    | 523       | 523 
 4 | 06-JAN-20 | 0      |           | 523 (=523 - 0)
 5 | 07-JAN-20 | 5      |           | 518 (=523 - 0 - 5)
 6 | 08-JAN-20 | 10     |           | 508 (=523 - 0 - 5 - 10)
 7 | 09-JAN-20 | 3      | 505       | 505 
 8 | 10-JAN-20 | 4      |           | 501 (=505 - 4)
 9 | 11-JAN-20 | -3     |           | 504 (=505 - 4 + 3)
 10| 12-JAN-20 | 1      | 503       | 503
 11| 13-JAN-20 | -6     |           | 509 (=503 + 6)

Я подумал, что может понадобиться использовать функцию last_value игнорировать пустую ноль, но может Не разбираюсь в части текущих минут.

Спасибо за помощь!

Ответы [ 3 ]

1 голос
/ 22 марта 2020

Решение ниже зависит от первого ненулевого значения для end_value, отсортированного по дате - т.е. оно игнорирует остальные значения.

with t (sid, dt,change,end_value) as ( 
 select 1 , to_date('03-JAN-20', 'dd-MON-rr') , -9     , null    from dual union all   
 select 2 , to_date('04-JAN-20', 'dd-MON-rr') , 12     , null    from dual union all       
 select 3 , to_date('05-JAN-20', 'dd-MON-rr') , -43    , 523     from dual union all       
 select 4 , to_date('06-JAN-20', 'dd-MON-rr') , 0      , null    from dual union all       
 select 5 , to_date('07-JAN-20', 'dd-MON-rr') , 5      , null    from dual union all       
 select 6 , to_date('08-JAN-20', 'dd-MON-rr') , 10     , null    from dual union all       
 select 7 , to_date('09-JAN-20', 'dd-MON-rr') , 3      , 505     from dual union all       
 select 8 , to_date('10-JAN-20', 'dd-MON-rr') , 4      , null    from dual union all       
 select 9 , to_date('11-JAN-20', 'dd-MON-rr') , -3     , null    from dual union all       
 select 10, to_date('12-JAN-20', 'dd-MON-rr') , 1      , 503     from dual union all       
 select 11, to_date('13-JAN-20', 'dd-MON-rr') , -6     , null    from dual 
 )
 select sid, dt, change, end_value, nvl(yy,yyy) rslt from (
   select a.* 
   , sum(case when dt = xx then end_value when dt > xx then -change end) over ( order by dt) yy
   , sum(case when dt = xx then end_value when dt < xx then ld end) over ( order by dt desc) yyy
   from (
     select t.*
     , min(dt) keep (dense_rank first order by nvl2(end_value,0,1)) over () xx
     , lead(change) over (order by dt) ld
     from t
   ) a
 ) b
 order by dt
1 голос
/ 22 марта 2020

Это тип проблемы пробелов и островков. Решение на самом деле довольно простое:

  • Определите острова путем подсчета числа ненулевых end_value с в или после каждой строки.
  • В каждой группе выполните накопительное сумму изменений и добавьте к end_value для этой группы.

Есть небольшая хитрость, потому что вы не хотите изменения для текущей строки. Это легко сделать, вычтя ее из совокупной суммы:

select t.*,
       (max(end_value) over (partition by grp order by dt desc) +
        sum(change) over (partition by grp order by dt desc) -
        change
      ) as new_end_value
from (select t.*, count(end_value) over (order by dt desc) as grp
      from t
     ) t
order by dt;

Здесь - это скрипта db <>.

Если вы хотите обновить значение, используйте merge:

merge into t using
      (select t.*,
              (max(end_value) over (partition by grp order by dt desc) +
               sum(change) over (partition by grp order by dt desc) -
               change
             ) as new_end_value
       from (select t.*, count(end_value) over (order by dt desc) as grp
             from t
            ) t
      ) src
      on (src.sid = t.sid)
when matched then update
    set end_value = src.new_end_value;
0 голосов
/ 22 марта 2020

Вы можете использовать курсор PL / SQL и сохранять текущую сумму CHANGE в переменной. Примерно так:

DECLARE
    CURSOR cur IS
    SELECT
        id,
        change,
        end_value
    FROM
        test
    ORDER BY
        "DATE";

    TYPE t_record IS RECORD (
        id          NUMBER,
        change      NUMBER,
        end_value   NUMBER
    );
    v_record     t_record;
    v_baseline   NUMBER := 0;
    v_change     NUMBER := 0;
BEGIN
    FOR row IN cur LOOP
        IF row.end_value IS NOT NULL THEN
            v_baseline := row.end_value;
            v_change := 0;
        ELSE
            v_change := v_change + row.change;
            UPDATE test
            SET
                end_value = v_baseline - v_change
            WHERE
                id = row.id;
            -- COMMIT;
        END IF;
    END LOOP;
END;
/

Обратите внимание, что вы написали, что столбец должен быть заполнен "на основе предыдущего ненулевого значения", но в вашем примере первые две строки заполнены на основе next ненулевое значение (если я правильно понял), поэтому этот код для них не работает. В любом случае, вы можете настроить его в соответствии с вашими потребностями.

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