2

I'm developing an application where I'll have to store some bank account informations, including daily account balances.

So, for instance:

17/10/2014 - (+) - Starting/Initial balance - 5,000.00
17/10/2014 - (=) - Balance - 5,000.00
-
18/10/2014 - (-) - Payment - (1,000.00)
18/10/2014 - (=) - Balance - 4,000.00
-
19/10/2014 - (=) - Balance - 4,000.00
-
20/10/2014 - (-) - Payment - (1,000.00)
20/10/2014 - (=) - Balance - 3,000.00

I think I could create a specific "account_balance" table where I could store every account balances for each day.

If I'm wrong, could you help me on finding the best way to do that? However, if I'm right, how can I make the database calculate daily balances and, specially, how can I make the database update balances when an user starts editing older values?

And by "older values", I mean:

1 - This is what "Account A" statement looks like:

18/10/2014 - (+) - Starting/Initial balance - 5,000.00
18/10/2014 - (=) - Balance - 5,000.00
-
19/10/2014 - (=) - Balance - 5,000.00
-
20/10/2014 - (=) - Balance - 5,000.00

2 - But the user forgot to register an income, so he does it by adding a new income (so now balances must be updated):

18/10/2014 - (+) - Starting/Initial balance - 5,000.00
18/10/2014 - (+) - Sales commission - 2,500.00 <- USER ADDED THIS.
18/10/2014 - (=) - Balance - 7,500.00 <- THIS BALANCE HAS BEEN UPDATED.
-
19/10/2014 - (=) - Balance - 7,500.00 <- THIS BALANCE HAS BEEN UPDATED.
-
20/10/2014 - (=) - Balance - 7,500.00 <- THIS BALANCE HAS BEEN UPDATED.
Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
Felipe
  • 376
  • 2
  • 5
  • 15
  • http://stackoverflow.com/questions/2494343/database-schema-design-for-a-double-entry-accounting-system might be helpful – radar Oct 21 '14 at 01:48
  • 1
    What about just storing the transaction and using a view to display the actual balance? – Eric B. Oct 21 '14 at 01:49
  • @EricB., could you elaborate it, please? I'm starting with bigger database structures. – Felipe Oct 21 '14 at 01:53
  • Do *you* really need to store daily account balances? (Some applications really do; most really don't.) Think hard before you allow editing values. If you allow edits, your accountants will probably hunt you down and poke you in the eye with a sharp stick. They're used to inserting a compensating transaction, not editing an errant transaction. – Mike Sherrill 'Cat Recall' Oct 21 '14 at 02:04
  • @MikeSherrill'CatRecall' I couldn't agree with you more, but in this case I really have to provide the functionality. Actually it is not intended to be a company/enterprise tool. – Felipe Oct 21 '14 at 02:07
  • Do you have the option of switching to PostgreSQL? Its window functions are just made for this kind of thing. – Mike Sherrill 'Cat Recall' Oct 21 '14 at 02:44
  • @MikeSherrill'CatRecall' I have to go with MySQL. But I will take a look at it, at least to know the tool. – Felipe Oct 21 '14 at 02:47
  • @Felipe: Based on our comments, I added an answer in written for PostgreSQL, and I added the "postgresql" tag to your question. – Mike Sherrill 'Cat Recall' Oct 21 '14 at 20:51

3 Answers3

4

Instead of storing balances, have a table which stores the transactions only for each user.

For example:

Date            Transactions        Comment
17/10/2014      +5,000.00           Starting/Initial balance - 
18/10/2014      -1,000.00           Payment
20/10/2014      -1,000.00           Payment

Then you can create a balance view (something like):

create view balance as
  select userId, sum(transactions) as balance from TransactionTable group by userId

If you want to be more precise and include start and stop dates (ie: to be able to get a balance at any point in time) you can create a parametrized view (haven't tried it using dates, but I presume it would work as well).

Eric B.
  • 23,425
  • 50
  • 169
  • 316
  • 1
    Right. But what about server resources? By this approach the server will have to calculate balances every time the user enters some new filter date. If I had the balances stored by day, It would only recover that data. But... Well, I'm not an experienced user. So what do you think about this? – Felipe Oct 21 '14 at 02:11
  • A valid consideration. Proper indexing will help a lot, but MySQL will still have to sum all the rows. A lot would depend on the number of transactions you expect to have per day per user, and how ofter you would need to check the daily balance. But given that you want the ability to update previous records, this kind of view would ensure that the data is always accurate. – Eric B. Oct 21 '14 at 02:25
4

This answer is for PostgreSQL, which the OP asked about in comments to the original question.

Data integrity is the most important thing to me. So I'm reluctant to store aggregate values unless a) performance is poor, and b) the dbms can guarantee the aggregate values are correct.

I started with this table.

create table transactions (
  trans_id serial primary key,
  cust_id integer not null, -- foreign key, references customers, not shown
  trans_time timestamp not null default current_timestamp,
  trans_amt numeric(14,2) not null 
);

create index on transactions (cust_id);

I chose timestamp instead of date, because applications like this usually need to support timestamps, and because in the general case it should perform worse than dates. If we get good performance with timestamps, we should be able to get good performance with dates. I did not assume that timestamps for a single customer were unique.

I loaded 20 million rows of random-ish data into this table, and then I updated the statistics. The data included positive and negative amounts. The amounts were in even hundreds of dollars to make visual inspection easier.

One of the more common queries in this kind of application involves returning a register for a single customer--all transactions with a running balance.

Here's the raw data for customer 128 for the first three days.

cust_id  trans_time            trans_amt
--
128      2014-01-01 08:36:09    200.00
128      2014-01-01 14:18:10    200.00
128      2014-01-01 14:26:56      0.00
128      2014-01-01 18:17:31    400.00
128      2014-01-01 20:18:53    100.00
128      2014-01-02 00:10:35      0.00
128      2014-01-02 01:44:26    300.00
128      2014-01-02 15:49:31   -300.00
128      2014-01-03 00:33:23    400.00
128      2014-01-03 11:55:13   -200.00
128      2014-01-03 11:56:34   -100.00
128      2014-01-03 14:58:42   -400.00
128      2014-01-03 17:31:11      0.00

We should expect these sums for the first three days.

2014-01-01   900.00
2014-01-02     0.00
2014-01-03  -300.00

And the running balance for the first three days should look like this.

2014-01-01   900.00
2014-01-02   900.00
2014-01-03   600.00

A register of daily balances

select 
      cust_id
    , trans_date
    , sum(daily_amt) over (partition by cust_id order by trans_date) daily_balance
from (select 
            cust_id
          , trans_time::date trans_date
          , sum(trans_amt) daily_amt
      from transactions
      where cust_id = 128
      group by cust_id, trans_date) x
order by cust_id, trans_date;
cust_id  trans_date   daily_balance
--
128      2014-01-01   900.00
128      2014-01-02   900.00
128      2014-01-03   600.00
. . .

Execution plan for the register

The execution plan shows that the query above runs in 12 ms. I think that's reasonable for this kind of application, but I might be able to reduce run time below 12 ms by indexing an expression (trans_time::date) or by a compound index.

"WindowAgg  (cost=7232.14..7252.94 rows=1040 width=40) (actual time=11.728..12.093 rows=294 loops=1)"
"  ->  Sort  (cost=7232.14..7234.74 rows=1040 width=40) (actual time=11.700..11.733 rows=294 loops=1)"
"        Sort Key: transactions.cust_id, ((transactions.trans_time)::date)"
"        Sort Method: quicksort  Memory: 38kB"
"        ->  HashAggregate  (cost=7156.62..7169.62 rows=1040 width=16) (actual time=11.392..11.466 rows=294 loops=1)"
"              ->  Bitmap Heap Scan on transactions  (cost=39.66..7141.89 rows=1964 width=16) (actual time=0.839..9.753 rows=1961 loops=1)"
"                    Recheck Cond: (cust_id = 128)"
"                    ->  Bitmap Index Scan on transactions_cust_id_idx  (cost=0.00..39.17 rows=1964 width=0) (actual time=0.501..0.501 rows=1961 loops=1)"
"                          Index Cond: (cust_id = 128)"
"Total runtime: 12.272 ms"
Mike Sherrill 'Cat Recall'
  • 91,602
  • 17
  • 122
  • 185
  • Thanks for the great answer and explanation! But can I do it with MySQL? Would there be any big differences? – Felipe Oct 22 '14 at 13:55
  • MySQL doesn't support window functions or window aggregates, so you'd have to do it a different way in MySQL. Performance would suffer, but might still be fast enough. If I get time, I'll rewrite it for MySQL. – Mike Sherrill 'Cat Recall' Oct 22 '14 at 14:04
  • Mike, after your suggestion (to go with PostgreSQL) I read some articles about the differences and use cases of both databases, and PostgreSQL seems to be a good choice. Anyway, a MySQL approach would be great! Do PostgreSQL have something similar to MySQL Workbench? I'm on Debian and didn't find anything on the repositories. – Felipe Oct 22 '14 at 14:36
  • Look at pgAdminIII. It *might* be installed by default when you install PostgreSQL. – Mike Sherrill 'Cat Recall' Oct 22 '14 at 15:28
1

In My Case Below is the table schema

table schema

for this i provide below solution

SELECT id, user_id, credit, debit,
COALESCE(((SELECT SUM(credit) FROM user_transactions b WHERE b.id <= a.id AND user_id = '7') - (SELECT SUM(debit) FROM user_transactions b WHERE b.id <= a.id AND user_id = '7')), 0) as balance
FROM user_transactions a WHERE user_id = '7' ORDER BY id ASC;

Here Is The Result Hope It Will Help You.

enter image description here

Renish Gotecha
  • 2,232
  • 22
  • 21