Math Formula Into Oracle Function - oracle

I have this Maths formula which i tried to convert into Oracle function.
This is how it must work, taxableIncome loops through the table,it picks the first line on the table and if it is more than CumulativeAmount,it subtracts CumulativeAmount from taxableIncome and keeps the remainder,it multiplies the CumulativeAmount and Percentage to get a value(x),takes the remainder and go next line in the table and subtract the CumulativeAmount from the remainder.when the remainder is less than the CumulativeAmount,it will apply the 17.5 on the remainder.Later I will sum the (x) values to get a total value. The values are not calculating properly.
CREATE OR REPLACE function CalculateIncomeTax(periodId NVARCHAR2,
employeeId NVARCHAR2,paygroupcode PAYROLL_MASTER.PAY_PAY_GROUP_CODE%type,
taxableIncome NUMBER)
return NUMBER
AS
IncomeTax NUMBER (10,2);
BEGIN
SELECT
coalesce(SUM(
CASE WHEN (taxableIncome > T.TAX_CUMMULATIVE_AMOUNT) THEN (taxableIncome -
T.TAX_CUMMULATIVE_AMOUNT)* (T.TAX_PERCENTAGE/ 100)
ELSE 0.00 END
) ,0)
INTO IncomeTax
FROM TAX_LAW T JOIN PAY_GROUP P ON P.PAY_FORMULA_ID =T.TAX_FORMULA_ID
WHERE P.PAY_CODE = paygroupcode;
return IncomeTax;
end;
/
For example:
taxableIncome= 1000,the function must loop on the first line on the
table CummulativeAmount=261,so 1000-261=739, and 0*739=0,0 is
value(x),
next loop 739>70,739 is the remainder,so 739-70=669, and (5/100)*70
=3.5,3.5 is value(x),
next loop 669>100,669-100=569, (10/100)*100=10,10 is value(x),
now 569 < 2810, so (17.5/100)*569=99.575,99.575 is value(x).
so we sum all x =0+3.5+10+99.575= 113.075
if taxableIncome = 300,300 is greater than 261 in the table so we will use the percentage of 0 on it,but we will get 0.now (300-261) is less than 70 so the percentage of 5 cannot be used on it so the Incometax is 0.But 17.5 will be applied on (300-261) to get 6.825,Total IncomeTax is 6.825.This is the logic behind the calculations.I want to used while statement on calculation it.

CREATE OR REPLACE function CalculateIncomeTax2(taxableIncome NUMBER)
return NUMBER
AS
IncomeTax NUMBER (10,2) := 0;
TaxableRemainder NUMBER := taxableIncome;
BEGIN
for r in (select tax_id, tax_percentage, tax_cummulative_amount
from incometax
order by tax_id)
loop
if TaxableRemainder > r.tax_cummulative_amount then
TaxableRemainder := TaxableRemainder - r.tax_cummulative_amount;
incometax := incometax + (r.tax_cummulative_amount * r.tax_percentage / 100);
else
incometax := incometax + (TaxableRemainder * r.tax_percentage / 100);
exit;
end if;
end loop;
return IncomeTax;
end;
/

Related

How to update each row in a table Oracle PLSQL

