asking for user input in PL/SQL - oracle

I am new to PL/SQL and I am stuck on some code.
I am wanting to ask the user for a number and then I want to be able to use this number in an IF THEN statement to verify if the number is greater than or less than 20.
I am stuck on how to obtain user input and once I have it, I can store it and give it back to the user and verify its greater than or less than 20.
DECLARE
a number(2) := 10;
BEGIN
a:= 10;
-- check the boolean condition using if statement
IF( a < 20 ) THEN
-- if condition is true then print the following
dbms_output.put_line('a is less than 20 ' );
END IF;
dbms_output.put_line('value of a is : ' || a);
END;
/

SQL> set serveroutput on; -- for "dbms_output.put_line" to take effect
SQL> DECLARE
a number := &i_nr; -- there's no need to restrict a non-decimal numeric variable to a length
BEGIN
--a:= 10; --no need this when initialization is in declaration section
-- check the boolean condition using if statement
IF( a < 20 ) THEN
-- if condition is true then print the following
dbms_output.put_line('a is less than 20 ' );
END IF;
dbms_output.put_line('value of a is : ' || a);
END;
/
-- it prompts you for value of &i_nr "enter a numeric value, for example 10", for string values it must be in quotes '&i_str'

In SQLPlus, you'd use "&"; in my example, it is && so that I wouldn't have to enter the same value several times (i.e. every time "a" is referenced):
SQL> begin
2 if &&a < 20 then
3 dbms_output.put_line('a is less than 20');
4 end if;
5 dbms_output.put_line('value of a is: ' || &&a);
6 end;
7 /
Enter value for a: 5
old 2: if &&a < 20 then
new 2: if 5 < 20 then
old 5: dbms_output.put_line('value of a is: ' || &&a);
new 5: dbms_output.put_line('value of a is: ' || 5);
a is less than 20
value of a is: 5
PL/SQL procedure successfully completed.
SQL>
Though, I'd say that you'd rather create a procedure with an IN parameter, such as
SQL> create or replace procedure p_test (par_a in number) is
2 begin
3 if par_a < 20 then
4 dbms_output.put_line('a is less than 20');
5 end if;
6 dbms_output.put_line('value of a is: ' || par_a);
7 end;
8 /
Procedure created.
SQL> exec p_test(15);
a is less than 20
value of a is: 15
PL/SQL procedure successfully completed.
SQL> exec p_test(34);
value of a is: 34
PL/SQL procedure successfully completed.
SQL>

Related

For loop not printing last record in a cursor

