PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Calcul de la somme cumulée dans PostgreSQL

Fondamentalement, vous avez besoin d'une fonction de fenêtre. C'est une caractéristique standard de nos jours. En plus des véritables fonctions de fenêtre, vous pouvez utiliser tout fonction d'agrégation en tant que fonction de fenêtre dans Postgres en ajoutant un OVER clause.

La difficulté particulière ici est d'obtenir les partitions et le bon ordre de tri :

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;

Et non GROUP BY .

La somme de chaque ligne est calculée à partir de la première ligne de la partition jusqu'à la ligne actuelle - ou en citant le manuel pour être précis :

L'option de cadrage par défaut est RANGE UNBOUNDED PRECEDING , qui est identique à RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW . Avec ORDER BY , cela définit le cadre sur toutes les lignes depuis le début de la partition jusqu'au dernier ORDER BY de la ligne actuelle pair .

... qui est la somme cumulée ou courante que vous recherchez. J'insiste sur moi.

Lignes avec le même (circle_id, ea_year, ea_month) sont des "pairs" dans cette requête. Tous ceux-ci affichent la même somme courante avec tous les pairs ajoutés à la somme. Mais je suppose que votre table est UNIQUE le (circle_id, ea_year, ea_month) , alors l'ordre de tri est déterministe et aucune ligne n'a de pairs.

Postgres 11 a ajouté des outils pour inclure/exclure des pairs avec le nouveau frame_exclusion options. Voir :

  • Agréger toutes les valeurs n'appartenant pas au même groupe

Maintenant, ORDER BY ... ea_month ne fonctionnera pas avec des chaînes pour les noms de mois . Postgres trierait par ordre alphabétique en fonction des paramètres régionaux.

Si vous avez la date réelle valeurs stockées dans votre table, vous pouvez les trier correctement. Sinon, je suggère de remplacer ea_year et ea_month avec une seule colonne mon de type date dans votre tableau.

  • Transformez ce que vous avez avec to_date() :

      to_date(ea_year || ea_month , 'YYYYMonth') AS mon
    
  • Pour l'affichage, vous pouvez obtenir des chaînes originales avec to_char() :

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

Bien que coincé avec la conception malheureuse, cela fonctionnera :

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;