I have two tables
Payments_history
CREATE TABLE payments_history(payment_date , account_id , currency, amount) AS
SELECT TO_DATE ('05/01/2022','DD/MM/YYYY'), 2291969088, 'GBP', 10.00 FROM DUAL UNION ALL
SELECT TO_DATE ('05/01/2022','DD/MM/YYYY'), 7851880663, 'USD', 20 FROM DUAL UNION ALL
SELECT TO_DATE ('06/01/2022','DD/MM/YYYY'), 5326844767, 'USD', 3.000 FROM DUAL UNION ALL
SELECT TO_DATE ('05/01/2022','DD/MM/YYYY'), 3668657617, 'EUR', 40 FROM DUAL UNION ALL
SELECT TO_DATE ('06/01/2022','DD/MM/YYYY'), 9040142052, 'GBP', 30.000 FROM DUAL
Historics_rates
Create TABLE Historics_rates(t_date,from_ccy,to_ccy,rate) AS
SELECT TO_DATE ('06/01/2022','DD/MM/YYYY'),'GBP','EUR',1.1832 FROM DUAL UNION ALL
SELECT TO_DATE ('06/01/2022','DD/MM/YYYY'),'AUD','GBP',0.5263 FROM DUAL UNION ALL
SELECT TO_DATE ('06/01/2022','DD/MM/YYYY'),'EUR','GBP',0.8452 FROM DUAL UNION ALL
SELECT TO_DATE ('05/01/2022','DD/MM/YYYY'),'USD','GBP',0.7388 FROM DUAL UNION ALL
SELECT TO_DATE ('05/01/2022','DD/MM/YYYY'),'EUR','USD',1.1441 FROM DUAL
what I am trying to find is the 'daily amount GBP Equivalent per day for the last three months'. For example on date '05/01/2022 ' check the given amount in Payments_history table if the currency is GBP add it into total and move on to the next payment, next if the currency is in USD check for its equivalent in GBP rate and convert it into GBP and print the result. If the amount is in some currency whose from_ccy is not GBP skip that transaction
Date Amount in GBP
2022-01-05 24.78
This is what I have done so far
DECLARE
total NUMBER;
BEGIN
select PH.PAYMENT_DATE,
CASE
WHEN PH.CURRENCY = 'GBP' THEN total = total + PH.AMOUNT
WHEN PH.CURRENCY = 'USD' AND HR.from_ccy = 'USD' AND HR.to_ccy = 'GBP' THEN total = total + (PH.AMOUNT*HR."rate")
WHEN PH.CURRENCY = 'AUD' AND HR.from_ccy = 'AUD' AND HR.to_ccy = 'GBP' THEN total = total + (PH.AMOUNT*HR."rate")
WHEN PH.CURRENCY = 'EUR' AND HR.from_ccy = 'EUR' AND HR.to_ccy = 'GBP' THEN total = total + (PH.AMOUNT*HR."rate")
ELSE 'CURRENCY NOT FOUND'
END AS total
FROM "historic_rates" AS HR RIGHT JOIN PAYMENTS_HISTORY AS PH on HR."date" = PH.payment_date AND PH.Currency = HR."from_ccy"
WHERE Extract(Month from PH.payment_date) = Extract(month from add_months( sysdate, -3 )) GROUP BY PH.PAYMENT_DATE;
but it's giving me error
[Err] ORA-06550: line 8, column 40:
PL/SQL: ORA-00905: missing keyword
ORA-06550: line 6, column 1:
PL/SQL: SQL Statement ignored
ORA-06550: line 15, column 117:
PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:
( begin case declare end exception exit for goto if loop mod
null pragma raise return select update while with
<an identifier> <a double-quot
Your errors include:
Using = inside the THEN clause of a CASE expression is invalid syntax.
You cannot refer to a column alias inside the sub-query where it is defined (except in an ORDER BY clause) so you cannot use total inside the CASE expression.
A CASE expression needs to have the same data type in all of its outputs so you cannot mix numbers and strings.
You GROUP BY PH.PAYMENT_DATE but you do not have any aggregation function around the CASE expression.
If you do aggregate then 'CURRENCY NOT FOUND' is not something you can total.
In Oracle, AS before a table alias is invalid syntax.
You have a mix of quoted identifiers HR."from_ccy" and unquoted identifiers HR.from_ccy. While it is possible to have both in a table, one of them is almost certainly wrong (and using quoted identifiers is bad practice) and your DDL statements do not use quotes.
Unless you really do intend to get rows from December of any year, do not compare only months. Compare on a range from the start of the month 3-months ago until before the start of the month two-months ago.
Your PL/SQL block does not have an END statement.
You are using a SELECT statement in PL/SQL without using SELECT ... INTO.
You probably don't want to use PL/SQL.
None of your sample data is from 3 months ago (December 2021).
Something like this:
select PH.PAYMENT_DATE,
SUM(
CASE
WHEN PH.CURRENCY = 'GBP'
THEN PH.AMOUNT
WHEN PH.CURRENCY IN ('USD', 'AUD', 'EUR')
AND HR.from_ccy = PH.CURRENCY
AND HR.to_ccy = 'GBP'
THEN PH.AMOUNT*HR.rate
END
) As total
FROM historics_rates HR
RIGHT JOIN PAYMENTS_HISTORY PH
on HR.t_date = PH.payment_date
AND PH.Currency = HR.from_ccy
WHERE PH.payment_date >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -3)
AND PH.payment_date < ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -2)
GROUP BY PH.PAYMENT_DATE;
Which, for the sample data:
CREATE TABLE payments_history(payment_date , account_id , currency, amount) AS
SELECT TO_DATE ('05/12/2021','DD/MM/YYYY'), 2291969088, 'GBP', 10.00 FROM DUAL UNION ALL
SELECT TO_DATE ('05/12/2021','DD/MM/YYYY'), 7851880663, 'USD', 20 FROM DUAL UNION ALL
SELECT TO_DATE ('06/12/2021','DD/MM/YYYY'), 5326844767, 'USD', 3.000 FROM DUAL UNION ALL
SELECT TO_DATE ('05/12/2021','DD/MM/YYYY'), 3668657617, 'EUR', 40 FROM DUAL UNION ALL
SELECT TO_DATE ('06/12/2021','DD/MM/YYYY'), 9040142052, 'GBP', 30.000 FROM DUAL;
Create TABLE Historics_rates(t_date,from_ccy,to_ccy,rate) AS
SELECT TO_DATE ('06/12/2021','DD/MM/YYYY'),'GBP','EUR',1.1832 FROM DUAL UNION ALL
SELECT TO_DATE ('06/12/2021','DD/MM/YYYY'),'AUD','GBP',0.5263 FROM DUAL UNION ALL
SELECT TO_DATE ('06/12/2021','DD/MM/YYYY'),'EUR','GBP',0.8452 FROM DUAL UNION ALL
SELECT TO_DATE ('05/12/2021','DD/MM/YYYY'),'USD','GBP',0.7388 FROM DUAL UNION ALL
SELECT TO_DATE ('05/12/2021','DD/MM/YYYY'),'EUR','USD',1.1441 FROM DUAL;
Outputs:
PAYMENT_DATE
TOTAL
06-DEC-21
30
05-DEC-21
24.776
db<>fiddle here
Related
I have a transaction table that stores amount paid(+amount) and corrected (-ve amount). I am looking for a query that would ignore a positive and a negative matching value of the amount for a date and post the sum of remaining number of transactions ignoring the 2 .
Id Dept Date Amount
1 A 21-Apr-21 1100
1 A 21-Apr-21 1100
1 A 21-Apr-21 -1100
1 A 07-Apr-21 1100
1 A 03-Feb-21 100
1 A 12-Jan-21 500
The sql query should ignore Rows 2 and 3 as the amount was corrected and should not be counted as a transaction.
o/p should be
Id Dept sum(Amount) count(transaction)
1 A 2800 4
If I got you well, you can use below solution for that purpose.
I first ranked all the occurrences of the same amount value, before I grouped them in order to make oracle ignore all matching positive and negative values.
with YourSample (Id, Dept, Date#, Amount) as (
select 1, 'A', to_date('21-Apr-21', 'dd-Mon-RR', 'nls_date_language=english'), 1100 from dual union all
select 1, 'A', to_date('21-Apr-21', 'dd-Mon-RR', 'nls_date_language=english'), 1100 from dual union all
select 1, 'A', to_date('21-Apr-21', 'dd-Mon-RR', 'nls_date_language=english'), -1100 from dual union all
select 1, 'A', to_date('07-Apr-21', 'dd-Mon-RR', 'nls_date_language=english'), 1100 from dual union all
select 1, 'A', to_date('03-Feb-21', 'dd-Mon-RR', 'nls_date_language=english'), 100 from dual union all
select 1, 'A', to_date('12-Jan-21', 'dd-Mon-RR', 'nls_date_language=english'), 500 from dual
)
, ranked_rws as (
select Id, Dept, Date#
, abs(Amount)Amount
, sign(AMOUNT) row_sign
, row_number() OVER (PARTITION BY Id, Dept, Amount order by date#, rownum) rn
from YourSample t
)
, ingored_matched_pos_neg_values as (
select ID, DEPT, sum(row_sign) * AMOUNT AMOUNT/*, sum(row_sign)*/
from ranked_rws
group by ID, DEPT, AMOUNT, RN
having sum(row_sign) != 0 /* this line filters out all matching positive
and negatives values (equality in terms of occurrences)*/
)
select ID, DEPT, sum(AMOUNT) sum, count(*) transactions
from ingored_matched_pos_neg_values
group by ID, DEPT
;
demo
Maybe some idea like this could work.
SELECT Id, Dept, Date, Amount, COUNT(*) AS RecordCount
INTO #temptable
FROM table GROUP BY ...
SELECT
t1.Id
,t1.Dept
,t1.Date
,(t1.RecordCount - COALESCE(t2.RecordCount, 0)) * t1.Amount
,t1.RecordCount - COALESCE(t2.RecordCount, 0)
FROM #temptable t1
LEFT JOIN #temptable t2 ON
t1.Id = t2.Id
AND t1.Dept = t2.Dept
AND t1.Date = t2.Date
AND (t1.Amount * -1) = t2.Amount
I wrote query to find sum of money in a given period with one filial and it is working fast:
SELECT FILIAL_CODE,
sum(sum_eqv)/100 AS summa
FROM table
WHERE substr(acc,1,5) = '65434'
and cast(substr(acc,18,3) as integer) >= 600
and cast(substr(account_co,18,3) as integer)<=607
AND o_day >= to_date('01.12.2019', 'DD.MM.YYYY')
and oday < to_date('08.12.2019', 'DD.MM.YYYY')+ INTERVAL '1' DAY
AND FILIAL_CODE = '001234'
Above query is working fine. But when I want to use it multiple filials it is becoming more complex.
Below query is needs to be fixed.
SELECT FILIAL_CODE,
sum(sum_eqv)/100 AS summa
FROM table
WHERE substr(acc,1,5) = '65434'
and cast(substr(acc,18,3) as integer) >= 600
and cast(substr(account_co,18,3) as integer)<=607
AND o_day >= to_date('01.12.2019', 'DD.MM.YYYY')
and oday < to_date('08.12.2019', 'DD.MM.YYYY')+ INTERVAL '1' DAY
AND FILIAL_CODE in (select code from city where region = '26')
group by FILIAL_CODE;
This query runs long. How can I maximize this statement. Any Help is appreciated!
Try JOIN instead of IN, such as:
SELECT t.filial_code, SUM (sum_eqv) / 100 AS summa
FROM your_table t JOIN city c ON c.code = t.filial_code
WHERE SUBSTR (t.acc, 1, 5) = '65434'
AND CAST (SUBSTR (t.acc, 18, 3) AS INTEGER) >= 600
AND CAST (SUBSTR (t.account_co, 18, 3) AS INTEGER) <= 607
AND t.o_day >= TO_DATE ('01.12.2019', 'DD.MM.YYYY')
AND t.oday < TO_DATE ('08.12.2019', 'DD.MM.YYYY') + INTERVAL '1' DAY
AND c.region = '26'
GROUP BY t.filial_code;
It would probably help if city.code and table.filial_code were indexed.
I have a table that has aggregations down to the hour level YYYYMMDDHH. The data is aggregated and loaded by an external process (I don't have control over). I want to test the data on a monthly basis.
The question I am looking to answer is: Does every hour in the month exist?
I'm looking to produce output that will return a 1 if the hour exists or 0 if the hour does not exist.
The aggregation table looks something like this...
YYYYMM YYYYMMDD YYYYMMDDHH DATA_AGG
201911 20191101 2019110100 100
201911 20191101 2019110101 125
201911 20191101 2019110103 135
201911 20191101 2019110105 95
… … … …
201911 20191130 2019113020 100
201911 20191130 2019113021 110
201911 20191130 2019113022 125
201911 20191130 2019113023 135
And defined as...
CREATE TABLE YYYYMMDDHH_DATA_AGG AS (
YYYYMM VARCHAR,
YYYYMMDD VARCHAR,
YYYYMMDDHH VARCHAR,
DATA_AGG INT
);
I'm looking to produce the following below...
YYYYMMDDHH HOUR_EXISTS
2019110100 1
2019110101 1
2019110102 0
2019110103 1
2019110104 0
2019110105 1
... ...
In the example above, two hours do not exist, 2019110102 and 2019110104.
I assume I'd have to join the aggregation table against a computed table that contains all the YYYYMMDDHH combos???
The database is Snowflake, but assume most generic ANSI SQL queries will work.
You can get what you want with a recursive CTE
The recursive CTE generates the list of possible Hours. And then a simple left outer join gets you the flag for if you have any records that match that hour.
WITH RECURSIVE CTE (YYYYMMDDHH) as
(
SELECT YYYYMMDDHH
FROM YYYYMMDDHH_DATA_AGG
WHERE YYYYMMDDHH = (SELECT MIN(YYYYMMDDHH) FROM YYYYMMDDHH_DATA_AGG)
UNION ALL
SELECT TO_VARCHAR(DATEADD(HOUR, 1, TO_TIMESTAMP(C.YYYYMMDDHH, 'YYYYMMDDHH')), 'YYYYMMDDHH') YYYYMMDDHH
FROM CTE C
WHERE TO_VARCHAR(DATEADD(HOUR, 1, TO_TIMESTAMP(C.YYYYMMDDHH, 'YYYYMMDDHH')), 'YYYYMMDDHH') <= (SELECT MAX(YYYYMMDDHH) FROM YYYYMMDDHH_DATA_AGG)
)
SELECT
C.YYYYMMDDHH,
IFF(A.YYYYMMDDHH IS NOT NULL, 1, 0) HOUR_EXISTS
FROM CTE C
LEFT OUTER JOIN YYYYMMDDHH_DATA_AGG A
ON C.YYYYMMDDHH = A.YYYYMMDDHH;
If your timerange is too long you'll have issues with the cte recursing too much. You can create a table or temp table with all of the possible hours instead. For example:
CREATE OR REPLACE TEMPORARY TABLE HOURS (YYYYMMDDHH VARCHAR) AS
SELECT TO_VARCHAR(DATEADD(HOUR, SEQ4(), TO_TIMESTAMP((SELECT MIN(YYYYMMDDHH) FROM YYYYMMDDHH_DATA_AGG), 'YYYYMMDDHH')), 'YYYYMMDDHH')
FROM TABLE(GENERATOR(ROWCOUNT => 10000)) V
ORDER BY 1;
SELECT
H.YYYYMMDDHH,
IFF(A.YYYYMMDDHH IS NOT NULL, 1, 0) HOUR_EXISTS
FROM HOURS H
LEFT OUTER JOIN YYYYMMDDHH_DATA_AGG A
ON H.YYYYMMDDHH = A.YYYYMMDDHH
WHERE H.YYYYMMDDHH <= (SELECT MAX(YYYYMMDDHH) FROM YYYYMMDDHH_DATA_AGG);
You can then fiddle with the generator count to make sure you have enough hours.
You can generate a table with every hour of the month and LEFT OUTER JOIN your aggregation to it:
WITH EVERY_HOUR AS (
SELECT TO_CHAR(DATEADD(HOUR, HH, TO_DATE(YYYYMM::TEXT, 'YYYYMM')),
'YYYYMMDDHH')::NUMBER YYYYMMDDHH
FROM (SELECT DISTINCT YYYYMM FROM YYYYMMDDHH_DATA_AGG) t
CROSS JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY NULL) - 1 HH
FROM TABLE(GENERATOR(ROWCOUNT => 745))
) h
QUALIFY YYYYMMDDHH < (YYYYMM + 1) * 10000
)
SELECT h.YYYYMMDDHH, NVL2(a.YYYYMM, 1, 0) HOUR_EXISTS
FROM EVERY_HOUR h
LEFT OUTER JOIN YYYYMMDDHH_DATA_AGG a ON a.YYYYMMDDHH = h.YYYYMMDDHH
Here's something that might help get you started. I'm guessing you want to have 'synthetic' [YYYYMMDD] values? Otherwise, if the value aren't there, then they shouldn't appear in the list
DROP TABLE IF EXISTS #_hours
DROP TABLE IF EXISTS #_temp
--Populate a table with hours ranging from 00 to 23
CREATE TABLE #_hours ([hour_value] VARCHAR(2))
DECLARE #_i INT = 0
WHILE (#_i < 24)
BEGIN
INSERT INTO #_hours
SELECT FORMAT(#_i, '0#')
SET #_i += 1
END
-- Replicate OP's sample data set
CREATE TABLE #_temp (
[YYYYMM] INTEGER
, [YYYYMMDD] INTEGER
, [YYYYMMDDHH] INTEGER
, [DATA_AGG] INTEGER
)
INSERT INTO #_temp
VALUES
(201911, 20191101, 2019110100, 100),
(201911, 20191101, 2019110101, 125),
(201911, 20191101, 2019110103, 135),
(201911, 20191101, 2019110105, 95),
(201911, 20191130, 2019113020, 100),
(201911, 20191130, 2019113021, 110),
(201911, 20191130, 2019113022, 125),
(201911, 20191130, 2019113023, 135)
SELECT X.YYYYMM, X.YYYYMMDD, X.YYYYMMDDHH
-- Case: If 'target_hours' doesn't exist, then 0, else 1
, CASE WHEN X.target_hours IS NULL THEN '0' ELSE '1' END AS [HOUR_EXISTS]
FROM (
-- Select right 2 characters from converted [YYYYMMDDHH] to act as 'target values'
SELECT T.*
, RIGHT(CAST(T.[YYYYMMDDHH] AS VARCHAR(10)), 2) AS [target_hours]
FROM #_temp AS T
) AS X
-- Right join to keep all of our hours and only the target hours that match.
RIGHT JOIN #_hours AS H ON H.hour_value = X.target_hours
Sample output:
YYYYMM YYYYMMDD YYYYMMDDHH HOUR_EXISTS
201911 20191101 2019110100 1
201911 20191101 2019110101 1
NULL NULL NULL 0
201911 20191101 2019110103 1
NULL NULL NULL 0
201911 20191101 2019110105 1
NULL NULL NULL 0
With (almost) standard sql, you can do a cross join of the distinct values of YYYYMMDD to a list of all possible hours and then left join to the table:
select concat(d.YYYYMMDD, h.hour) as YYYYMMDDHH,
case when t.YYYYMMDDHH is null then 0 else 1 end as hour_exists
from (select distinct YYYYMMDD from tablename) as d
cross join (
select '00' as hour union all select '01' union all
select '02' union all select '03' union all
select '04' union all select '05' union all
select '06' union all select '07' union all
select '08' union all select '09' union all
select '10' union all select '11' union all
select '12' union all select '13' union all
select '14' union all select '15' union all
select '16' union all select '17' union all
select '18' union all select '19' union all
select '20' union all select '21' union all
select '22' union all select '23'
) as h
left join tablename as t
on concat(d.YYYYMMDD, h.hour) = t.YYYYMMDDHH
order by concat(d.YYYYMMDD, h.hour)
Maybe in Snowflake you can construct the list of hours with a sequence much easier instead of all those UNION ALLs.
This version accounts for the full range of days, across months and years. It's a simple cross join of the set of possible days with the set of possible hours of the day -- left joined to actual dates.
set first = (select min(yyyymmdd::number) from YYYYMMDDHH_DATA_AGG);
set last = (select max(yyyymmdd::number) from YYYYMMDDHH_DATA_AGG);
with
hours as (select row_number() over (order by null) - 1 h from table(generator(rowcount=>24))),
days as (
select
row_number() over (order by null) - 1 as n,
to_date($first::text, 'YYYYMMDD')::date + n as d,
to_char(d, 'YYYYMMDD') as yyyymmdd
from table(generator(rowcount=>($last-$first+1)))
)
select days.yyyymmdd || lpad(hours.h,2,0) as YYYYMMDDHH, nvl2(t.yyyymmddhh,1,0) as HOUR_EXISTS
from days cross join hours
left join YYYYMMDDHH_DATA_AGG t on t.yyyymmddhh = days.yyyymmdd || lpad(hours.h,2,0)
order by 1
;
$first and $last can be packed in as sub-queries if you prefer.
I have one table of data with revenue in multiple currencies (let's call this one TRANSACTION_TABLE, with columns as such:
TRANSACTION_NAME
TRANSACTION_VALUE
CURRENCY
, and another table with exchange rates (EXCHANGE_RATE) with columns as such:
FROM_CURRENCY (e.g. JPY)
TO_CURRENCY (e.g. USD)
EXCHANGE_RATE (x)
The table has, at minimum, every currency converting to USD, but is not exhaustive with exchange rates for non-USD TO_CURRENCY values.
What I'm trying to achieve, is a query which converts the transactions to any currency, even if not explicitly stipulated in the EXCHANGE_RATE table, by converting the currencies to USD first, and then from USD into the destination currency.
E.g. 1000 JPY to GBP:
Find rate JPY to USD - calculation = 1000 * EXCHANGE_RATE = 9
Find rate GBP to USD - calculation = 9 \ EXCHANGE_RATE = 7
At the moment, I've done a left join for TRANSACTION_TABLE on EXCHANGE_RATE but I'm lost at where to go next.
Any assistance would be greatly appreciated.
The query (very basic) I've built so far is as follows, and I'm a novice SQL user. I built this query first to convert to USD, which works fine (as my Exchange Rate table contains values for all currencies to USD) - but it obviously fails when setting the destination currency as GBP, as it'll just return nulls.
SELECT TRANSACTION_NAME,
SUM (TRANSACTION_VALUE * EXCHANGE_RATE)
AS "REVENUE GBP"
FROM TRANSACTION_TABLE S
LEFT JOIN EXCHANGE_RATE C ON S.CURRENCY = C.FROM_CURRENCY AND C.TO_CURRENCY = 'GBP'
ORDER BY TRANSACTION_NAME
If your EXCHANGE_RATE table is exhaustive to USD, then you won't ever have more than two "hops" to do your conversion. At most, you'll convert to USD and then from USD to whatever. Given that, I would just code for all the possible cases rather than try something fancy like a CONNECT BY.
"All possible cases", I think, are:
The transaction is already in the target currency
The transaction is in a currency that is directly convertible to the target currency
The transaction must be converted to USD and then from USD to the target currency.
Here is a query that will do that. The WITH clauses are just to give it some data -- they won't be part of your solution, since you have the actual tables.
WITH rates ( from_currency, to_currency, exchange_rate ) AS
( SELECT 'JPY', 'USD', 0.009 FROM DUAL UNION ALL
SELECT 'GBP', 'USD', 1.31 FROM DUAL UNION ALL
SELECT 'CNY', 'USD', 0.15 FROM DUAL UNION ALL
SELECT 'JPY', 'CNY', 0.06 FROM DUAL),
txns ( transaction_name, transaction_value, currency ) AS
( SELECT 'txn 1 in JPY', 1000, 'JPY' FROM DUAL UNION ALL
SELECT 'txn 2 in GBP', 1000, 'GBP' FROM DUAL UNION ALL
SELECT 'txn 3 IN CNY', 1000, 'CNY' FROM DUAL UNION ALL
SELECT 'txn 4 IN unknown', 1000, 'XXX' FROM DUAL),
params ( target_currency ) AS
( SELECT 'CNY' FROM DUAL )
SELECT t.transaction_name,
t.transaction_value base_value,
t.currency base_currency,
t.transaction_value * CASE WHEN t.currency = params.target_currency THEN 1
WHEN r1.from_currency IS NOT NULL THEN r1.exchange_rate
ELSE r2usd.exchange_rate / r2tar.exchange_rate END converted_value,
params.target_currency converted_currency
FROM params CROSS JOIN
txns t
LEFT JOIN rates r1 ON r1.from_currency = t.currency AND r1.to_currency = params.target_currency
LEFT JOIN rates r2usd ON r2usd.from_currency = t.currency AND r2usd.to_currency = 'USD'
LEFT JOIN rates r2tar ON r2tar.from_currency = params.target_currency AND r2tar.to_currency = 'USD'
I'd propose to make an extra step to expand you exchange table with the exchange rates additionaly defined using UDS as transfer currency.
This query adds the new rates calulated via USD. It is a simple inner join constrained so that the calculation is via 'USD' and the from and to currencies are different. The WHERE clause limits the already know combinations.
select er1.FROM_CURRENCY, er2.TO_CURRENCY, er1.EXCHANGE_RATE * er2.EXCHANGE_RATE EXCHANGE_RATE
from exchange_rates er1
join exchange_rates er2
on er1.TO_CURRENCY = 'USD' and er2.FROM_CURRENCY = 'USD' and er1.FROM_CURRENCY != er2.TO_CURRENCY
where (er1.FROM_CURRENCY, er2.TO_CURRENCY)
not in (select FROM_CURRENCY, TO_CURRENCY from exchange_rates)
You may define a physical new table or view or even perform it only as a subquery as an UNION ALL of your original table and the result of this query.
Your final query uses this extended exchange rate table instead of the original one.
Here are sample data I tested with
create table exchange_rates
as
select 'GBP' FROM_CURRENCY, 'USD' TO_CURRENCY, 1.31 EXCHANGE_RATE from dual union all
select 'EUR' FROM_CURRENCY, 'USD' TO_CURRENCY, 1.16 EXCHANGE_RATE from dual union all
select 'AUD' FROM_CURRENCY, 'USD' TO_CURRENCY, .73 EXCHANGE_RATE from dual union all
select 'USD' FROM_CURRENCY, 'GBP' TO_CURRENCY, .76 EXCHANGE_RATE from dual union all
select 'USD' FROM_CURRENCY, 'EUR' TO_CURRENCY, .86 EXCHANGE_RATE from dual union all
select 'USD' FROM_CURRENCY, 'AUD' TO_CURRENCY, 1.36 EXCHANGE_RATE from dual union all
select 'GBP' FROM_CURRENCY, 'EUR' TO_CURRENCY, 1.12 EXCHANGE_RATE from dual;
I need to count occurrences of protocol violations and durations between 2 dates from table to achieve effect like statistics table which will look like at the picture below:
Expected effect:
Explanation:
As you can see I need to select 'Country', 'Site' existing in Violations table and: 'Numbers', 'Maximum', 'Minimum' and 'Mean' of protocol violations duration existing in DB in the same table 'Violations' between two dates. So we have to count:
protocol violations occurrences existing in Violations table by country and site
min/max/avg durations of protocol violations by country and site
under two different conditions:
occurrences from Date Discovered to Date Reported
occurrences from Date Reported to Date Confirmed
Database Structure:
Available at SQLFILDDLE: Look HERE
I will add that code in attached SQLFIDDLE has more tables and an query but they are unnecessary right now for this problem. Feel free to use it.
I didn't remove old query because there is nice way to do:
'- All -' and
'- Unknown -' values. -
Violation table:
create table violations (
id long,
country varchar(20),
site varchar(20),
status_id int,
trial_id int,
discovered_date date,
reporded_date date,
confirmed_date date
);
Site table:
create table site (
id long,
site varchar(20)
);
My First try:
Here is my new SQLFIDDLE with query needed to improve commented lines:
SELECT v.country as country, v.site as site,
COUNT(*) as N --,
--MAX(list of durations in days between discovered date to repored date on each violation by country and site) as "Maximum",
--MIN(list of durations in days between discovered date to repored date on each violation by country and site) as "Minimum",
--AVG(list of durations in days between discovered date to repored date on each violation by country and site) as "Mean"
FROM violations v
WHERE v.trial_id = 3
GROUP BY ROLLUP (v.country, v.site)
I've managed to create abstract query with my idea. But I have a problem to write correct query for MAX, MIN and AVG where we must select max/min/avg value from list of durations in days between discovered date to reported date on each violation by country and site.
Could you help me please?
Please check this query. It is simplified and may give you an idea and direction. If you need more then this then let me know. Copy and paste to see results. This query will select and calc only the results between two dates in where clause. You need to run inner query first w/out where to see all dates etc... This query counts violations between 2 dates. Not sure what is the list of duration in days... See below for count of duration. You may add MAX/MIN etc...
-- Days between (duration) = (end_date-start_date) = number of days (number) --
SELECT (to_date('14-MAR-2013') - to_date('01-MAR-2013')) days_between
FROM dual
/
SELECT country, site
, Count(*) total_viol
, MAX(susp_viol) max_susp_viol
, MIN(susp_viol) min_susp_viol
FROM
(
SELECT 'GERMANY' country, '12222' site, 1 susp_viol, 2 conf_viol, trunc(Sysdate-30) disc_date, trunc(Sysdate-25) conf_date
FROM dual
UNION
SELECT 'GERMANY', '12222' , 3 , 14, trunc(Sysdate-20) , trunc(Sysdate-15) FROM dual
UNION
SELECT 'GERMANY', '12222' , 6 , 25, trunc(Sysdate-20) , trunc(Sysdate-15) FROM dual
UNION
SELECT 'GERMANY', '12222' , 2 , 1, trunc(Sysdate-20) , trunc(Sysdate-15) FROM dual
UNION
SELECT 'GERMANY', '13333' , 10 , 5, trunc(Sysdate-15) , trunc(Sysdate-10) FROM dual
UNION
SELECT 'GERMANY', '13333' , 15 , 3, trunc(Sysdate-15) , trunc(Sysdate-10) FROM dual
UNION
SELECT 'GERMANY', 'Unknown Site' , 0 , 7, trunc(Sysdate-5) , trunc(Sysdate-2) FROM dual
UNION
SELECT 'RUSSIA', '12345' , 1 , 5, trunc(Sysdate-20) , trunc(Sysdate-15) FROM dual
UNION
SELECT 'RUSSIA', '12345' , 2 , 10, trunc(Sysdate-15) , trunc(Sysdate-12) FROM dual
UNION
SELECT 'RUSSIA', 'Unknown Site' , 10 , 10, trunc(Sysdate-3) , trunc(Sysdate-1) FROM dual
)
-- replace sysdate with your_date-default format is to_date('14-MAR-2013') or give format mask
WHERE conf_date BETWEEN trunc(Sysdate-20) AND trunc(Sysdate-10)
GROUP BY ROLLUP (country, site)
ORDER BY country, site
/
Count of duration:
SELECT country, site, (conf_date-disc_date) duration, count(*) total_durations
FROM
(
SELECT 'GERMANY' country, '12222' site, 1 susp_viol, 2 conf_viol, trunc(Sysdate-30) disc_date, trunc(Sysdate-20) conf_date
FROM dual
UNION
SELECT 'GERMANY', '12222' , 3 , 14, trunc(Sysdate-20) , trunc(Sysdate-12) FROM dual
UNION
SELECT 'GERMANY', '12222' , 6 , 25, trunc(Sysdate-20) , trunc(Sysdate-12) FROM dual
UNION
SELECT 'GERMANY', '12222' , 2 , 1, trunc(Sysdate-20) , trunc(Sysdate-12) FROM dual
UNION
SELECT 'GERMANY', '13333' , 10 , 5, trunc(Sysdate-12) , trunc(Sysdate-6) FROM dual
UNION
SELECT 'GERMANY', '13333' , 15 , 3, trunc(Sysdate-17) , trunc(Sysdate-11) FROM dual
UNION
SELECT 'GERMANY', 'Unknown Site' , 0 , 7, trunc(Sysdate-5) , trunc(Sysdate-2) FROM dual
UNION
SELECT 'RUSSIA', '12345' , 1 , 5, trunc(Sysdate-20) , trunc(Sysdate-15) FROM dual
UNION
SELECT 'RUSSIA', '12345' , 2 , 10, trunc(Sysdate-15) , trunc(Sysdate-12) FROM dual
UNION
SELECT 'RUSSIA', 'Unknown Site' , 10 , 10, trunc(Sysdate-3) , trunc(Sysdate-1) FROM dual
)
WHERE conf_date BETWEEN trunc(Sysdate-20) AND trunc(Sysdate-10)
GROUP BY ROLLUP (country, site, (conf_date-disc_date))
ORDER BY country, site
/