So,I am working with a cursor,the cursor was initially operating with the initial for loop,now i needed to perform operation if the no of records in the cursor is more than one,so i fetched the no of records first and stored in a variable and used an if-condition based on that.Now the problem is when I run the whole process,the procedure does its job,but only for the first record in the cursor and entirely skips the second record.Please suggest or help me identify the mistake.
Adding code snippet.
for m in get_m_p(a,b)--main cursor
loop
fetch get_m_p into c_m;
exit when g_m_p%notfound;
end loop;
tempcount := g_m_p%ROWCOUNT:
statements---
if(tempcount>1) then
statements----
end if;
end loop;
for the two records the main curosr is returning in first line,operations are only done for the first one,and the second record is being skipped entirely.
This is a superfluous line:
fetch get_m_p into c_m;
You don't explicitly fetch within a cursor FOR loop, it is implicitly done in each loop iteration. Remove that line.
How to get number of rows returned by a cursor? Lucky you, it seems that you don't care whether how many rows - exactly - it returns. All you want to know is whether it returned more than 1 row. So, count them, literally; exit the loop if counter exceeds 1.
SQL> DECLARE
2 CURSOR get_m_p IS
3 SELECT *
4 FROM emp
5 WHERE deptno = 10;
6
7 l_cnt NUMBER := 0;
8 BEGIN
9 FOR m IN get_m_p
10 LOOP
11 l_cnt := l_cnt + 1;
12 EXIT WHEN l_cnt > 1;
13 END LOOP;
14
15 DBMS_OUTPUT.put_line ('Cursor returned at least ' || l_cnt || ' row(s)');
16
17 IF l_cnt > 1
18 THEN
19 NULL;
20 -- the rest of statements go here
21 END IF;
22 END;
23 /
Cursor returned at least 2 row(s)
PL/SQL procedure successfully completed.
SQL>
As there's no way to know how many rows will cursor return, unfortunately, you'll have to check that first, and then decide what to do with the result.
DECLARE
CURSOR get_m_p IS
SELECT *
FROM emp
WHERE deptno = 10;
l_cnt NUMBER := 0;
BEGIN
SELECT COUNT (*)
INTO l_cnt
FROM (-- this is cursor's SELECT statement
SELECT *
FROM emp
WHERE deptno = 10);
FOR m IN get_m_p
LOOP
-- some statements here
IF l_cnt > 1
THEN
NULL;
-- statements to be executed if cursor return more than 1 row
END IF;
END LOOP;
END;
/
Cursor:
Oracle creates memory area to process SQL statement which is called context area and the cursor is pointer to the context area. A cursor holds the rows (one or more) returned by a SQL statement. The set of rows the cursor holds is referred to as the active set.
There are two type of cursor
1. Implicit cursor
2. Explicit cursor
Implicit Cursors :
Implicit cursors are automatically created by Oracle whenever an SQL statement is executed. Any SQL cursor attribute will be accessed as sql%attribute_name as shown below in the example. Use the SQL%ROWCOUNT attribute to determine the number of rows affected
DECLARE
no_of_records number(2);
BEGIN
select * from records;
IF sql%notfound THEN
dbms_output.put_line('no records present');
ELSIF sql%found THEN
no_of_records := sql%rowcount;
IF no_of_records > 1 THEN
dbms_output.put_line('no of records ' || no_of_records);
END IF
END IF;
END;
Explicit Cursors :
Explicit cursors are programmer-defined cursors for gaining more control over the context area. An explicit cursor should be defined in the declaration section of the PL/SQL Block. It is created on a SELECT Statement which returns more than one row.
Please see below example:
DECLARE
r_id records.id%type;
CURSOR c_records is
SELECT id FROM records;
BEGIN
OPEN c_records;
LOOP
FETCH c_records into r_id;
EXIT WHEN c_records%notfound;
dbms_output.put_line('Record id ' || r_id );
END LOOP;
CLOSE c_records;
END;
Reference :
https://www.tutorialspoint.com/plsql/plsql_cursors.htm
As an alternative you can cache every row and process after.
Example using sample schema "HR" on Oracle 11g Express Edition:
DECLARE
CURSOR get_m_p
IS
SELECT *
FROM hr.employees
WHERE department_id = 60
order by employee_id;
--
rcEmp_last get_m_p%rowtype;
l_cnt NUMBER;
BEGIN
FOR rcM IN get_m_p LOOP
l_cnt := get_m_p%rowcount;
Dbms_Output.Put_Line('l_cnt='||l_cnt);
if l_cnt=1 then
rcEmp_last:=rcM;
Else
Dbms_Output.Put_Line('Process='||to_char(l_cnt-1));
Dbms_Output.Put_Line('rcEmp_last.employee_id='||rcEmp_last.employee_id);
--
rcEmp_last:=rcM;
END IF;
End loop;
--
Dbms_Output.Put_Line('Exited FOR-LOOP');
Dbms_Output.Put_Line('l_cnt='||l_cnt);
--
if l_cnt>1 then
Dbms_Output.Put_Line('rcEmp_last.employee_id='||rcEmp_last.employee_id);
End if;
END;
Output:
Text
PL/SQL block, executed in 1 ms
l_cnt=1
l_cnt=2
Process=1
rcEmp_last.employee_id=103
l_cnt=3
Process=2
rcEmp_last.employee_id=104
l_cnt=4
Process=3
rcEmp_last.employee_id=105
l_cnt=5
Process=4
rcEmp_last.employee_id=106
Exited FOR-LOOP
l_cnt=5
rcEmp_last.employee_id=107
Total execution time 35 ms

PLS-00103: Encountered the symbol " " when expecting one of the following: := . ( # % ; not null range default character

Why doesn't this work?
Any advice or solutions are greatly appreciated.
CREATE OR REPLACE TRIGGER OverReading
BEFORE UPDATE OR INSERT ON MeterReadings
FOR EACH ROW
DECLARE
emp_counter INTEGER;
max_meter INTEGER : = 5;
BEGIN
SELECT COUNT(EmployeeId) INTO emp_counter
FROM MeterReadings
WHERE EmployeeId = :NEW.EmployeeId;
IF : OLD.EmployeeId = NEW.EmployeeId THEN
RETURN;
ELSIF emp_counter >= max_meter THEN
raise_application_error(-20900,'Employee are limited to a maximum of 5 meters');
END IF;
END;
/
The Error that is thrown to me
Three syntax errors, all of them related to : character.
When fixed, trigger compiles:
SQL> CREATE OR REPLACE TRIGGER OverReading
2 BEFORE UPDATE OR INSERT ON MeterReadings
3 FOR EACH ROW
4 DECLARE
5 emp_counter INTEGER;
6 max_meter INTEGER := 5; --> here
7 BEGIN
8 SELECT COUNT(EmployeeId) INTO emp_counter
9 FROM MeterReadings
10 WHERE EmployeeId = :NEW.EmployeeId;
11 IF :OLD.EmployeeId = :NEW.EmployeeId THEN --> 2x here
12 RETURN;
13 ELSIF emp_counter >= max_meter THEN
14 raise_application_error(-20900,'Employee are limited to a maximum of 5 meters');
15 END IF;
16 END;
17 /
Trigger created.
SQL>
However: this code will run if you insert rows one-by-one. Otherwise, it will raise mutating table error as you're selecting from the same table you're inserting into (which is not allowed). If that bothers you, there are ways to fix it. Depending on database version you use, it could be a compound trigger or a type + package option.

What is the difference between BEGIN followed by IF and IF followed by BEGIN

I am new to PL/SQL. What is the difference between the below two flows?
BEGIN followed by IF
BEGIN
IF flag= 1 THEN
FOR SOME_REC(id)
LOOP
END LOOP;
END IF;
END
IF followed by BEGIN
IF flag= 1 THEN
BEGIN
FOR SOME_REC(id)
LOOP
END LOOP;
END
END IF;
By its design PL/SQL is a block structured language with each block consisting of 4 components:
Declaration Section - optional:; DECLARE or Named Block (Function, Procedure, ...)
Execution Section - required: BEGIN
Exception Section - optional: EXCEPTION
End Statement - required: END
As #Mr.AF indicates each block defines the SCOPE of variables. Further these blocks can be nested. So a variable declared in an outer block is visible within that block and all blocks nested within. Subject to the rule that closest declaration wins and items declared in an nested bloc are not visible in outer blocks. See GURU99 for fuller description. Try the following.
declare
var varchar2(5) := '12345';
begin
dbms_output.put_line ( 'Outer block has variable ''var'' and has a length of '
|| to_char(length(var)) || ' and value of ' || var);
declare -- start nested block
var varchar2(8);
inner_only varchar2(8) := 'ShotTerm';
begin
var := '12345678';
dbms_output.put_line ( 'Inner block has variable ''var'' and has a length of '
|| to_char(length(var)) || ' and value of ' || var
|| ' and also variable inner_only with value ' || inner_only);
end ; -- end nested block
dbms_output.put_line ( 'Outer block variable ''var'' still has a length of '
|| to_char(length(var)) || ' and value of ' || var);
-- uncomment below to see difference
-- dbms_output_put_line('A reference to ' || inner_only || ' does not work; gives compile error');
end ; --outer block
For your specific scenarios #1 the IF and LOOP are being run in an outer (or only) block. While #2 the IF is run in the outer block while the loop runs in an inner block. Note: #2 must have a prior BEGIN and the END IF must be followed by an END - without them you will get a compile syntax error.
BEGIN ... END keywords implies starting point and end point of a code block like { } and compiler will validate local references (locality of references) within the block.otherwise, unexpected results may be happened.
The way you put it, without any further explanation of what problem you are trying to solve (if any), then there's no much difference in those two pieces of code. Both will do what they are supposed to do.
However, I'd like to say something about the 3rd case you never mentioned: BEGIN-EXCEPTION-END block within the loop, which does make the difference.
IF flag = 1 THEN
FOR SOME_REC(id) LOOP
BEGIN
do_something_here;
EXCEPTION
when some exception then
handle it
END;
END LOOP;
END IF;
What would be its purpose? In both of your examples, if any exception occurs within the loop, processing will be stopped and the procedure will terminate.
But, if there's an inner begin-exception-end block, it lets you capture an error within the loop, handle it the way you find the most appropriate (if there's nothing better, just log the error) and move on to the next loop cycle.
Doing so, you'll be able to process all records returned by the cursor. Some of them might fail, that's true, but you'll be able to proceed with the execution.
Here's an example based on Scott's EMP table; I'll try to divide SAL and NVL(COMM, 0) which will result in divide by zero error in some cases.
SQL> select ename, sal, nvl(comm, 0) comm from emp where deptno = 30 order by ename;
ENAME SAL COMM
---------- ---------- ----------
ALLEN 1600 300
BLAKE 2850 0 --> 2850/0 --> error
JAMES 950 0 --> error
MARTIN 1250 1400
TURNER 1500 0 --> error
WARD 1250 500
6 rows selected.
Code, as you wrote it:
SQL> set serveroutput on
SQL> declare
2 l_divided number;
3 begin
4 for cur_r in (select ename, sal, nvl(comm, 0) comm
5 from emp
6 where deptno = 30
7 order by ename
8 )
9 loop
10 l_divided := round(cur_r.sal / cur_r.comm);
11 dbms_output.put_line(cur_r.ename ||': ' || l_divided);
12 end loop;
13 end;
14 /
ALLEN: 5
declare
*
ERROR at line 1:
ORA-01476: divisor is equal to zero
ORA-06512: at line 10
SQL>
See? Only the first one, Allen, had valid data. The rest of them weren't even calculated, as soon as the first error appeared.
But, if you rewrite it as I suggested, then all of them are processed:
SQL> declare
2 l_divided number;
3 begin
4 for cur_r in (select ename, sal, nvl(comm, 0) comm
5 from emp
6 where deptno = 30
7 order by ename
8 )
9 loop
10 begin
11 l_divided := round(cur_r.sal / cur_r.comm);
12 dbms_output.put_line(cur_r.ename ||': ' || l_divided);
13 exception
14 when zero_divide then
15 dbms_output.put_line(cur_r.ename ||': COMM is equal to zero');
16 end;
17 end loop;
18 end;
19 /
ALLEN: 5
BLAKE: COMM is equal to zero
JAMES: COMM is equal to zero
MARTIN: 1
TURNER: COMM is equal to zero
WARD: 3
PL/SQL procedure successfully completed.
SQL>
See if the above makes any sense in what you asked.

I am trying to write a program for Armstrong number in PL/SQL.Can anyone tell me where i am wrong?

while N !=0
LOOP
R:=MOD(N,10);
R1:=power(R,3);
A:=A+R1;
N:=TRUNC(N/10);
END LOOP;
After this it comes IF N=A THEN
SYS.DBMS_OUTPUT.PUT_LINE(' number is armstrong ');
ELSE
SYS.DBMS_OUTPUT.PUT_LINE(' number is not an armstrong ');
Your problem is when you compare (N=A) because N is zero at the end of LOOP. Then you must compare A with the original number entered, (NOrig=A).
This procedure you can help:
create or replace procedure amstrong_number(pNumber int)
is
NOrig int:=0;
N int:=0;
R int:=0;
R1 int:=0;
A int:=0;
begin
NOrig:=pNumber;
N:=pNumber;
WHILE N!= 0
LOOP
R:=MOD(N,10);
R1:=POWER(R,3);
A:=A+R1;
N:=TRUNC(N/10);
END LOOP;
IF NOrig = A THEN
dbms_output.put_line(' number is amstrong ');
ELSE
dbms_output.put_line(' number is not an amstrong ');
END IF;
end;
Regards
In a comment you say you want to take user input from the keyboard. It is not clear how you plan to do that; in general, PL/SQL does not have facilities for interaction with end users, it is a language for processing on the server.
One way to have the user provide a number is to have a procedure with an in parameter. Below is one example of how you can do it. You must compile the procedure first (for example, I saved it in a script, "Armstrong.sql", and then I ran the command "start Armstrong" in SQL*Plus). Then the user can use the procedure, which I called is_Armstrong, from SQL*Plus or a graphical interface like SQL Developer or Toad, like this: exec is_Armstrong(...) where in parentheses is the user's input number.
I built in two application exception checks - for negative or non-integer numbers and for numbers that are too long (more than 20 digits). I wrote it for Armstrong numbers of arbitrary length (<= 20), limiting it to three digits doesn't save almost any work so why not make it general. There are other possible application exceptions - for example, what if the user inputs a string instead of a number? I didn't handle all the errors - the very last example below shows an unhandled one. Write your own error handling code for whatever else you want to handle explicitly.
Content of Armstrong.sql:
create or replace procedure is_Armstrong(p_input_number number)
as
l_string varchar2(20);
l_sum number := 0;
l_loop_counter number;
l_len number;
not_a_positive_integer exception;
number_too_large exception;
begin
l_string := to_char(p_input_number);
l_len := length(l_string);
if p_input_number <= 0 or p_input_number != floor(p_input_number) then
raise not_a_positive_integer;
elsif l_len > 20 then
raise number_too_large;
end if;
for l_loop_counter in 1..l_len
loop
l_sum := l_sum + power(to_number(substr(l_string, l_loop_counter, 1)), l_len);
end loop;
if p_input_number = l_sum then
dbms_output.put_line('Number ' || l_string || ' is an Armstrong number.');
else
dbms_output.put_line('Number ' || l_string || ' is not an Armstrong number.');
end if;
exception
when not_a_positive_integer then
dbms_output.put_line('Input number must be a positive integer.');
when number_too_large then
dbms_output.put_line('Input number must be no more than twenty digits.');
end;
/
Compiling the procedure:
SQL> start Armstrong.sql
Procedure created.
Elapsed: 00:00:00.03
Usage examples:
SQL> exec is_Armstrong(-33);
Input number must be a positive integer
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.08
SQL> exec is_Armstrong(1.5);
Input number must be a positive integer
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.08
SQL> exec is_Armstrong(1234512345123451234512345);
Input number must be no more than twenty digits
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL> exec is_Armstrong(3);
Number 3 is an Armstrong number.
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.00
SQL> exec is_Armstrong(100);
Number 100 is not an Armstrong number.
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.00
SQL> exec is_Armstrong(371);
Number 371 is an Armstrong number.
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.00
SQL> exec is_Armstrong('abc')
BEGIN is_Armstrong('abc'); END;
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 1
Elapsed: 00:00:00.00

DELETE Collection Method in oracle work wrong

I want to use DELETE Collection Method to delete some elements in collection
such as:
create or replace procedure testloop3 (clearaaa out nestedtable) as
type nestedtable is table of varchar2(255);
reply_ref_messageIds nestedtable;
getDelete_messageIds nestedtable;
begin
select distinct r.messagebox_id bulk collect into reply_ref_messageIds from reply r;
select m.id bulk collect into getDelete_messageIds from messagebox m;
getDelete_messageIds.delete(2);
getDelete_messageIds.delete(4);
getDelete_messageIds.delete(7);
getDelete_messageIds.delete(11);
getDelete_messageIds.delete(13);
for i in getDelete_messageIds.FIRST .. getDelete_messageIds.LAST loop
dbms_output.put_line(i);
end loop;
for i in 5 .. 12 loop
dbms_output.put_line(i);
end loop;
end;
and then I debug this procedure with plsql dev
-- Created on 2013/4/4 by THINKPAD
declare
-- Local variables here
aa nestedtable;
begin
-- Test statements here
testloop3(aa);
end;
and I get the indexes of getDelete_messageIds before remove which are 1 to 15.
However: when I debug to getDelete_messageIds.delete(2); it removes index 1 and 2...I can't explain why.
And then when I debug next statement getDelete_messageIds.delete(4); it removes index 3 and 4. And then getDelete_messageIds.delete(7); only removes index 7...
I can't understand...
your procedure, as posted, is showing nothing of the sort. you are simply looping around like
for idx in 1..15 loop
(.FIRST will resolve to 1 and .LAST will resolve to 15). it does not mean there are still 15 elements in the NT.
you are not checking to see if the indexes are deleted. I think you're confused about the proper way to loop through a nested table where there are gaps.
i.e. you can see the elements are deleted:
SQL> create table messagebox(id ) as select to_char(rownum) from dual connect by level <= 15;
Table created.
SQL> create or replace procedure testloop3
2 as
3 type nestedtable is table of varchar2(255);
4 getDelete_messageIds nestedtable;
5 v_idx number;
6 begin
7 select m.id bulk collect into getDelete_messageIds from messagebox m;
8 getDelete_messageIds.delete(2);
9 getDelete_messageIds.delete(4);
10 getDelete_messageIds.delete(7);
11 getDelete_messageIds.delete(11);
12 getDelete_messageIds.delete(13);
13 v_idx := getDelete_messageIds.first;
14 while v_idx is not null
15 loop
16 dbms_output.put_line(v_idx);
17 v_idx := getDelete_messageIds.next(v_idx);
18 end loop;
19 end;
20 /
Procedure created.
SQL> exec testloop3
1
3
5
6
8
9
10
12
14
15
so 2, 4, 7, 11, 13 are deleted. exactly as expected.
you can see
DECLARE
TYPE NumList IS TABLE OF NUMBER;
n NumList := NumList(1,3,5,7);
counter INTEGER;
BEGIN
DBMS_OUTPUT.PUT_LINE('N''s first subscript is ' || n.FIRST);
DBMS_OUTPUT.PUT_LINE('N''s last subscript is ' || n.LAST);
-- When the subscripts are consecutive starting at 1,
-- it's simple to loop through them.
FOR i IN n.FIRST .. n.LAST
LOOP
DBMS_OUTPUT.PUT_LINE('Element #' || i || ' = ' || n(i));
END LOOP;
n.DELETE(2); -- Delete second element.
-- When the subscripts have gaps
-- or the collection might be uninitialized,
-- the loop logic is more extensive.
-- Start at the first element
-- and look for the next element until there are no more.
IF n IS NOT NULL THEN
counter := n.FIRST;
WHILE counter IS NOT NULL
LOOP
DBMS_OUTPUT.PUT_LINE
('Element #' || counter || ' = ' || n(counter));
counter := n.NEXT(counter);
END LOOP;
ELSE
DBMS_OUTPUT.PUT_LINE('N is null, nothing to do.');
END IF;
END;
reference Finding the First or Last Collection Element

Resources