PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Calculando a soma cumulativa no PostgreSQL


Basicamente, você precisa de uma função de janela. Essa é uma característica padrão hoje em dia. Além das funções de janela genuínas, você pode usar qualquer função agregada como função de janela no Postgres anexando um OVER cláusula.

A dificuldade especial aqui é obter partições e ordenar corretamente:
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 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 é o mesmo que RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW . Com ORDER BY , isso define o quadro como todas as linhas da partição iniciam até o último ORDER BY da linha atual colega .

... que é a soma acumulada ou corrente que você procura. Minha ênfase em negrito.

Linhas com o mesmo (circle_id, ea_year, ea_month) são "pares" nesta consulta. Todos eles mostram a mesma soma corrente com todos os pares adicionados à soma. Mas suponho que sua tabela seja UNIQUE em (circle_id, ea_year, ea_month) , a ordem de classificação será determinística e nenhuma linha terá pares.

Postgres 11 adicionou ferramentas para incluir/excluir peers com o novo frame_exclusion opções. Ver:
  • Agregando todos os valores que não estão no mesmo grupo

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

Se você tiver date real valores armazenados em sua tabela você pode classificar corretamente. Caso contrário, sugiro substituir ea_year e ea_month com uma única coluna mon do tipo date na sua mesa.

  • 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
    

Enquanto estiver preso ao 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;