Progressive LOOP Statement ORACLE - oracle

How to create a function that returns a float(ChargeTotal)?
ChargeTotal is based on a progressive table using number of batches.
num of batches | charge
----------------------------
1-10 | 0
11-20 | 50
21-30 | 60
31-40 | 70
40+ | 80
If number of batches is 25 then
num of batches | charge
----------------------------
1-10 | 0
11-20 | 50*10
21-30 | 60*5
----------------------------
total | 800 <number I need to be returned(ChargeTotal)
So far I have come up with the following, but I'm unsure how to get the total for each loop, or if it is even possible to do more than one FOR statements:
CREATE OR REPLACE FUNCTION ChargeTotal
RETURN FLOAT IS
total FLOAT;
BEGIN
FOR a in 1 .. 10 LOOP
FOR a in 11 .. 20 LOOP
FOR a in 21 .. 30 LOOP
FOR a in 40 .. 1000 LOOP
RETURN Total;
END ChargeTotal;

Ok so take into consideration that right now I have no DB available to test this (there might be some syntax errors etc).
But I am thinking something along this lines of code...
function ChargeTotal(xin number) return number is
cursor mycursor is
select lowLimit,highLimit,charge
from progressive_table order by lowLimit;
sum number;
segment number;
x number;
begin
sum:=0;
x :=xin;
for i in mycursor loop
segment := (i.highLimit-i.lowLimit)+1;
x := greatest ( x - segment,x);
sum := sum + segment*i.charge;
if (x<segment) then
return sum;
end if;
end loop;
return sum;
end;

I think you can do the calculation via single sql without complex function
the logic is:
you have weights for each "band"
calculate the "band" each row
count(*) over to calculate number of rows in each "band"
join your weight table to get sub.total for each band
use rollup to get grand total
sql
select r.num_of_batches
,sum(r.subtotal_charge)
from (
with weights as
(select 1 as num_of_batches, 0 as charge from dual
union all
select 2 as num_of_batches, 50 as charge from dual
union all
select 3 as num_of_batches, 60 as charge from dual
union all
select 4 as num_of_batches, 70 as charge from dual
union all
select 5 as num_of_batches, 80 as charge from dual
)
select distinct n.num_of_batches
, w.charge
, count(*) over (partition by n.num_of_batches) as cnt
, count(*) over (partition by n.num_of_batches) * charge as subtotal_charge
from (
select num, case when floor(num / 10) > 4 then 5 else floor(num / 10)+1 end as num_of_batches
from tst_brances b
) n
inner join weights w on n.num_of_batches = w.num_of_batches
order by num_of_batches
) r
group by ROLLUP(r.num_of_batches)
populate test data
create table tst_branches(num int);
declare
i int;
begin
delete from tst_brances;
for i in 1..10 loop
insert into tst_brances(num) values (i);
end loop;
for i in 11..20 loop
insert into tst_brances(num) values (i);
end loop;
for i in 21..25 loop
insert into tst_brances(num) values (i);
end loop;
for i in 31..32 loop
insert into tst_brances(num) values (i);
end loop;
for i in 41..43 loop
insert into tst_brances(num) values (i);
end loop;
for i in 51..55 loop
insert into tst_brances(num) values (i);
end loop;
commit;
end;
results
1 1 0
2 2 500
3 3 360
4 4 140
5 5 640
6 1640

Related

Display all records of a table using PL/SQL Procedure

There is a table named Student(Roll, Name, Sub1, Sub2, Sub3).
I want to write a procedure that will print Roll, Name and the average marks obtained by all the students. But I don't know how to make the procedure read the rows one by one and perform the operation on each row iteratively.
One option is to use nested loops (one for rolls, and one for students within that roll). Display data using dbms_output.put_line calls.
Sample table:
SQL> create table student (roll, name, sub1, sub2, sub3) as
2 select 1, 'Scott', 1, 2, 3 from dual union all
3 select 1, 'Tiger', 4, 5, 2 from dual union all
4 select 2, 'King' , 3, 4, 2 from dual;
Table created.
Read comments within code:
SQL> set serveroutput on
SQL>
SQL> declare
2 l_sum number; -- sum of all marks
3 l_cnt number; -- number of studnents per roll
4 l_avg number; -- average mark per roll
5 begin
6 for cur_r in (select distinct roll from student order by roll)
7 loop
8 dbms_output.put_line('Roll ' || cur_r.roll ||' ------------------------------');
9
10 -- initialize variables for each roll
11 l_sum := 0;
12 l_cnt := 0;
13 for cur_s in (select name, sub1 + sub2 + sub3 as sub
14 from student
15 where roll = cur_r.roll
16 order by name
17 )
18 loop
19 -- total sum of marks per all students within that roll
20 l_sum := l_sum + cur_s.sub;
21 l_cnt := l_cnt + 1;
22
23 -- This is one student and their average
24 dbms_output.put_line('Student: ' || cur_s.name ||', their average mark = '
25 || round(cur_s.sub / 3, 1));
26 end loop;
27 -- divide by 3 as there are 3 subjects per student
28 l_avg := round(l_sum / 3 / l_cnt, 1);
29
30 -- This is roll's average
31 dbms_output.put_line('Roll ' || cur_r.roll ||' average mark = '
32 || l_avg);
33 end loop;
34 end;
35 /
Result:
Roll 1 ------------------------------
Student: Scott, their average mark = 2
Student: Tiger, their average mark = 3.7
Roll 1 average mark = 2.8
Roll 2 ------------------------------
Student: King, their average mark = 3
Roll 2 average mark = 3
PL/SQL procedure successfully completed.
SQL>
Now that you know how, improve it and make it pretty.

