Reputation: 3002
I'm trying to generate a "cash lag" report out of an off-the-shelf financial system. I have access to the database (Oracle). The report should apply credits to debits, oldest first, and calculate the difference in days between when the bill was generated and when the payment was received. All debits and credits are stored in a single column of the financial transaction table. The applicable columns are:
Column Name Type
----------- -------------
AMOUNT NUMERIC(15,2) --debits are positive, credits are negative
FT_ID CHAR(10)
ACCOUNT_ID CHAR(10)
ACCEPTED_DT DATETIME
So for a table like:
ACCOUNT_ID FT_ID AMOUNT ACCEPTED_DT
---------------------------------------
1 12345 100.00 12/01/2011
1 12346 -75.00 12/11/2011
1 12347 100.00 12/12/2011
1 12348 -50.00 12/16/2011
What I would like to get back is something like the following (edit: updated to show amounts applied. I want the actual credit amount but that can be looked up based on the FT ID for the credit):
ACCOUNT_ID DEBIT_ID DEBIT_AMOUNT CREDIT_ID CREDIT_AMOUNT AGE
------------------------------------------------------------------
1 12345 75.00 12346 -75.00 10
1 12345 25.00 12348 -25.00 15
1 12347 25.00 12348 -25.00 4
1 12347 75.00 NULL NULL NULL
EDIT: the beauty of the off the shelf system my company has chosen is that payment application is configurable, meaning there is no direct link of a credit to a debit. We apply monies to oldest debt first, which is what I was trying to show in the example. The first payment gets applied completely to the oldest debt. The second payment gets split between the oldest and second oldest debt. This continues on until all credits have been applied.
EDIT 2: sorry this seems to be difficult for me to explain :) Looking at the example data, FT 12346 completely applies to FT 12345, leaving $25 of debt on the account. The next payment would then also apply to this debt, with any remaining amount applied to the next oldest debt. A running total doesn't match exactly what I'm trying to accomplish, because I need to know how old each "slice" of the match is:
EDIT 3: The table above was not clear at all, sorry. I've updated the table.
Debits Credits
+-----------------------+-------------------------+
| FT 12345 | FT 12346 |
| | |
| $100.00 | $75.00 |
| | |
| | |
| | |
| | |
| | |
| | |
| +-------------------------+
| | FT 12348 |
| | $50.00 |
| | +----->$25.00 towards 12345
+-----------------------|.........................|
| FT 12347 | |
| | +----->$25.00 towards 12347
| $100.00 +-------------------------+
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
+-----------------------+-------------------------+
We have a solution for this working with a cursor, but across the whole financial transaction table (~50m rows) this is extremely slow. I'm wondering if there's a way to restate the problem in terms of pure tables to speed this up. I found a recipe for genreating a running balance which seems like a start, but I'm not sure where to go from there.
Upvotes: 3
Views: 1836
Reputation: 4466
If an understanding what you want correctly, you can do this with analytics:
create table cred_deb (account_id integer, transaction_date date, amount number);
insert into cred_deb values (1, sysdate - 10, 100.00); -- bill of 100
insert into cred_deb values (1, sysdate - 9, -10.00); -- payment of 10
insert into cred_deb values (1, sysdate - 8, -80.00); -- payment of 80
insert into cred_deb values (1, sysdate - 5, 80.00); -- bill of 80
insert into cred_deb values(1, sysdate - 3, -80.00); -- payment of 80
-- 2nd account
insert into cred_deb values(2, sysdate - 3, 80.00); -- bill of 80
insert into cred_deb values(2, sysdate - 3, -80.00); -- payment of 80
select account_id,
transaction_date,
amount,
sum(amount) over( partition by account_id order by transaction_date) running_total
from cred_deb
order by account_id, transaction_date;
ACCOUNT_ID TRANSACTION_DATE AMOUNT RUNNING_TOTAL
---------------------- ------------------------- ---------------------- ----------------------
1 06-DEC-11 100 100
1 07-DEC-11 -10 90
1 08-DEC-11 -80 10
1 11-DEC-11 80 90
1 13-DEC-11 -80 10
2 13-DEC-11 80 80
2 13-DEC-11 -80 0
Looking closer at your example, maybe you want to group all credits for a particular bill along with the bill amount onto one row. If you can give a more clear description of what you what, I think analytics will be able to solve it.
EDIT - added age in days.
select account_id,
transaction_date,
amount,
sum(amount) over( partition by account_id order by transaction_date) running_total,
trunc(transaction_date) - max(case
when amount > 0 then
trunc(transaction_date)
else
null
end) over (partition by account_id order by transaction_date) age_in_days
from cred_deb
order by account_id, transaction_date
Upvotes: 1