How can I create a procedure to verify each row of a table and update a field according to the established statement?
I have 4 fields that I work with
SYS_UPDATE_PING
UPDATE_PING
PING_STATUS
TIME_OUT_PING
Here is my code:
create or replace procedure SP_DASHBOARD_PINGSTATUS is
begin
declare
sp_ping_final number;
BEGIN
Update rsmes.tb_op_pc_monitoring_v4 t
set t.sys_update_ping = sysdate;
for c in (select ((SYS_UPDATE_PING - UPDATE_PING)*60*60*24) as PING_RESULT into sp_ping_final from TB_OP_PC_MONITORING_V4)
loop
sp_ping_final := c.ping_result;
if c.ping_result <= 5 then
Update rsmes.tb_op_pc_monitoring_v4 tg
set tg.ping_status = 'OK',
tg.time_out_ping = sp_ping_final;
else
Update rsmes.tb_op_pc_monitoring_v4 tn
set tn.ping_status = 'NG',
tn.time_out_ping = sp_ping_final;
end if;
end loop;
commit;
END;
end SP_DASHBOARD_PINGSTATUS;
I make a time difference between SYS_UPDATE_PING and UPDATE_PING, if the result in seconds is less than 5 it must update the PING_STATUS field to OK and put the seconds difference in TIME_OUT_PING, otherwise it will update NG and put the seconds difference in TIME_OUT_PING,
I want it to compare on each row but it updates me on all fields instead of one by one.
I know I'm forgetting something but could you help me find my fault and know the solution?
Thank you
That's because UPDATE statements miss the WHERE clause. Without it, you're always updating all rows in the table.
It means that cursor should contain some kind of an ID, which you'll the reuse in UPDATE. For example:
for c in (select id, --> this
((sys_update_ping - ...)
) loop
...
update tb_op_pc_monitoring_v4 tg set
tg.ping_status = 'OK'
where tg.id = c.id; --> this
...
end loop;
Also, you don't SELECT INTO in cursor. Remove the sp_ping_final entirely.
Finally, I'd say that you don't need PL/SQL (loop especially) at all. The whole code you wrote can be rewritten into a single
UPDATE tb_op_pc_monitoring_v4 tg
SET tg.ping_status =
CASE
WHEN (SYSDATE - tg.update_ping) * 60 * 60 * 24 <= 5 THEN 'OK'
ELSE 'NG'
END,
tg.time_out_ping = (SYSDATE - tg.update_ping) * 60 * 60 * 24;
which should work way faster than your row-by-row processing.

Which REGEXP to use

I would suppose this can be best done with regexp, as that's probably the shortest way, but anything else as a suggestion is also good.
Say, I have a string in PL/SQL, and want to capitalize every Nth character if its small, and lower if its capital. For example, every fifth letter.
I'd want to examine the possibilities to achieve that.
Thanks.
Since I have nothing better to do on a Saturday night (after Dark Matter of course) I decided to challenge my brain a bit. So after learning that SQLFiddle is broken, I installed Oracle 11g R2 Express Edition on my computer to work this out... yeah, I'm really that bored.
Directly from a table:
drop table test;
create table test (nbr number, txt varChar2(26));
insert into test (nbr, txt) values (1,'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
insert into test (nbr, txt) values (2,'abcdefghijklmnopqrstuvwxyz');
insert into test (nbr, txt) values (3,'ABCDEFGHIJklmnopqrstUVWXYZ');
insert into test (nbr, txt) values (4,'abcdefghijKLMNOPQRSTuvwxyz');
select
nbr,
(
select
listAgg
(
case subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1)
when upper(subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1))
then subStr(subStr(t.txt,((level - 1) * 5) + 1,5),1,4) ||
lower(subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1))
when lower(subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1))
then subStr(subStr(t.txt,((level - 1) * 5) + 1,5),1,4) ||
upper(subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1))
else subStr(t.txt,((level - 1) * 5) + 1,5)
end
)
within group (order by level)
from
dual
connect by
subStr(t.txt,((level - 1) * 5) + 1,5) is not null
) as "newTxt"
from
test t
;
Or you can use a PL/SQL function:
declare
function inverseCase(
txt varChar2,
nbr number
)
return varChar2 as
newTxt varChar2(254);
txtSeg varChar2(254);
segA varChar2(254);
segB varChar2(254);
begin
for i in 0..floor(length(txt)/nbr) loop
txtSeg := subStr(txt, (i * nbr) + 1, nbr);
segA := subStr(txtSeg, 1, nbr - 1);
segB := subStr(txtSeg, nbr, 1);
newTxt := newTxt || case segB
when upper(segB) then segA || lower(segB)
when lower(segB) then segA || upper(segB)
else txtSeg
end;
end loop;
return newTxt;
end;
begin
dbms_output.put_line(inverseCase('ABCDEFGHIJKLMNOPQRSTUVWXYZ',5));
dbms_output.put_line(inverseCase('abcdefghijklmnopqrstuvwxyz',5));
dbms_output.put_line(inverseCase('ABCDEFGHIJklmnopqrstUVWXYZ',5));
dbms_output.put_line(inverseCase('abcdefghijKLMNOPQRSTuvwxyz',5));
end;
Both return the following output.
nbr newTxt
1 ABCDeFGHIjKLMNoPQRStUVWXyZ
2 abcdEfghiJklmnOpqrsTuvwxYz
3 ABCDeFGHIjklmnOpqrsTUVWXyZ
4 abcdEfghiJKLMNoPQRStuvwxYz
Which I believe to be the text you are looking for.
It's been a while and I forgot how much fun this website is when I'm bored!
As one of options, you can check 5-th character using CASE statement
SELECT ( case
when SUBSTR(fieldname, 5,1) = upper (SUBSTR(fieldname, 5,1)) then lower (SUBSTR(fieldname, 5,1))
when SUBSTR(fieldname, 5,1) = lower (SUBSTR(fieldname, 5,1)) then upper (SUBSTR(fieldname, 5,1))
else SUBSTR(fieldname, 5,1) ) 5char
FROM YourTable
The problem will occur afterwards as you'll have to divide string on 3, change letter case and concatenate them back. This will be bulky. Or play around with REPLACE function, but again, as far as I know it does not work with particular character position, only with substrings by pattern, thus you will end up with dividing and concatenating again.
Regexp probably least painfull
That is if we limit transformation to be in SELECT statement only, no PL/SQL procedures

