Long ago Oracle introduced analytic functions for this kind of processing - to avoid joins, which may often be expensive (take a long time to process, compared to everything else in the query).
In this type of problem, it is most efficient to union all the two tables, using null column values where needed; then use the last_value function, ignoring nulls, and then just collect the results.
Assumptions:
- The Exchange Rate table has conversion rates for a variety of target currencies, not just USD. The test data does not illustrate that, but in the query I select only rows where
target_currency = 'USD' to allow for that. (Indeed, if the table only had exchange rates for target = USD then the target currency column wouldn't be needed).
- The exchange rate column is not nullable. (It shouldn't be!)
- For each transaction date, for the same currency there is a row in the exchange rate table with the same or earlier effective start date. This is a cross-table constraint that should be maintained at the DB level.
Query (including test data in with clause - not needed when using base tables)
with
sales ( sales_date, sales_amount, currency ) as (
select to_date('01-JAN-16', 'dd-MON-rr'), 500, 'INR' from dual union all
select to_date('01-JAN-16', 'dd-MON-rr'), 100, 'GBP' from dual union all
select to_date('02-JAN-16', 'dd-MON-rr'), 1000, 'INR' from dual union all
select to_date('02-JAN-16', 'dd-MON-rr'), 150, 'GBP' from dual union all
select to_date('03-JAN-16', 'dd-MON-rr'), 1500, 'INR' from dual
),
exch_rate ( source_currency, target_currency, exchange_rate, effective_date ) as (
select 'INR', 'USD', 0.014, to_date('31-DEC-15', 'dd-MON-rr') from dual union all
select 'INR', 'USD', 0.015, to_date('02-JAN-16', 'dd-MON-rr') from dual union all
select 'GBP', 'USD', 1.32, to_date('20-DEC-15', 'dd-MON-rr') from dual union all
select 'GBP', 'USD', 1.30, to_date('01-JAN-16', 'dd-MON-rr') from dual union all
select 'GBP', 'USD', 1.35, to_date('10-JAN-16', 'dd-MON-rr') from dual
),
prep ( dt, amt, src_curr, x_rate ) as (
select sales_date, sales_amount, currency, null from sales
union all
select effective_date, null, source_currency, exchange_rate
from exch_rate
where target_currency = 'USD'
),
with_x_rates ( dt, amt, src_curr, x_rate ) as (
select dt, amt, src_curr,
last_value (x_rate ignore nulls)
over (partition by src_curr order by dt, x_rate) as x_rate
from prep
)
select dt as sales_date, amt as sales_amount, src_curr as currency,
x_rate as exchange_rate,
amt * x_rate as sales_amount_in_usd
from with_x_rates
where amt is not null
order by sales_date, currency -- if needed
;
Output:
SALES_DATE SALES_AMOUNT CURRENCY EXCHANGE_RATE SALES_AMOUNT_IN_USD
---------- ------------ -------- ------------- -------------------
01-JAN-16 100 GBP 1.300 130.000
01-JAN-16 500 INR 0.014 7.000
02-JAN-16 150 GBP 1.300 195.000
02-JAN-16 1000 INR 0.015 15.000
03-JAN-16 1500 INR 0.015 22.500
5 rows selected.
Note: The query output has each column in its proper data type (date, number etc.). The formatting was all done in SQL*Plus; we don't want to format the data (convert it to strings) in the SQL query, since perhaps this is not the final product; its output may be consumed by further processing.