Расчет кумулятивной суммы в PostgreSQL

87

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

ea_month    id       amount    ea_year    circle_id
April       92570    1000      2014        1
April       92571    3000      2014        2
April       92572    2000      2014        3
March       92573    3000      2014        1
March       92574    2500      2014        2
March       92575    3750      2014        3
February    92576    2000      2014        1
February    92577    2500      2014        2
February    92578    1450      2014        3          

Я хочу, чтобы моя целевая таблица выглядела примерно так:

ea_month    id       amount    ea_year    circle_id    cum_amt
February    92576    1000      2014        1           1000 
March       92573    3000      2014        1           4000
April       92570    2000      2014        1           6000
February    92577    3000      2014        2           3000
March       92574    2500      2014        2           5500
April       92571    3750      2014        2           9250
February    92578    2000      2014        3           2000
March       92575    2500      2014        3           4500
April       92572    1450      2014        3           5950

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

Может ли кто-нибудь подсказать, как достичь этого результата?

Юсуф Султан
источник
1
Как получить cum_amount равное 1000 в целевой таблице? Для circle_id сумма кажется 2000.

Ответы:

132

По сути, вам нужна оконная функция . В настоящее время это стандартная функция. Помимо настоящих оконных функций, вы можете использовать любую агрегатную функцию в качестве оконной функции в Postgres, добавив OVERпредложение.

Особая трудность заключается в том, чтобы получить правильные разделы и порядок сортировки:

SELECT ea_month, id, amount, ea_year, circle_id
     , sum(amount) OVER (PARTITION BY circle_id
                         ORDER BY ea_year, ea_month) AS cum_amt
FROM   tbl
ORDER  BY circle_id, month;

И нет GROUP BY .

Сумма для каждой строки рассчитывается от первой строки в разделе до текущей строки - или, если быть точным, цитируя руководство :

Параметр кадрирования по умолчанию RANGE UNBOUNDED PRECEDINGтакой же, как у RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. С ORDER BY, это устанавливает в кадре все строки от начала раздела до последнего ORDER BYоднорангового узла текущей строки .

... которая представляет собой накопительную или текущую сумму, которую вы ищете. Смелый акцент мой.

Строки с одинаковыми значениями (circle_id, ea_year, ea_month)являются «одноранговыми» в этом запросе. Все они показывают одну и ту же текущую сумму со всеми равноправными участниками, добавленными к сумме. Но я предполагаю, что ваш стол UNIQUEвключен(circle_id, ea_year, ea_month) , тогда порядок сортировки детерминирован и ни одна строка не имеет сверстников.

Теперь ORDER BY ... ea_month не будет работать со строками для названий месяцев . Postgres будет выполнять сортировку в алфавитном порядке в соответствии с настройками локали.

Если у вас есть фактические dateзначения, хранящиеся в вашей таблице, вы можете правильно отсортировать их. Если нет, я предлагаю заменить ea_yearи ea_monthодним столбцом monтипа dateв вашей таблице.

  • Преобразуйте то, что у вас есть to_date():

      to_date(ea_year || ea_month , 'YYYYMonth') AS mon
    
  • Для отображения можно получить оригинальные строки с помощью to_char():

      to_char(mon, 'Month') AS ea_month
      to_char(mon, 'YYYY') AS ea_year
    

Несмотря на неудачный дизайн, это будет работать:

SELECT ea_month, id, amount, ea_year, circle_id
     , sum(amount) OVER (PARTITION BY circle_id ORDER BY mon) AS cum_amt
FROM   (SELECT *, to_date(ea_year || ea_month, 'YYYYMonth') AS mon FROM tbl)
ORDER  BY circle_id, mon;
Эрвин Брандштеттер
источник
Спасибо за решение .. Можете ли вы мне помочь еще с одним делом. Я хочу реализовать то же самое с помощью курсора с логикой, что каждый круг будет иметь только одну запись за месяц в году. И функция должна запускаться один раз в месяц. Как я могу этого добиться?
Юсуф Султан
4
@YousufSultan: В большинстве случаев есть лучшее решение, чем курсор. Это определенно материал для нового вопроса. Пожалуйста, начните новый вопрос.
Эрвин Брандштеттер
Я считаю этот ответ неполным без хотя бы заметки о том, что здесь происходит «кадрирование», которое по умолчанию range unbounded precedingсовпадает с range between unbounded preceding and current row. Вот почему sum()при использовании в качестве оконной функции выдает промежуточную сумму - в то время как другие оконные функции не имеют этого фрейма по умолчанию.
Colin 't Hart
1
@ Colin'tHart: я добавил еще кое-что выше, чтобы уточнить.
Эрвин Брандштеттер
Вот ссылка на аналогичный вопрос с более простым запросом ( PARTITIONне всегда требуется для создания
Джейсон Аксельсон