Using the data from a for loop as a table in the From in a Select

I am converting some code in Access over to Oracle, and one of the queries in Access uses a table that I am unable to use in Oracle. I am unable to create new tables, so I am trying to figure out a way to use the logic behind the table in the FOR section of my select.
The logic of the table is similar to:
FOR i = 1 To 100
number = number + 1
.AddNew
!tbl_number = number
NEXT i
I'm trying to convert this to oracle, and so far I have:
FOR i in 1 .. 100 LOOP
number := number + 1;
--This is where I am stuck; How do I simulate the table part
END LOOP;
I was thinking a cursor or a record would be the answer, but I can't seem to figure out how to implement that. In the end I basically want to have:
SELECT
table.number
FROM
(
--My for loop logic
) table
EDIT
The calculation is a bit more complicated; that was just an example. They aren't actually sequential, and there isn't really a pattern to rows.
EDIT
Here is a more complicated version of the for loop which is closer to what I'm actually doing:
FOR i in 1 .. 100 LOOP
number1 := number1 + 7;
number2 := (number2 + 8) / number1;
--This is where I am stuck; How do I simulate the table part
END LOOP;
You could use a recursive query (assuming you are on Oralce 11gR2 or later):
with example(idx, number1, number2) as (
-- Anchor Section
select 1
, 1 -- initial value
, 2 -- initial value
from dual
union all
-- Recursive Section
select prev.idx + 1
, prev.number1 + 7
, (prev.number2 + 8) / prev.number1
from example prev
where prev.idx < 100 -- The Guard
)
select * from example;
In the Anchor section set all the values for your first record. Then in the Recursive section setup the logic to determine the next records values as a function of the prior records values.
The Anchor section could select the initial values from some other table rather than being hard coded as in my example.
The recursive section needs to select from the named subquery (in this case example) but may also join to other tables as needed.
You need to generate a set with sequential integer numbers. Maybe you can use this (for Oracle 10g and above):
SELECT
ROWNUM NUM
FROM
DUAL D1,
DUAL D2
CONNECT BY
(D1.DUMMY = D2.DUMMY AND ROWNUM <= 100)

Oracle Function .How to compare previous value with Current value

