I need help with PVA calculation in PL/SQL .I have formula:
Annuity = r * PVA Ordinary / [1 – (1 + r)-n]
Where:
PVA Ordinary = Present value of an ordinary annuity
r = Effective interest rate
n = Number of periods.
enter image description here
You can use:
DECLARE
principal NUMBER := 9000;
r NUMBER := 0.015;
n NUMBER := 5;
start_dt DATE := DATE '2022-07-14';
payment NUMBER := r * principal / (1 - POWER(1 + r, -n));
amt NUMBER := principal;
interest NUMBER;
pmt_dt DATE;
BEGIN
FOR i IN 1 .. n LOOP
pmt_dt := ADD_MONTHS(start_dt, i);
pmt_dt := pmt_dt + CASE pmt_dt - TRUNC(pmt_dt, 'IW')
WHEN 5 THEN 2 -- Saturday
WHEN 6 THEN 1 -- Sunday
ELSE 0 -- Weekday
END;
interest := amt * r;
amt := amt - payment + interest;
DBMS_OUTPUT.PUT_LINE(
TO_CHAR(i, 'fm0')
|| ', ' || TO_CHAR(pmt_dt, 'YYYY-MM-DD (DY)')
|| ', ' || TO_CHAR(payment, '9990.00')
|| ', ' || TO_CHAR(interest, '990.00')
|| ', ' || TO_CHAR(payment - interest, '9990.00')
|| ', ' || TO_CHAR(amt, '9990.00')
);
END LOOP;
END;
/
Which outputs:
1, 2022-08-15 (MON), 1881.80, 135.00, 1746.80, 7253.20
2, 2022-09-14 (WED), 1881.80, 108.80, 1773.01, 5480.19
3, 2022-10-14 (FRI), 1881.80, 82.20, 1799.60, 3680.59
4, 2022-11-14 (MON), 1881.80, 55.21, 1826.60, 1853.99
5, 2022-12-14 (WED), 1881.80, 27.81, 1853.99, -0.00
Or, in SQL using a MODEL clause:
WITH data (id, start_date, principal, rate, period ) AS (
SELECT 1, DATE '2022-07-14', 9000, 0.015, 5 FROM DUAL
)
SELECT pmt_dt + CASE pmt_dt - TRUNC(pmt_dt, 'IW')
WHEN 5 THEN 2
WHEN 6 THEN 1
ELSE 0
END AS pmt_dt,
ROUND(payment, 2) AS payment,
ROUND(interest, 2) AS interest,
ROUND(payment - interest, 2) AS reduction,
ROUND(balance, 2) AS balance
FROM data
MODEL
PARTITION BY (id)
DIMENSION BY (1 AS key)
MEASURES (
start_date,
principal,
rate,
period,
DATE '1900-01-01' AS pmt_dt,
rate * principal / (1 - POWER(1 + rate, -period)) AS payment,
0 AS interest,
0 AS balance
)
RULES SEQUENTIAL ORDER ITERATE (100) UNTIL (balance[ITERATION_NUMBER+1] <= 0) (
payment[ITERATION_NUMBER+1] = payment[1],
pmt_dt[ITERATION_NUMBER+1] = ADD_MONTHS(start_date[1], ITERATION_NUMBER+1),
interest[ITERATION_NUMBER + 1]
= COALESCE(balance[ITERATION_NUMBER],principal[1]) * rate[1],
balance[ITERATION_NUMBER+1]
= COALESCE(balance[ITERATION_NUMBER],principal[1])
- payment[1] + interest[ITERATION_NUMBER+1]
)
ORDER BY id, key
Which outputs:
PMT_DT
PAYMENT
INTEREST
REDUCTION
BALANCE
15-AUG-22
1881.8
135
1746.8
7253.2
14-SEP-22
1881.8
108.8
1773.01
5480.19
14-OCT-22
1881.8
82.2
1799.6
3680.59
14-NOV-22
1881.8
55.21
1826.6
1853.99
14-DEC-22
1881.8
27.81
1853.99
0
db<>fiddle here
Related
I am trying to create a function to return lowest fraction value. the sample code is here :
create or replace function fraction_sh(x number) return varchar2
is
fra1 number;
pwr number;
intprt number;
v4 number;
numer number;
denom number;
gcdval number;
frac varchar2(50);
begin
if x <> 0 then
fra1 := mod(x,1);
pwr := length(mod(x,1))-1;
intprt := trunc(x);
numer :=mod(x,1)*power(10,length(mod(x,1))-1);
denom :=power(10,length(mod(x,1))-1);
gcdval := gcdnew(power(10,length(mod(x,1))-1),mod(x,1)*power(10,length(mod(x,1))-1));
if intprt = 0 then
frac := to_char(trunc(numer/gcdval))||'/'||to_char(trunc(denom/gcdval));
DBMS_OUTPUT.put_line(1||' '||denom||' '||gcdval||' '||numer);
else
frac := (intprt*to_char(trunc(denom/gcdval)))+to_char(trunc(numer/gcdval))||'/'||to_char(trunc(denom/gcdval));
DBMS_OUTPUT.put_line(2||' '||denom||' '||gcdval||' '||numer);
end if;
end if;
return frac;
end;
create or replace function gcdnew (a number, b number, p_precision number default null, orig_larger_num number default null) return number is
v_orig_larger_num number := greatest(nvl(orig_larger_num,-1),a,b);
v_precision_level number := p_precision;
begin
if a is null or b is null or (a = 0 and b = 0) then return 1; end if;
if p_precision is null or p_precision <= 0 then
v_precision_level := 4;
end if;
if b is null or b = 0 or (b/v_orig_larger_num <= power(10,-1*v_precision_level) and greatest(a,b) <> v_orig_larger_num) then
return a;
else
return (gcdnew(b,mod(a,b),v_precision_level,v_orig_larger_num));
end if;
end;
Inmost cases it works, but when i try to pass 2/11 it returns 2/10.
Any help appreciated.
The problem with what you're currently doing is precision. With 2/11 the resulting number is 0.1818181... recurring, and the length of that - and therefore the pwr value - end up as 40, which destroys the later calculations.
With modifications to limit the precision (and tidied up a bit, largely to remove repeated calculations when you have handy variables already):
create or replace function fraction_sh(p_float number) return varchar2
is
l_precision pls_integer := 10;
l_int_part pls_integer;
l_frac_part number;
l_power pls_integer;
l_numer number;
l_denom number;
l_gcdval number;
l_result varchar2(99);
begin
if p_float is null or p_float = 0 then
return null;
end if;
l_int_part := trunc(p_float);
l_frac_part := round(mod(p_float, 1), l_precision);
l_power := length(l_frac_part);
l_denom := power(10, l_power);
l_numer := l_frac_part * l_denom;
l_gcdval := gcdnew(l_denom, l_numer, ceil(l_precision/2));
if l_int_part = 0 then
l_result := trunc(l_numer/l_gcdval) ||'/'|| trunc(l_denom/l_gcdval);
else
l_result := l_int_part * (trunc(l_denom/l_gcdval) + trunc(l_numer/l_gcdval))
||'/'|| trunc(l_denom/l_gcdval);
end if;
return l_result;
end;
/
Which gets:
with t(n) as (
select 9/12 from dual
union all select 2/11 from dual
union all select 1/2 from dual
union all select 1/3 from dual
union all select 1/4 from dual
union all select 1/5 from dual
union all select 1/6 from dual
union all select 1/7 from dual
union all select 1/8 from dual
union all select 1/9 from dual
union all select 1/10 from dual
union all select 4/3 from dual
union all select 0 from dual
union all select 1 from dual
)
select n, fraction_sh(n) as fraction
from t;
N FRACTION
---------- ------------------------------
.75 3/4
.181818182 2/11
.5 1/2
.333333333 1/3
.25 1/4
.2 1/5
.166666667 1/6
.142857143 1/7
.125 1/8
.111111111 1/9
.1 1/10
1.33333333 4/3
0
1 1/1
So you might want to add some handling for either passing in 1, or the approximation after rounding ending up as 1/1 - presumably just to return a plain '1' in either case.
I've set l_precision to 10 rather arbitrarily, you can make that larger, but will hit problems at some point so test carefully with whatever value you pick.
(And I haven't looked at gdcnew at all; that can probably be simplified a bit too.)
you can use like this:
create or replace function fraction_sh(dividing number,divided number) return varchar2
is
dividing2 number;
divided2 number;
frac varchar2(100 char);
temp number;
loop_value boolean;
begin
loop_value:=true;
dividing2:=dividing;
divided2 :=divided;
if dividing <> 0 then
while loop_value
loop
if gcd(dividing2,divided2)<> 1 then
temp:=gcd(dividing2,divided2);
dividing2:=dividing2/temp;
divided2 :=divided2/temp;
frac:=dividing2||'/'||divided2;
else
loop_value:=false;
frac:=dividing2||'/'||divided2;
end if;
end loop;
else
frac:='0';
end if;
return frac;
end;
gcd func:
create or replace function gcd(a number, b number)
return number is
begin
if b = 0 then
return a;
else
return gcd(b,mod(a,b));
end if;
end;
I am trying to write an Oracle PL/SQL procedure to calculate a simple confusion matrix table. I have my labelled data prepared, basically two columns of 0 and 1, actual value vs predicted value.
I was able to calculate it with simple pivot (I think most straightforward option):
SELECT * FROM
( SELECT ACTUAL_VALUE, PREDICTED_VALUE
FROM MY_TABLE
)
PIVOT (
COUNT(PREDICTED_VALUE)
FOR PREDICTED_VALUE IN (1, 0))
ORDER BY ACTUAL_VALUE;
Now I am trying to "plug" all this into a DECLARE, BEGIN... framework but no success. Is it even possible to create procedure to calculate this pivot?
Thanks in advance for any suggestion!
I had to go with cursors and loops. And got my calculations working this way. So closing this one.Plus 3 more summing up calculations at the end.
Thank you
CREATE OR REPLACE PROCEDURE "mydb"."C_MATRIX"
IS
CURSOR MY_DATA IS
SELECT ACTUAL_CARD, PREDICTED_VALUE FROM my_table;
my_data_rec my_data%rowtype;
fp pls_integer := 0;
tp pls_integer := 0;
fn pls_integer := 0;
tn pls_integer := 0;
BEGIN
OPEN my_data;
LOOP
FETCH my_data INTO my_data_rec;
EXIT WHEN my_data%notfound;
IF my_data_rec.ACTUAL_CARD = 0 and my_data_rec.PREDICTED_VALUE = 0 then
tn := tn + 1;
elsif my_data_rec.ACTUAL_CARD = 0 and my_data_rec.PREDICTED_VALUE = 1 then
fn := fn + 1;
elsif my_data_rec.ACTUAL_CARD = 1 and my_data_rec.PREDICTED_VALUE = 1 then
tp := tp + 1;
elsif my_data_rec.ACTUAL_CARD = 1 and my_data_rec.PREDICTED_VALUE = 0 then
fp := fp + 1;
end if;
END LOOP;
dbms_output.put_line
('Number of false Positives: ' ||fp || ' Number of true Positives: ' ||tp || ' Total numbers of records: ' ||(fp + tp) );
dbms_output.put_line
('Number of false Negatives: ' ||fn || ' Number of true Negatives: ' ||tn || ' Total numbers of records: ' ||(fn + tn ));
dbms_output.put_line(' ');
dbms_output.put_line
(' Incorrect predictions: ' || (fp + fn) || ' Correct predictions: ' || (tp + tn) );
dbms_output.put_line(' ');
dbms_output.put_line(' Acurracy: ' || round((((tn + tp) / (tn + tp + fn + fp)) * 100),2) || '%');
dbms_output.put_line(' Precision: ' || round(((tp / (tp + fp)) * 100),2) || '%');
dbms_output.put_line(' Recall: ' || round(((tp / (tp + fn)) * 100),2) || '%');
END;
/
SELECT /*+first_rows */ PPS_ID,TOTAL_WEIGHT from
(SELECT PPS_ID,TOTAL_WEIGHT ,row_number() over (order by total_weight desc) row_num
FROM (SELECT pps_id,round((((60 * name_pct_match / 100) + prs_weight + year_weight + dt_weight +
case
when mother_name_pct_match = -1
then
0
else
(10 * mother_name_pct_match / 100)
end)/decode(mother_name_pct_match,-1,(total_attrib_weight - mother_weight),total_attrib_weight)) * 100) total_weight
FROM (SELECT pps_id,
round(func_compare_name('MUHAMMAD YASIN MUHAMMAD ASHRAF',upper(name_en),' ',60)) name_pct_match,
decode(prs_nationality_id, 271, 15, 0) prs_weight,
case
when upper(mother_name_en) in ('MR','MRS','MISS','NISA','M','X')
then -1
else
round(func_compare_name(upper('.'), upper(mother_name_en), ' ',60))
end mother_name_pct_match, 10 mother_weight,
100 total_attrib_weight,
case when to_number(to_char(birth_date, 'yyyy')) = 2007 then 5 else 0 end year_weight,
case when to_char(to_date('01/01/2007','DD-MM-RRRR'), 'dd') = to_char(birth_date, 'dd')
and to_char(to_date('01/01/07','DD-MM-RRRR'), 'mm') = to_char(birth_date, 'mm') then 10
when to_date('01/01/2007','DD-MM-RRRR') between birth_date-6 and birth_date+6 then 8
when to_date('01/01/2007','DD-MM-RRRR') between birth_date-28 and birth_date+28 then 5
when to_date('01/01/2007','DD-MM-RRRR') between birth_date-90 and birth_date+90 then 3
else 0
end dt_weight
FROM INDIV_PROF_sub_test123
WHERE birth_date = '01/01/2007'
AND IS_ACTIVE = 1
AND gender_id = 1
AND round(func_compare_name('MUHAMMAD YASIN MUHAMMAD ASHRAF',upper(name_en),' ',60)) > 20
)
)
WHERE TOTAL_WEIGHT >= 70
)
where row_num <= 10
CREATE OR REPLACE FUNCTION VISION_APP.func_compare_name(p_name_str IN VARCHAR2,
p_str IN VARCHAR2,
p_delim IN VARCHAR2,
p_relev_thresh IN NUMBER)
RETURN NUMBER DETERMINISTIC AS
l_str LONG;
l_n NUMBER;
TYPE mytabletype IS TABLE OF VARCHAR2(255);
l_name_data mytabletype := mytabletype();
l_data mytabletype := mytabletype();
v_name_cnt NUMBER := 0;
v_pct_per_name NUMBER := 0;
v_pct_match NUMBER := 0;
v_flag NUMBER;
v_jaro_pct NUMBER := 0;
v_highest_jaro_pct NUMBER := 0;
v_match_exact NUMBER := 0;
v_match_exact_res NUMBER := 0;
BEGIN
l_str := p_name_str || p_delim;
LOOP
l_n := instr(l_str, p_delim);
EXIT WHEN(nvl(l_n, 0) = 0);
-- condition to check if only space
IF l_n <> 1 THEN
l_name_data.extend;
l_name_data(l_name_data.count) := ltrim(rtrim(substr(l_str,
1,
l_n - 1)));
v_name_cnt := v_name_cnt + 1;
END IF;
l_str := substr(l_str, l_n + length(p_delim));
END LOOP;
v_pct_per_name := 100 / v_name_cnt;
l_str := p_str || p_delim;
LOOP
l_n := instr(l_str, p_delim);
EXIT WHEN(nvl(l_n, 0) = 0);
l_data.extend;
l_data(l_data.count) := ltrim(rtrim(substr(l_str, 1, l_n - 1)));
l_str := substr(l_str, l_n + length(p_delim));
END LOOP;
FOR nme IN 1 .. l_name_data.count LOOP
v_flag := 0;
v_highest_jaro_pct := 0;
FOR i IN 1 .. l_data.count LOOP
v_jaro_pct := utl_match.jaro_winkler_similarity(l_name_data(nme),
l_data(i));
IF soundex(l_name_data(nme)) = soundex(l_data(i)) AND
v_jaro_pct >= p_relev_thresh THEN
IF v_jaro_pct > v_highest_jaro_pct THEN
v_highest_jaro_pct := v_jaro_pct;
END IF;
END IF;
END LOOP;
v_pct_match := v_pct_match +
(v_pct_per_name * v_highest_jaro_pct / 100);
END LOOP;
SELECT utl_match.edit_distance_similarity(p_name_str, p_str)
INTO v_match_exact
FROM dual;
if (trunc(v_match_exact) =100 ) then
return trunc(v_pct_match, 2);
else
if( v_match_exact <> 0) then
v_match_exact_res := (5 / v_match_exact) * 100;
v_pct_match := v_pct_match - v_match_exact_res;
end if;
if v_pct_match >20 then
RETURN trunc(v_pct_match, 2);
end if;
end if;
END;
This query is fetching result from INDIV_PROF_sub_test123 table this is having data of 35 million and partitioned.
i found the problematic area in query func_compare_name we are using it having name similarity checking function
Adding explain plan:
Plan
SELECT STATEMENT HINT: FIRST_ROWS
Cost: 262
Bytes: 4,056
Cardinality: 104 4
VIEW VC_CLONE.
Cost: 262
Bytes: 4,056
Cardinality: 104 3
WINDOW SORT PUSHED RANK
Cost: 262
Bytes: 5,512
Cardinality: 104 2
TABLE ACCESS BY GLOBAL INDEX ROWID TABLE VC_CLONE.INDIV_PROF_ONE_MONTH_1
Cost: 261
Bytes: 5,512
Cardinality: 104
Partition #: 3
Partitions accessed #1672 1
INDEX RANGE SCAN INDEX VC_CLONE.IDX_BIRTH_DT_INVID
Cost: 4
Cardinality: 1 –
Am having query,in which two fields and getting as output pps_id and total_weight. Here pps_id is the column from the table and total_weight we are calculating from inner query. after doing all process in query we are order by the query by total weight. Its taking more cost and response.Is there any way to improve this query performance.
SELECT PPS_ID, TOTAL_WEIGHT
FROM ( SELECT PPS_ID, TOTAL_WEIGHT
FROM (SELECT pps_id,
ROUND (
( ( (60 * name_pct_match / 100)
+ prs_weight
+ year_weight
+ dt_weight)
/ 90)
* 100)
total_weight
FROM (SELECT pps_id,
ROUND (func_compare_name ('aaaa',
UPPER (name_en),
' ',
60))
name_pct_match,
DECODE (prs_nationality_id, 99, 15, 0)
prs_weight,
10 mother_weight,
100 total_attrib_weight,
CASE
WHEN TO_NUMBER (
TO_CHAR (birth_date, 'yyyy')) =
1986
THEN
5
ELSE
0
END
year_weight,
CASE
WHEN TO_CHAR (
TO_DATE ('12-JAN-86',
'DD-MON-RRRR'),
'dd') =
TO_CHAR (birth_date, 'dd')
AND TO_CHAR (
TO_DATE ('12-JAN-86',
'DD-MON-RRRR'),
'mm') =
TO_CHAR (birth_date, 'mm')
THEN
10
WHEN TO_DATE ('12-JAN-86', 'DD-MON-RRRR') BETWEEN birth_date
- 6
AND birth_date
+ 6
THEN
8
WHEN TO_DATE ('12-JAN-86', 'DD-MON-RRRR') BETWEEN birth_date
- 28
AND birth_date
+ 28
THEN
5
WHEN TO_DATE ('12-JAN-86', 'DD-MON-RRRR') BETWEEN birth_date
- 90
AND birth_date
+ 90
THEN
3
ELSE
0
END
dt_weight
FROM individual_profile
WHERE birth_date = '12-JAN-86'
AND IS_ACTIVE = 1
AND gender_id = 1
AND ROUND (func_compare_name ('aaa',
UPPER (name_en),
' ',
60)) > 20))
WHERE TOTAL_WEIGHT >= 100
ORDER BY total_weight DESC)
WHERE ROWNUM <= 10
i have tried by splitting the query and put values in temp tables and tried but it also taking time. I want to improve the performance of the query
I don't know how to convert integer into percentage, please help me. Thank you
Here's the query:
SELECT 'Data' || ',' ||
TO_CHAR(D.DTIME_DAY,'MM/dd/yyyy') || ',' ||
NVL(o.CNT_OPENED,0) || ',' || --as cnt_opened
NVL(c.CNT_CLOSED,0) --as cnt_closed
FROM OWNER_DWH.DC_DATE d
LEFT JOIN (SELECT TRUNC(t.CREATE_TIME, 'MM') AS report_date,
count(*) AS cnt_opened
FROM APP_ACCOUNT.OTRS_TICKET t
WHERE t.CREATE_TIME BETWEEN SYSDATE -120 AND SYSDATE
GROUP BY TRUNC(t.CREATE_TIME, 'MM')) o
ON d.DTIME_DAY=o.REPORT_DATE
LEFT JOIN (SELECT TRUNC(t.CLOSE_TIME, 'MM') as report_date,
count(*) AS cnt_closed
FROM APP_ACCOUNT.OTRS_TICKET t
WHERE t.CLOSE_TIME BETWEEN SYSDATE -120 AND SYSDATE
GROUP BY TRUNC(t.CLOSE_TIME, 'MM')) c
ON D.DTIME_DAY=c.REPORT_DATE
WHERE d.DTIME_DAY BETWEEN SYSDATE -120 AND TRUNC(SYSDATE) -1 AND
d.DTIME_DAY = TRUNC(d.DTIME_DAY, 'MM') AND
TRUNC(d.DTIME_DAY,'MM')= d.DTIME_DAY
ORDER BY D.DTIME_DAY;
The output of that query:
Data,10/01/2013,219,201
Data,11/01/2013,249,234
Data,12/01/2013,228,224
Data,01/01/2014,269,256
example output that I need is like this:
Data,10/01/2013,219, 52%, 201, 45%
Data,11/01/2013,249, 75%, 234, 60%
.......
........
Formula:
create_time + close time = total / create_time (for cnt_opened each column) = percentage
create_time + close time = total / close_time (for cnt_closed each column) = percentage
Try this:
Basically just add the total of CNT_OPENED and CNT_CLOSED, then whichever you want to take the percentage of, multiply that by 100 and divide by the sum.
For instance, CNT_OPENED = 219 and CNT_CLOSED = 201 so the total is 420. Multiply CNT_OPENED by 100 and then divide by 420 -> (219 * 100) / 420 = 52. Do the same thing with CNT_CLOSED.
Note that this WILL result in an exception if both CNT_OPENED and CNT_CLOSED are 0.
SELECT 'Data'
||','||TO_CHAR(D.DTIME_DAY,'MM/dd/yyyy')
||','||NVL(o.CNT_OPENED,0) --as cnt_opened
||','||(NVL(o.CNT_OPENED,0) * 100) / (NVL(o.CNT_OPENED,0) + NVL(o.CNT_CLOSED,0)) || '%'
||','||NVL(c.CNT_CLOSED,0) --as cnt_closed
||','||(NVL(o.CNT_CLOSED,0) * 100) / (NVL(o.CNT_OPENED,0) + NVL(o.CNT_CLOSED,0)) || '%'
That will also potentially give you a million decimal places, so if you only want to take it out to a couple, simply use the TRUNC function and specify your precision (2 decimal places in this case):
TRUNC((NVL(o.CNT_OPENED,0) * 100) / (NVL(o.CNT_OPENED,0) + NVL(o.CNT_CLOSED,0)), 2)