问题
I want to calculate account balance using raw sql without extra application logic. Transaction schema includes amount, from_account_id and to_account_id
My query is
SELECT SUM(tdebit.amount) - SUM(tcredit.amount) as balance
FROM accounts as a
INNER JOIN transactions as tdebit ON a.id = tdebit.to_account_id
INNER JOIN transactions as tcredit ON a.id = tcredit.from_account_id
WHERE a.id = $1 AND tdebit.succeed = true AND tcredit.succeed = true
And it does not work as I expected - result is wrong, but if I join transaction only once it works correctly, for example just debit amount is ok
SELECT SUM(tdebit.amount) as debit
FROM accounts as a
INNER JOIN transactions as tdebit ON a.id = tdebit.to_account_id
WHERE a.id = $1 AND tdebit.succeed = true
What did I miss in my balance query?
http://sqlfiddle.com/#!15/b5565/1
回答1:
You are basically calculating the cross product between a tdebits
and tcredits
, i.e. for each row in tdebits
you are iterating over all the rows in tcredits
. There's also no reason to join to accounts
(unless to_account_id
and from_account_id
aren't foreign keys).
You only need to do one pass over transactions and you just need to know whether the amount is a credit or debit.
SELECT SUM(CASE WHEN t.to_account_id = $1 THEN t.amount ELSE -t.amount END) AS amount
FROM transactions AS t
WHERE (t.to_account_id = $1 OR t.from_account_id = $1)
AND t.succeed = true
If an account can transfer to itself, add a t.to_account_id <> t.from_account_id
.
回答2:
Aggregate before doing the joins, if you want the value for all accounts:
select a.*, coalesce(tdebit.debit, 0) - coalesce(tcredit.credit, 0)
from accounts a left join
(select t.to_account_id, sum(t.amount) as debit
from transactions t
group by t.to_account_id
) tdebit
on a.id = tdebit.to_account_id left join
(select t.from_account_id, sum(t.amount) as credit
from transactions t
group by t.from_account_id
) tcredit
on a.id = tcredit.from_account_id;
来源:https://stackoverflow.com/questions/35102056/sql-query-for-calculating-account-balance