Write PL/SQL code to generate Armstrong number from 1 to 500

I am getting output as a = 1. I have taken a for loop from 1 to 500 and while loops inside the outer for loop.
declare
n number;
s number:=0;
r number;
len number;
m number;
begin
for a in 1..500 loop
m:=a;
n:=a;
len:=length(to_char(n));
while(n>0) loop
r:=mod(n,10);
s:=s+power(r,len);
n:=trunc(n/10);
end loop;
if m=s then
dbms_output.put_line('a='||to_char(a)');
end if;
end loop;
end;
How about this?
substr splits i to 3 separate digits
nvl is here to avoid adding null value if those digits don't exist (yet)
power function calculates i's cube
display i if it is equal to r (as "result")
SQL> declare
2 r number;
3 begin
4 for i in 1 .. 500 loop
5 r := power(to_number(substr(to_char(i), 1, 1)), 3) +
6 nvl(power(to_number(substr(to_char(i), 2, 1)), 3), 0) +
7 nvl(power(to_number(substr(to_char(i), 3, 1)), 3), 0);
8
9 if r = i then
10 dbms_output.put_line(i);
11 end if;
12 end loop;
13 end;
14 /
1
153
370
371
407
PL/SQL procedure successfully completed.
SQL>
You never reset s and you do not need the final IF statement (unless you are only interested in the Armstrong numbers which equal the original number):
declare
n number;
s number:=0;
r number;
len number;
m number;
begin
for a in 1..500 loop
m:=a;
n:=a;
s:=0; -- Reset s for each loop
len:=length(to_char(n));
while(n>0) loop
r:=mod(n,10);
s:=s+power(r,len);
n:=trunc(n/10);
end loop;
dbms_output.put_line(a || '=' || s); -- Output values for every loop.
end loop;
end;
/
db<>fiddle here

Print the sum of even digits of a number in Pl/sql

I cant seem to resolve this issue
As of the error itself, it is because you're trying to put the whole row (temp, which is declared as a cursor variable - c_nm%rowtype) into an integer variable (i). It won't work, although temp actually contains only one column - num.
So, line #15 on your screenshot:
No : i := temp;
Yes: i := temp.num;
Once you fix it, your code works but - unfortunately, produces wrong result. In input number 121974553, even digits are 2, 9, 4 and 5 whose sum equals 20, not 6 (as your result suggests):
SQL> set serveroutput on;
SQL> declare
2 ad int := 0;
3 i int;
4 b int;
5 cursor c_nm is select * From numb;
6 temp c_nm%rowtype;
7 begin
8 open c_nm;
9 loop
10 fetch c_nm into temp;
11 exit when c_nm%notfound;
12 i := temp.num; --> this
13
14 while i > 0 loop
15 b := mod(i, 10);
16 if mod(b, 2) = 0 then
17 ad := b + ad;
18 end if;
19 i := trunc(i/10);
20 end loop;
21 end loop;
22 dbms_output.put_line('ad = ' ||ad);
23 close c_nm;
24 end;
25 /
ad = 6
PL/SQL procedure successfully completed.
SQL>
A simpler option might be this: split number into digits (each in its separate row) so that you could apply SUM function to its even digits:
SQL> select * from numb;
NUM
----------
121974553 --> sum of even digits = 2 + 9 + 4 + 5 = 20
253412648 --> = 5 + 4 + 2 + 4 = 15
SQL> with
2 split as
3 -- split number into rows
4 (select num,
5 substr(num, column_value, 1) digit,
6 case when mod(column_value, 2) = 0 then 'Y'
7 else 'N'
8 end cb_odd_even
9 from numb cross join table(cast(multiset(select level from dual
10 connect by level <= length(num)
11 ) as sys.odcinumberlist))
12 )
13 -- final result: summary of digits in even rows
14 select num,
15 sum(digit)
16 from split
17 where cb_odd_even = 'Y'
18 group by num;
NUM SUM(DIGIT)
---------- ----------
253412648 15
121974553 20
SQL>
If it must be PL/SQL, slightly rewrite it as
SQL> declare
2 result number;
3 begin
4 for cur_r in (select num from numb) loop
5 with
6 split as
7 -- split number into rows
8 (select substr(cur_r.num, level, 1) digit,
9 case when mod(level, 2) = 0 then 'Y'
10 else 'N'
11 end cb_odd_even
12 from dual
13 connect by level <= length(cur_r.num)
14 )
15 -- final result: summary of digits in even rows
16 select sum(digit)
17 into result
18 from split
19 where cb_odd_even = 'Y';
20
21 dbms_output.put_line(cur_r.num || ' --> ' || result);
22 end loop;
23 end;
24 /
121974553 --> 20
253412648 --> 15
PL/SQL procedure successfully completed.
SQL>
You could do something like this. A few things of note: first, you can use an implicit cursor (the for rec in (select ...) loop construct). You don't need to declare the data type of rec, you don't need to open the cursor - and then remember to close it when you are finished - etc. Implicit cursors are a great convenience, best to learn about it as early as possible. Second, note that rec (in my notation) is just a pointer; to access the value from it, you must reference the table column (as in, rec.num) - Littlefoot has already shown that in his reply. Third, a logic mistake in your attempt: if the table has more than one row, you must initialize ad to 0 for each new value - you can't just initialize it to 0 once, at the beginning of the program.
So, with all that said - here is some sample data, then the procedure and its output.
create table numb (num int);
insert into numb values(121975443);
insert into numb values(100030000);
insert into numb values(null);
insert into numb values(113313311);
insert into numb values(2020);
commit;
. . . . .
declare
ad int;
i int;
begin
for rec in (select num from numb) loop
ad := 0;
i := rec.num;
while i > 0 loop
if mod(i, 2) = 0 then
ad := ad + mod(i, 10);
end if;
i := trunc(i/10);
end loop;
dbms_output.put_line('num: ' || nvl(to_char(rec.num), 'null') ||
' sum of even digits: ' || ad);
end loop;
end;
/
num: 121975443 sum of even digits: 10
num: 100030000 sum of even digits: 0
num: null sum of even digits: 0
num: 113313311 sum of even digits: 0
num: 2020 sum of even digits: 4
PL/SQL procedure successfully completed.

Lowest fraction Value In Oracle

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;

oracle show table like cartesian coordinate system

I have a table
create table test_table(
id number(10),
x number(10),
y number(10),
svalue number(10));
with filling a table as
declare
i integer;
begin
i := 0;
for x in 1 .. 10 loop
for y in 1 .. 10 loop
i := i + 1;
insert into test_table
(Id, x, y, svalue)
values
(i, x, y, x + y);
end loop;
end loop;
commit;
end;
how I can show table like
1 2 3 4 5 Ny
1 2 3 4 5 6
2 3 4 5 6 7
3 4 5 6 7 8
Nx
where x - rows, y - columns, svalue - value x,y
if we want to get pivot output for Cartesian coordinate system
run below script for create pivot function
http://paste.ubuntu.com/21378705/
pivot function is actually ref cursor that give dynamic column output after ruining above script run query as
select * from table(pivot('select x,y,svalue from test_table'))
in above query select statement use as following manner
select rowsection,columnsection,data from table
i hope this will help.
Hear i achieve cartesian coordinate system using looping test_table.
declare
HEAD VARCHAR2(100);
CURSOR c1 IS SELECT distinct x rec FROM test_table order by x;
CURSOR c2 IS SELECT distinct y rec FROM test_table order by y;
begin
-- for disply header in y
for y in c2 loop
HEAD := HEAD || lpad(y.rec,4);
end loop;
DBMS_OUTPUT.put_line( lpad('X',3) || HEAD );
--
-- disply value with repect to x and y
for x in c1 loop
declare
STR VARCHAR2(100);
CURSOR c2 IS SELECT svalue rec FROM test_table where x= x.rec order by y;
begin
for y in c2 loop
STR := str || lpad(y.rec,4);
end loop;
DBMS_OUTPUT.put_line(lpad(x.rec,3) || STR);
end;
end loop;
--
end;
i hope this will help.

Resources