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.
Related
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.
1 declare
2 a number;
3 b number;
4 c number;
5 d number;
6 PROCEDURE findMin(x IN number, y IN number, z IN number , L out number) IS
7 BEGIN
8 IF x > y&& x>z then
9 L:= x;
10 ELSE if y>z&&y>x then
11 L:= y;
12 else
13 L:=z
14 END IF;
15 End if;
16 END;
17 BEGIN
18 a:= 23;
19 b:= 45;
20 c:=36;
21 findMin(a, b, c,d);
22 dbms_output.put_line(' Minimum of (23, 45,36) : ' || d);
END;
this is the first one I couldnt unerstand what is wrong with this code it is showing
*
ERROR at line 1:
ORA-06540: PL/SQL: compilation error
ORA-06553: PLS-906: Compilation is not possible
the second one is
2. DECLARE
3. num number;
4. c number;
5. PROCEDURE fact(x IN number, f out number) IS
6. BEGIN
7. IF x = 0 THEN
8. f:= x;
9. ELSE
10. f:= x*fact(x-1);
11. END IF;
12. END;
13. BEGIN
14. c:=f;
15. num:=6
16. fact(num,c);
17. dbms_output.put_line(' Factorial: ' ||'is'||c);
18. END;
19. /
i am getting output as
z:= x*fact(x-1) ;
*
ERROR at line 9:
ORA-06550: line 9, column 14:
PLS-00306: wrong number or types of arguments in call to 'FACT'
ORA-06550: line 9, column 8:
PL/SQL: Statement ignored
this is the second procedure i wrote but i couldnt get the problem in it
1 create or replace function tables(n in number) return number is s number;
2 begin
3 i number;
4 for i in 1...10 loop
5 s:=n*i;
6 end loop;
7 return s ;
8* end;
this is the multiplication table function what is wrong with my codes they are showing output as
Warning: Function created with compilation errors.
As of your 1st code: you should use AND, not && and terminate statements with a colon. When fixed, it runs (and produces wrong result, though, but I'll leave it to you):
SQL> DECLARE
2 a NUMBER;
3 b NUMBER;
4 c NUMBER;
5 d NUMBER;
6
7 PROCEDURE findMin (x IN NUMBER,
8 y IN NUMBER,
9 z IN NUMBER,
10 L OUT NUMBER)
11 IS
12 BEGIN
13 IF x > y
14 AND x > z
15 THEN
16 L := x;
17 ELSE
18 IF y > z
19 AND y > x
20 THEN
21 L := y;
22 ELSE
23 L := z;
24 END IF;
25 END IF;
26 END;
27 BEGIN
28 a := 23;
29 b := 45;
30 c := 36;
31 findMin (a,
32 b,
33 c,
34 d);
35 DBMS_OUTPUT.put_line (' Minimum of (23, 45,36) : ' || d);
36 END;
37 /
Minimum of (23, 45,36) : 45
PL/SQL procedure successfully completed.
SQL>
All that (37 lines of code) could be shortened to only one (which actually works):
SQL> select least(23, 45, 36) minimum from dual;
MINIMUM
----------
23
SQL>
As of your 2nd code: procedure is wrong as fact can't be used that way and lacks in 2nd parameter. One option to fix it is
SQL> DECLARE
2 num NUMBER;
3 c NUMBER;
4
5 PROCEDURE fact (x IN NUMBER, f OUT NUMBER)
6 IS
7 l_var NUMBER := 1;
8 BEGIN
9 FOR i IN 1 .. x
10 LOOP
11 l_var := l_var * i;
12 END LOOP;
13
14 f := l_var;
15 END;
16 BEGIN
17 num := 6;
18 fact (num, c);
19 DBMS_OUTPUT.put_line (' Factorial of ' || num || ' is ' || c);
20 END;
21 /
Factorial of 6 is 720
PL/SQL procedure successfully completed.
SQL>
Finally, the 3rd code: I have no idea what you meant to say with it, it's full of errors. I tried to salvage it, can't tell whether I succeeded.
SQL> CREATE OR REPLACE FUNCTION tables (n IN NUMBER)
2 RETURN NUMBER
3 IS
4 s NUMBER := 0;
5 BEGIN
6 FOR i IN 1 .. 10
7 LOOP
8 s := s + n * i;
9 END LOOP;
10
11 RETURN s;
12 END;
13 /
Function created.
SQL> SELECT tables (3) FROM DUAL;
TABLES(3)
----------
165
SQL>
n number := &n;
c number;
i number;
function isprime(x in number)
RETURN number
IS
begin
count number:=0;
for i in 2..x/2 loop
if mod(x,i)=0 then
count := count+1;
end if;
end loop;
return count;
end;
begin
c:=isprime(n);
if c=0 then
dbms_output.put_line(n||'is a prime number');
else
dbms_output.put_line(n||'is not prime');
end if;
end;
/
ORA-06550: line 11, column 7:
PLS-00103: Encountered the symbol "NUMBER" when expecting one of the following:
:= . ( # % ;
The symbol "." was substituted for "NUMBER" to continue.
Don't use column names that match Oracle's built-in functions (count is one of them). Declare variables in declaration section, not just anywhere.
SQL> DECLARE
2 n NUMBER := &par_n;
3 c NUMBER;
4 i NUMBER;
5
6 FUNCTION isprime (x IN NUMBER)
7 RETURN NUMBER
8 IS
9 l_count NUMBER := 0;
10 BEGIN
11 FOR i IN 2 .. x / 2
12 LOOP
13 IF MOD (x, i) = 0
14 THEN
15 l_count := l_count + 1;
16 END IF;
17 END LOOP;
18
19 RETURN l_count;
20 END;
21 BEGIN
22 c := isprime (n);
23
24 IF c = 0
25 THEN
26 DBMS_OUTPUT.put_line (n || ' is a prime number');
27 ELSE
28 DBMS_OUTPUT.put_line (n || ' is not prime');
29 END IF;
30 END;
31 /
Enter value for par_n: 6
6 is not prime
PL/SQL procedure successfully completed.
SQL> /
Enter value for par_n: 7
7 is a prime number
PL/SQL procedure successfully completed.
SQL>
I want to create 10 pairs of random integers within the range of [-4,+4) using PLSQL and then using the pair to solve a primary equation(num1*X+num2=0) and save the results inside the table, as well as a text output of what the type of outcome for the equation(Solvable/Indefinite/Impossible). I am using Oracle LiveSQL.
I used cursor to make sure the randomized number is an integer.
CURSOR NUMcursor1 IS
SELECT ROUND(DBMS_RANDOM.VALUE(-4,+4),0) FROM DUAL;
num1 EquatA2.dat1%TYPE; num2 EquatA2.dat2%TYPE;
solution EquatA2.sol%TYPE; notes EquatA2.note%TYPE;
`
`
CREATE TABLE EquatA2
(
dat1 NUMBER(2,0),
dat2 NUMBER(2,0),
sol NUMBER(6,3),
note VARCHAR2(20)
)
DECLARE
num1 EquatA2.dat1%TYPE; num2 EquatA2.dat2%TYPE;
solution EquatA2.sol%TYPE; notes EquatA2.note%TYPE;
i INT; i:=1;
CURSOR NUMcursor1 IS
SELECT ROUND(DBMS_RANDOM.VALUE(-4,+4),0) FROM DUAL;
CURSOR NUMcursor2 IS
SELECT ROUND(DBMS_RANDOM.VALUE(-4,+4),0) FROM DUAL;
BEGIN
OPEN NUMcursor1;
OPEN NUMcursor2;
FOR i IN 1..10
LOOP
FETCH NUMcursor1 INTO num1;
EXIT WHEN NUMcursor1%NOTFOUND;
FETCH NUMcursor2 INTO num2;
EXIT WHEN NUMcursor2%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(num1);
DBMS_OUTPUT.PUT_LINE(num2);
IF (num1 != 0) THEN solution := -num2 / num1 AND notes := 'solvable';
ELSIF (num1 == 0 AND num2 == 0) THEN notes := 'indefinite';
ELSIF (num1 == 0 AND num2 != 0) THEN notes := 'impossible';
END IF;
INSERT INTO EquatA2 VALUES(num1,num2,solution,notes);
END LOOP;
END;
`
`
Expected results: 10 text outputs and the range of the random numbers to be [-4,+4)
Actual results(errors):
ORA-00922: missing or invalid option
Invalid statement
Unsupported Command
Invalid statement
Result Set 6
ROUND(DBMS_RANDOM.VALUE(-4,+4),0)
-2
Download CSV
Invalid statement
Result Set 7
ROUND(DBMS_RANDOM.VALUE(-4,+4),0)
-3
Download CSV
ORA-06550: line 18, column 56: PLS-00103: Encountered the symbol "=" when expecting one of the following: . ( * # % & = - + ; < / > at in is mod remainder not rem <an exponent (**)> <> or != or ~= >= <= <> and or like like2 like4 likec between || multiset member submultiset The symbol "* was inserted before "=" to continue.
I've modified your code so that it works. Here you go.
Table:
SQL> CREATE TABLE EquatA2
2 (
3 dat1 NUMBER(2,0),
4 dat2 NUMBER(2,0),
5 sol NUMBER(6,3),
6 note VARCHAR2(20)
7 );
Table created.
SQL>
PL/SQL anonymous procedure:
SQL> DECLARE
2 num1 EquatA2.dat1%TYPE;
3 num2 EquatA2.dat2%TYPE;
4 solution EquatA2.sol%TYPE;
5 notes EquatA2.note%TYPE;
6 BEGIN
7 delete from equata2;
8 FOR i IN 1..10 LOOP
9 num1 := ROUND(DBMS_RANDOM.VALUE(-4, +4), 0);
10 num2 := ROUND(DBMS_RANDOM.VALUE(-4, +4), 0);
11 -- DBMS_OUTPUT.PUT_LINE(num1);
12 -- DBMS_OUTPUT.PUT_LINE(num2);
13
14 IF num1 != 0 THEN
15 solution := -num2 / num1;
16 notes := 'solvable';
17 ELSIF num1 = 0 AND num2 = 0 THEN
18 notes := 'indefinite';
19 ELSIF num1 = 0 AND num2 != 0
20 THEN notes := 'impossible';
21 END IF;
22
23 INSERT INTO EquatA2 VALUES (num1, num2, solution, notes);
24 END LOOP;
25 END;
26 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from equata2;
DAT1 DAT2 SOL NOTE
---------- ---------- ---------- --------------------
1 -4 4 solvable
-1 0 0 solvable
0 3 0 impossible
0 2 0 impossible
0 0 0 indefinite
3 -1 ,333 solvable
4 3 -,75 solvable
1 -1 1 solvable
-1 -2 -2 solvable
2 3 -1,5 solvable
10 rows selected.
SQL>
Does anyone have a working function available to use within Oracle using PL/SQL which implements the Luhn Mod 16 Algorithm to generate a check digit for an input code number such as the following example? 0B012722900021AC35B2
LOGIC
Map from HEX into Decimal equivalent 0 B 0 1 2 7 2 2 9 0 0 0 2 1 A C 3 5 B 2 - becomes 0 11 0 1 2 7 2 2 9 0 0 0 2 1 10 12 3 5 11 2
Start with the last character in the string and move left doubling every other number -
Becomes 0 22 0 2 2 14 2 4 9 0 0 0 2 2 10 24 3 10 11 4
Convert the "double" to a Base 16 (Hexadecimal) format. If the conversion results in numeric output, retain the value.
Becomes: 0 16 0 2 2 E 2 4 9 0 0 0 2 2 10 18 3 A 11 4
Reduce by splitting down any resultant values over a single digit in length.
Becomes 0 (1+6) 0 2 2 E 2 4 9 0 0 0 2 2 10 (1+8) 3 A 11 4
Sum all digits. Apply the last numeric value returned from the previous sequence of calculations (if the current value is A-F substitute the numeric value from step 1)
Becomes 0 7 0 2 2 7 2 4 9 0 0 0 2 2 10 9 3 5 11 4
The sum of al l digits is 79 (0+7+0+2+2+7+2+4+9+0+0+0+2+2+10+9+3+5+11+4)
Calculate the value needed to obtain the next multiple of 16, in this case the next multiple 16 is 80 therefore the value is 1
The associated check character is 1
Thanks Lee
How about this?
CREATE OR REPLACE TYPE VARCHAR_TABLE_TYPE AS TABLE OF VARCHAR2(1000);
DECLARE
luhn VARCHAR2(100) := '0B012722900021AC35B2';
digits VARCHAR_TABLE_TYPE;
DigitSum INTEGER;
BEGIN
SELECT REGEXP_SUBSTR(luhn, '.', 1, LEVEL)
BULK COLLECT INTO digits
FROM dual
CONNECT BY REGEXP_SUBSTR(luhn, '.', 1, LEVEL) IS NOT NULL;
FOR i IN digits.FIRST..digits.LAST LOOP
digits(i) := TO_NUMBER(digits(i), 'X'); -- Map from HEX into Decimal equivalent
IF digits.COUNT MOD 2 = i MOD 2 THEN -- every second digit from left
digits(i) := 2 * TO_NUMBER(digits(i)); -- doubling number
digits(i) := TO_CHAR(digits(i), 'fmXX'); -- Convert the "double" to a Base 16 (Hexadecimal) format
IF (REGEXP_LIKE(digits(i), '^\d+$')) THEN
-- Reduce by splitting down any resultant values over a single digit in length.
SELECT SUM(REGEXP_SUBSTR(digits(i), '\d', 1, LEVEL))
INTO digits(i)
FROM dual
CONNECT BY REGEXP_SUBSTR(digits(i), '\d', 1, LEVEL) IS NOT NULL;
END IF;
END IF;
END LOOP;
FOR i IN digits.FIRST..digits.LAST LOOP
-- I don't understand step 5), let's simulate it
IF digits(i) = 'E' THEN digits(i) := 7; END IF;
IF digits(i) = 'A' THEN digits(i) := 5; END IF;
END LOOP;
-- The sum of all digits
SELECT SUM(COLUMN_VALUE)
INTO DigitSum
FROM TABLE(digits);
-- Calculate the value needed to obtain the next multiple of 16
DBMS_OUTPUT.PUT_LINE ( 16 - DigitSum MOD 16 );
END;
Here is a function which implements the steps you outlined in your question:
create or replace function get_luhn_16_check_digit
( p_str in varchar)
return pls_integer
is
b16 sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll() ;
n16 sys.odcinumberlist := sys.odcinumberlist();
tot simple_integer := 0;
chk_digit pls_integer;
begin
-- step 1)
select to_number(tkn, 'X')
bulk collect into n16
from ( select substr(p_str, level, 1) as tkn
from dual
connect by level <= length(p_str));
b16.extend(n16.count());
for idx in 1..n16.count() loop
if mod(idx, 2) = 0
then
-- step 2) and 3)
b16(idx) := to_char( to_char(n16(idx)) * 2, 'fmXX');
-- step 4)
if length( b16(idx)) = 2 then
b16(idx) := to_number(substr(b16(idx),1,1)) + to_number(substr(b16(idx),2,1));
end if;
else
b16(idx) := trim(to_char(n16(idx)));
end if;
end loop;
-- step 5) and 6)
for idx in 1..b16.count() loop
if b16(idx) not in ('A','B','C','D','E','F') then
tot := tot + to_number(b16(idx));
else
tot := tot + n16(idx);
end if;
end loop;
-- step 7) and 8)
chk_digit := (ceil(tot/16)*16) - tot;
return chk_digit;
end;
/
To run:
select get_luhn_16_check_digit('0B012722900021AC35B2') from dual;
This now returns 1. Here is a LiveSQL demo.
There is still the question of what happens when the modulus 16 is greater than 9; for instance, this value returns 15.
select get_luhn_16_check_digit('22111111111111111111') from dual;
A decimal result such as 15 is obviously not a check digit, which suggests you need an additional step 9). Perhaps we need to convert 15 -> (1+5) -> 6. Or Perhaps the digit should be base16? Please edit your question to confirm the appropriate rule.