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?


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;

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.