My Source Data
ename Age
BAL N
BAL Y
BAL Y
YUV N
YUV Y
NAR N
Logic
if ( (ename <> Previous_Ename or Previous_AGE='N' ) then AGE = as is
Else AGE='Y'
Could you please let me know how to code this using Oracle funcaiton? i tried but in all case it not showing the desired result set.
i used
create or replace function () RETURN
VARCHAR2
IS
previous_name VARCHAR2 (9) := 'DUMMY';
previous_age VARCHAR2 (9) := 'Z';
BEGIN
For cur_rec in (select ename, age from tablename order by ename) LOOP
if ( cur_rec.ename <> previous_ename or previous_age ='N')
then return cur_rec.age; /** it is populating the result set with only "N"***/
else return 'Y';
end if;
previous_ename :=ename; /*** not sure whether this assignment is correct- im trying to assignt current value as previous value for next row reference.****/
previous_age :=age; /*** not sure whether this assignment is correct****/
END LOOP;
END
REsult im getting:- actually the result should be same as Source for this data scenerio
ename Age
BAL N
BAL N
BAL N
YUV N
YUV N
NAR N
I still don't understand what you're trying to achieve here (at all)... or why
The problem is that you're trying to to this in a function but you're returning the something almost immediately. By your order logic (in the comments) the first value will always be N because that's the first value in the ORDER BY. For every record in your table this will be true.
Use a MERGE statement instead:
merge into tmp n
using ( select rowid as rid
, ename
, age
, lag(age) over ( partition by ename order by age ) as lag_age
from tmp
) o
on ( n.rowid = o.rid )
when matched then
update
set n.age = case when lag_age is null then age
when lag_age = 'N' then age
else 'Y'
end
;
SQL Fiddle

Sybase CURSOR UPDATE extremeley slow / locking up

I have a temp table with 13,000 rows. Most of the rows have a numeric price (100) but some are quoted in 32nds, i.e. 100-30 = 100 + 30/32 = 100.9375. (Some even have fractions of a 32th)
I am opening a cursor FOR UPDATE and iterating over the temp table. It takes so long to execute, I am not even sure it is working (my DBA says the exec plan looks 'strange')
Can anyone suggest why this is so ridiculously slow?
Better still, could anyone suggest a better alternative? I have control over how the temp table is created, but I don't fancy trying to compact the logic for 100-30 to 100.9375 into a single update statement.
I'd like to write a function to do this, but as far as I can tell, I have to install Java to enable UDFs?!
Lastly, any idea why Sybase is such an awful, primitive database even at version 12?
My stored proc:
DECLARE cur CURSOR FOR SELECT ticket_no, price_st, price, cur FROM #t FOR UPDATE OF price
DECLARE
#ticket_no INT,
#price_st VARCHAR(20),
#price FLOAT,
#int FLOAT,
#32s FLOAT,
#frac VARCHAR(6),
#num FLOAT,
#denom FLOAT
OPEN cur
FETCH cur INTO #ticket_no, #price_st
WHILE (##SQLSTATUS != 2)
BEGIN
IF isnumeric(#price_st) = 1
BEGIN
SELECT #price = convert(FLOAT, #price_st)
END
ELSE
BEGIN
-- Convert a price like '99-22 3/4' to
-- 99 + (22/32) + (3/4 * 1/32)
SELECT #int = convert(FLOAT, substring(#price_st, 1, charindex('-', #price_st)-1))
SELECT #32s = convert(FLOAT, substring(#price_st, charindex('-', #price_st)+1, 2))
SELECT #frac = substring(#price_st, charindex(' ', #price_st)+1, 10)
SELECT #num = convert(FLOAT, substring(#frac, 1, charindex('/', #frac)-1))
SELECT #denom = convert(FLOAT, substring(#frac, charindex('/', #frac)+1, 3))
SELECT #price = #int + (#32s / 32) + (#num / (#denom * 32))
END
UPDATE #t SET price = #price WHERE CURRENT OF cur
FETCH cur INTO #ticket_no, #price_st
END
CLOSE cur
DEALLOCATE cur
Ah! I had a column in the table, with the same name as the cursor:
DECLARE cur CURSOR FOR SELECT ticket_no, price_st, price, cur FROM #t FOR UPDATE OF price
^ ^
This seems to put the Sybase server into a tailspin...
Cursors in Sybase are slower than SET based operations. Despite your reluctance I doubt you will do better than putting all the logic into one Update statement as below.
UPDATE #t
SET price =
CASE
WHEN isnumeric(price_st) = 1
THEN CONVERT(FLOAT, price_st)
ELSE
CONVERT(FLOAT, substring(price_st, 1, charindex('-', price_st)-1)) +
(CONVERT(FLOAT, substring(price_st, charindex('-', price_st)+1, 2)) / 32) +
(CONVERT(FLOAT, substring(substring(price_st, charindex(' ', price_st)+1, 10), 1, charindex('/', substring(price_st, charindex(' ', price_st)+1, 10))-1)) /
(CONVERT(FLOAT, substring(substring(price_st, charindex(' ', price_st)+1, 10), charindex('/', substring(price_st, charindex(' ', price_st)+1, 10))+1, 3)) * 32))
END

Resources