Reputation: 47
I have a requirement to generate exchange rates through the end of the month based on the latest transaction for each currency.
For example, let's say we get 2 days worth of exchange rates to process (the file could bring x number of days worth of exch rates) and they look as follows:
04/25/2015,GBP,USD,1.8 04/25/2015,USD,GBP,1.25 04/26/2015,GBP,USD,1.7346 04/26/2015,USD,GBP,1.1357
In this example, I would need to take the two records on 4/26 and generate records through the end of the month so the output would need to look like this:
04/27/2015,GBP,USD,1.7346 04/28/2015,GBP,USD,1.7346 04/29/2015,GBP,USD,1.7346 04/30/2015,GBP,USD,1.7346 04/27/2015,USD,GBP,1.1357 04/28/2015,USD,GBP,1.1357 04/29/2015,USD,GBP,1.1357 04/30/2015,USD,GBP,1.1357
What I've done is the following in procedure in PLSQL but this doesn't seem to be working as I would expect it.
DECLARE
l_max_date date;
CURSOR C1 IS
SELECT FROM_CURRENCY_DATE,
FROM_CURRENCY,
TO_CURRENCY,
NUMERATOR_BUY,
CONVERSION_RATE,
LEAD(conversion_rate) OVER (PARTITION BY from_currency ORDER BY from_currency_date) AS LEAD_conversion_rate,
ROW_NUMBER() OVER (PARTITION BY from_currency ORDER BY from_currency_date DESC) AS rn
FROM exchange_rate_staging_tbl
WHERE valid_flag is null or valid_flag <> 'E';
cur_rec c1%rowtype;
BEGIN
SELECT MAX(from_currency_date)
INTO l_max_date
FROM exchange_rate_staging_tbl;
FOR cur_rec IN c1 LOOP
if cur_rec.lead_conversion_rate is null then -->Null means it is the latest transaction
dbms_output.put_line(cur_rec.from_currency||' '||
cur_rec.to_currency||' '||
'Inside IF'||' '||
cur_rec.conversion_rate);
ELSE --> Records here are not the latest transaction but they still need to be inserted with their respective exch rate
--dbms_output.put_line(l_max_date||last_day(l_max_date));
dbms_output.put_line(cur_rec.from_currency||' '||
cur_rec.to_currency||' '||
'Inside Else'||' '||
cur_rec.conversion_rate);
END IF;
l_max_date := l_max_date+1;
END LOOP;
END;
The procedure above outputs:
GBP USD 25-APR-15 25-APR-15 Inside Else 1.8
GBP USD 26-APR-15 26-APR-15 Inside IF 1.7346
How would I generate the records based on the latest transaction for each currency through the end of the month in SQL or PLSQL.
Upvotes: 1
Views: 475
Reputation: 14848
This query looks promising:
with
dm as (select max(from_currency_date) d from exchange_rate_staging_tbl),
dr as (select distinct from_currency fc, to_currency tc,
last_value(conversion_rate ignore nulls) over
(partition by from_currency, to_currency order by from_currency_date
rows between unbounded preceding and unbounded following) rate
from exchange_rate_staging_tbl, dm where from_currency_date = dm.d),
days as (select d+level-1 day from dm connect by d+level-1 <= last_day(d))
select days.day, dr.fc, dr.tc, rate
from days cross join dr
There is one problem - if 26 Apr we have exchange rates for GBP/USD but USD/GBP is missing then only GBP/USD will be replicated. I don't know if this situation may take place, if yes then query has to be changed. There are also more doubts like what to do if there are two exchange rates for same currencies etc, but I don't know what you want in such situation.
How does it work:
dm
just looks for max date in table, you may want to add filter for month here, for instance where from_currency_date <= 'date 2015-04-30'
,dr
gathers info for all currency rates from this day, distinct last_value
guarantees that only one row will be taken for each pair of currencies,days
is hierarchical date generator, produces missing dates to the end of month,Edit according to comment:
How can I still bring the converstion rate for 4/25
with
dm as (select date '2015-04-25' d from dual),
dc as (
select distinct from_currency, to_currency
from exchange_rate_staging_tbl, dm
where from_currency_date between d and last_day(d)),
days as (
select d+level-1 from_currency_date
from dm connect by d+level-1 <= last_day(d))
select from_currency_date, from_currency, to_currency,
last_value(conversion_rate ignore nulls) over
(partition by from_currency, to_currency order by from_currency_date
rows unbounded preceding ) rate
from dc cross join days
left join exchange_rate_staging_tbl e
using (from_currency_date, from_currency, to_currency)
order by from_currency_date, from_currency, to_currency
This query shows data from given date (you define it in first line) to the end of the month filling gaps - let's say we have no conversion rates for Saturday and Sunday, query fills this gap with data from Friday.
Function last_value() finds last value of conversion rate
from some set of values somehow grouped (partitioned). In our case these partitions are pairs of currencies, order is currency_date.
In human words: find last not null value of conversion rate for each pair of currencies in given period. Note that this period changes for each row. Last_value
is one of analytic functions which have very broad range of use.
Good article about these functions: Analytic functions by Example.
Upvotes: 0
Reputation: 5636
Your data doesn't look like any rate data I've ever seen. Usually, everything is referenced against one currency. In my case, it was USD. Now you need only one entry for each currency per day. Like this:
Effective Code Factor
2015-05-02 GBP 0.662251656 --based on today's rate of 1.51 GBP->USD
To go from USD to GBP: USD * factor = GBP
To go from GBP to USD: GBP / factor = USD
There is a slide presentation that shows such a table design here. The currency discussion starts on slide/page 12, the query on page 16. The table design is simple:
create table XRates(
Code char( 3 ) not null,
Effective date not null,
Factor decimal( 12, 9 ) not null
constraint PK_XRates primary key( Code, Effective )
);
A couple of nice features about the design is that you maintain historical rates in the same table and daily entries are not necessary. An entry is made only when the rate changes. The query returns the rate that was in effect on the given date, even if that rate was established by a week-old entry (not likely in today's currency markets).
Upvotes: 0