Распределение заданной суммы по списку отрицательных значений снизу вверх в SQL - PullRequest
2 голосов
/ 19 февраля 2020

У меня есть таблица отрицательных значений для данной даты, например:

------------------
| date | deficit |    budget: 100
|----------------|
|01.01 |   -5    |
|01.02 |   -7    |
|        .       |
|        .       |
|20.12 |   -8    |
|30.12 |   -6    |
------------------

У меня также есть фиксированное число, давайте просто назовем его бюджет и скажем его 100.

Сейчас Я хочу использовать свой бюджет для отмены отрицательных значений, например, я уменьшаю свой бюджет со 100 до 95 и пишу 0 в таблице в строке с датой 01.01 вместо 5.

Вот так:

------------------
| date | deficit |    budget: 95
|----------------|
|01.01 |    0    |
|01.02 |   -7    |
|        .       |
|        .       |
|20.12 |   -8    |
|30.12 |   -6    |
------------------

Я хочу сделать это последовательно, начиная с самой низкой даты и заканчивая самой высокой датой, и если у меня заканчивается бюджет, просто оставьте отрицательные значения в конце, который я больше не смогу заполнить.

В конце концов, это выглядело бы так, предполагая, что мы достигли записи 20.12 с бюджетом 6, таким образом, не имея возможности полностью отменить отрицательное значение и исчерпать бюджет

------------------
| date | deficit |    budget: 0
|----------------|
|01.01 |    0    |
|01.02 |    0    |
|        .       |
|        .       |
|20.12 |   -2    |
|30.12 |   -6    |
------------------

Как бы я это сделал? Я знаю, как бы я сделал это на императивном языке, но как вы гарантируете, в каком порядке он вычитает и меняет статую суммы в основном декларативном языке? Я использую oracle.

РЕДАКТИРОВАТЬ:

Вот что я хочу сделать, учитывая в императивном псевдокоде:

*Asuming deficits are already ordered by date
foreach deficit of deficits
  if (budget - deficit) >= 0
    set deficit = 0 
    set budget = budget - deficit
  else 
    set deficit = budget - deficit   
    set budget = 0
  end if 

Так что в основном мне это нужно, но в SQL код.

1 Ответ

4 голосов
/ 19 февраля 2020

Есть несколько способов приблизиться к этому, но вы можете использовать оконные аналитические функции c, чтобы получить промежуточную сумму дефицита и того, сколько бюджета было потрачено. Чтобы получить конечный номер, который вы хотите, вы можете использовать что-то вроде:

greatest(deficit,
  sum(deficit) over (order by date_col)
    + least(100, - sum(deficit) over (order by date_col)))

или, возможно, немного более четко:

case
  when sum(deficit)
    over (order by date_col rows between unbounded preceding and 1 preceding) <= -100
  then
    deficit
  else
    sum(deficit) over (order by date_col)
      + least(100, - sum(deficit) over (order by date_col))
end

sum(deficit) over (order by date_col) дает вам промежуточный итог в дате порядок, дефицитов. Для первой даты, которая рассчитывается как -5; для второго как (-5 + -7) = -12; et c.

least(100, - sum(deficit) over (order by date_col))) дает вам бюджет, использованный до текущей даты включительно, но ограниченный 100. Для первой даты least(100, - (-5)), которая равна 5; для второго это least(100, - (-5 + -7)), что составляет 12; к 20-му это least(100, - (-102)), так что вы получаете наименьшее из 100 и 102 - таким образом, ограниченное до 100. (Вы можете сделать это как greatest(-100, sum(deficit) over (order by date_col))), если хотите, а затем вместо этого вычесть это на последнем шаге.)

Когда это добавляется к промежуточному итогу дефицита, первые несколько строк удаляются, и вы получаете ноль. Для 20-го сумма дефицита равна 102, а ограниченное значение равно 100, так что в итоге вы получите -2. Для 30-го это даст вам 100 - 108, то есть -8 вместо -6, который вы на самом деле хотите. Вот тут и приходит greatest(deficit, ...). До 20 числа это не меняет ответа; greatest(-8, 100 - 102) по-прежнему -2, потому что -2> -8. Для 30 числа у вас greatest(-6, 100 - 108) равно -6, потому что -6> -8.

Демонстрация с некоторыми примерами данных и демонстрацией некоторых из рабочих:

-- sample data
with your_table (date_col, deficit) as (
            select date '2020-01-01', -5 from dual
  union all select date '2020-01-02', -7 from dual
  union all select date '2020-01-03', -50 from dual
  union all select date '2020-01-04', -32 from dual
  union all select date '2020-01-20', -8 from dual
  union all select date '2020-01-30', -6 from dual
)
-- actual query
select date_col, deficit,
  sum(deficit) over (order by date_col) as sum_deficit,
  least(100, - sum(deficit) over (order by date_col)) as budget_used,
  greatest(0, 100 + sum(deficit) over (order by date_col)) as budget_left_1,
  100 - least(100, - sum(deficit) over (order by date_col)) as budget_left_2,
  greatest(deficit,
    sum(deficit) over (order by date_col)
      + least(100, - sum(deficit) over (order by date_col))) as new_deficit_1,
  case
    when sum(deficit)
      over (order by date_col rows between unbounded preceding and 1 preceding) <= -100
    then
      deficit
    else
      sum(deficit) over (order by date_col)
        + least(100, - sum(deficit) over (order by date_col))
    end as new_deficit_2
from your_table
order by date_col;

, которая производит:

DATE_COL      DEFICIT SUM_DEFICIT BUDGET_USED BUDGET_LEFT_1 BUDGET_LEFT_2 NEW_DEFICIT_1 NEW_DEFICIT_2
---------- ---------- ----------- ----------- ------------- ------------- ------------- -------------
2020-01-01         -5          -5           5            95            95             0             0
2020-01-02         -7         -12          12            88            88             0             0
2020-01-03        -50         -62          62            38            38             0             0
2020-01-04        -32         -94          94             6             6             0             0
2020-01-20         -8        -102         100             0             0            -2            -2
2020-01-30         -6        -108         100             0             0            -6            -6
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...