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
. AvecORDER BY
, cela définit le cadre sur toutes les lignes depuis le début de la partition jusqu'au dernierORDER 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;