Skip to content
Advertisement

Calculate account balance history in PostgreSQL

I am trying to get a balance history on the account using SQL. My table in PostgreSQL looks like this:

   id  sender_id    recipient_id           amount_money
   --- -----------  ---------------------- -----------------
   1   1            2                      60.00
   2   1            2                      15.00
   3   2            1                      35.00

so the user with id number 2 currently has 40 dollars in his account. I would like to get this result using sql:

[60, 75, 40]

Is it possible to do something like this using sql in postgres?

Advertisement

Answer

To get a rolling balance, you can SUM the amounts (up to and including the current row) based on whether the id was the recipient or sender:

SELECT id, sender_id, recipient_id, amount_money,
       SUM(CASE WHEN recipient_id = 2 THEN amount_money
                WHEN sender_id = 2 THEN -amount_money
           END) OVER (ORDER BY id) AS balance
FROM transactions

Output:

id  sender_id   recipient_id    amount_money    balance
1   1           2               60.00           60.00
2   1           2               15.00           75.00
3   2           1               35.00           40.00

If you want an array, you can use array_agg with the above query as a derived table:

SELECT array_agg(balance)
FROM (
  SELECT SUM(CASE WHEN recipient_id = 2 THEN amount_money
                  WHEN sender_id = 2 THEN -amount_money
             END) OVER (ORDER BY id) AS balance
  FROM transactions
) t

Output:

[60,75,40]

Demo on dbfiddle

If you want to be more sophisticated and support balances for multiple accounts, you need to split the initial data into account ids, adding when the id is the recipient and subtracting when the sender. You can use CTEs to generate the appropriate data:

WITH trans AS (
  SELECT id, sender_id AS account_id, -amount_money AS amount
  FROM transactions
  UNION ALL
  SELECT id, recipient_id AS account_id, amount_money AS amount
  FROM transactions
),
balances AS (
  SELECT id, account_id, ABS(amount),
         SUM(amount) OVER (PARTITION BY account_id ORDER BY id) AS balance
  FROM trans
)
SELECT account_id, ARRAY_AGG(balance) AS bal_array
FROM balances
GROUP BY account_id

Output:

account_id  bal_array
1           [-60,-75,-40]
2           [60,75,40]

Demo on dbfiddle

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement