Cálculo da soma cumulativa no PostgreSQL

87

Desejo encontrar a quantidade cumulativa ou corrente de campo e inseri-la da preparação para a tabela. Minha estrutura de teste é mais ou menos assim:

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          

Quero que minha tabela de destino se pareça com isto:

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

Estou realmente muito confuso sobre como proceder para alcançar este resultado. Eu quero alcançar este resultado usando PostgreSQL.

Alguém pode sugerir como proceder para atingir esse conjunto de resultados?

Yousuf Sultan
fonte
1
Como você obtém cum_amount de 1000 em sua tabela de destino? Para circle_id, o valor parece ser

Respostas:

132

Basicamente, você precisa de uma função de janela . Esse é um recurso padrão hoje em dia. Além das funções de janela genuínas, você pode usar qualquer função de agregação como função de janela no Postgres anexando uma OVERcláusula.

A dificuldade especial aqui é obter as partições e a ordem de classificação correta:

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;

E não GROUP BY .

A soma de cada linha é calculada a partir da primeira linha na partição até a linha atual - ou citando o manual para ser mais preciso:

A opção de enquadramento padrão é RANGE UNBOUNDED PRECEDING, que é igual a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Com ORDER BY, isso define o quadro como todas as linhas da partição inicializada até o último ORDER BYpar da linha atual .

... que é a soma cumulativa ou contínua que você busca. Ênfase em negrito minha.

Linhas com o mesmo (circle_id, ea_year, ea_month)são "pares" nesta consulta. Todos eles mostram a mesma soma contínua com todos os pares adicionados à soma. Mas suponho que sua tabela esteja UNIQUEativada (circle_id, ea_year, ea_month), então a ordem de classificação é determinística e nenhuma linha tem pares.

Agora, ORDER BY ... ea_month não funcionará com strings para nomes de meses . Postgres seria classificado em ordem alfabética de acordo com a configuração local.

Se você tiver datevalores reais armazenados em sua tabela, poderá classificar corretamente. Caso contrário, sugiro substituir ea_yeare ea_monthpor uma única coluna mondo tipo dateem sua tabela.

  • Transforme o que você tem com to_date():

      to_date(ea_year || ea_month , 'YYYYMonth') AS mon
    
  • Para exibição, você pode obter strings originais com to_char():

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

Embora preso com o design infeliz, isso funcionará:

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;
Erwin Brandstetter
fonte
Obrigado pela solução .. Você pode me ajudar com mais uma coisa. Quero implementar a mesma coisa usando um cursor com a lógica de que todos os círculos terão apenas um registro por mês do ano. E a função deve ser executada uma vez por mês. Como posso conseguir isso?
Yousuf Sultan
4
@YousufSultan: Na maioria das vezes, há uma solução melhor do que um cursor. Isso é definitivamente material para uma nova questão. Por favor, comece uma nova pergunta.
Erwin Brandstetter
Acho essa resposta incompleta sem pelo menos uma observação de que há um "enquadramento" acontecendo aqui cujo padrão range unbounded precedingé, que é o mesmo que range between unbounded preceding and current row. É por isso que sum()quando usado como uma função de janela produz um total em execução - enquanto outras funções de janela não têm esse quadro padrão.
Colin 't Hart
1
@ Colin'tHart: Eu adicionei mais alguns itens acima para esclarecer.
Erwin Brandstetter
Aqui está um link para uma pergunta semelhante com uma consulta mais simples ( PARTITIONnem sempre é necessário para criar um total em execução): stackoverflow.com/a/5700744/175830
Jason Axelson