ERROR: Reference the counter as the target of an assignment - PL/SQL - sql

I'm relatively new to SQL.
I'm trying to print a simple pattern through this code
declare
n number(2):=5;
temp number(2):=n;
begin
for a in 1..5
a:=a+1;loop
for b in 1..temp loop
b:=b+1;
dbms_output.put_line(' ');
temp:=temp-1;
end loop;
for c in 1..2*a-1 loop
c:=c+1;
dbms_output.put_line('*');
end loop;
end loop;
end;
/
I keep getting this error:
PLS-00103: Encountered the symbol "A" when expecting one of the following:
* & - + / at loop mod remainder rem <an exponent (**)> ||
multiset
I understand Oracle doesn't allow to reference the counter as the target of an assignment which is why I keep getting error at line 6 but I'm unable to make it work even by declaring another global variable and assigning increment statement in it but it doesn't work either.
Please help.
Thanks!

Modifying the previous answers to actually give you Pascal's triangle, which you mentioned you were attempting in a comment:
set serveroutput on format wrapped
declare
n number(2):=5;
begin
for a in 1..n loop
for b in 1..n-a loop
dbms_output.put(' ');
end loop;
for c in 1..2*a-1 loop
dbms_output.put('*');
end loop;
dbms_output.new_line;
end loop;
end;
/
*
***
*****
*******
*********
PL/SQL procedure successfully completed.
Both your dbms_output.put_line calls needed to be just dbms_output.put, as that was printing each * on a line on its own. But you do need a line break after each time around the a loop, so I've added a dbms_output.newline at the end of that. You were also decrementing temp inside the b loop, which meant it was zero instead of (n-1) for the second time around the a loop; but you don't really need a separate temp variable at all as that is always the same as (n-a)+1 and the +1 just puts an extra space on every line. (I also made the a loop 1..n as I assume you want to change the value of n later in one place only). With n := 8:
*
***
*****
*******
*********
***********
*************
***************
Crucially though you also have to set serveroutput on format wrapped, otherwise the leading spaces you're generating in the b loop are discarded.
You can also do this in plain SQL, though you need to supply the 5 twice, or use a bind or substitution variable:
select lpad(' ', 5 - level, ' ') || rpad('*', (level * 2) - 1, '*') as pascal
from dual
connect by level <= 5
PASCAL
------------------------------
*
***
*****
*******
*********
Your b and c loops are just doing a manual lpad really.

When I took your code in my editor I first noticed, you tried to increase a before starting the loop, and Oracle gives first error at that point. And also it does not allow you to increase counter variable in for loop, (I don't know why) I checked on the internet and found that you can not set increment step for Oracle for loops also you can not set a value for counter variable in for loop.
The code below works fine for me :
declare
n number(2):=5;
temp number(2):=n;
begin
for a in 1..5
loop --a:=a+1;
for b in 1..temp loop
--b:=b+1;
dbms_output.put_line(' ');
temp:=temp-1;
end loop;
for c in 1..2*a-1 loop
--c:=c+1;
dbms_output.put_line('*');
end loop;
end loop;
end;
/

As StephaneM says, loop variables are incremented by the loop itself: you don't need to do a := a + 1, and most of all you CAN'T assign them ! Here is a corrected version:
declare
n number(2):=5;
temp number(2):=n;
begin
for a in 1..5
loop
for b in 1..temp loop
dbms_output.put_line(' ');
temp:=temp-1;
end loop;
for c in 1..2*a-1 loop
dbms_output.put_line('*');
end loop;
end loop;
end;
/

Related

PL/SQL CREATE PROCEDURE - factorial

I don't know what's wrong with my code block to create a procedure to find out the factorial of a number. Thank you.
Question 1: write a stored procedure that gets an integer number n and calculates and displays its factorial.
SET SERVEROUTPUT ON;
CREATE OR REPLACE PROCEDURE factorial_number(
n NUMBER) AS
factorial NUMBER;
num Number;
BEGIN
FOR i IN REVERSE 1..n LOOP
num := i - 1;
factorial := factorial * num;
END LOOP;
DBMS_OUTPUT.PUT_LINE (factorial);
EXCEPTION
WHEN OTHERS
THEN DBMS_OUTPUT.PUT_LINE ('Error!');
END;
/
BEGIN
factorial_number(5);
END;
/
You're failing to initialize the local variable factorial. Uninitialized variables are initially null and multiplying null by any value produces null.
I don't see why you'd want your loop to go in reverse order. It doesn't matter since multiplication is communitive but it it unlikely to make your code easier to read/ debug/ follow.
You don't want to subtract 1 from the value you are multiplying by on each iteration of the loop. When i = 1, for example, you're multiplying factorial by 0 which means that (assuming you initialize factorial), you'd always end up with 0. You want to multiply by i so there is no need for the local variable num.
If I make those fixes
CREATE OR REPLACE PROCEDURE factorial_number(
n NUMBER)
AS
factorial NUMBER := 1; -- Initialize
BEGIN
FOR i IN 1..n LOOP -- Loop normally
dbms_output.put_line( 'Beginning iteration ' || i || ' of loop. ' ||
'Factorial = ' || factorial ||
' multiplying by ' || i );
factorial := factorial * i; -- Multiply by i not i-1
END LOOP;
DBMS_OUTPUT.PUT_LINE (factorial);
EXCEPTION
WHEN OTHERS
THEN DBMS_OUTPUT.PUT_LINE ('Error!');
END;
Then
BEGIN
factorial_number(5);
END;
will print out 120 (5*4*3*2*1).
I'm also adding an additional dbms_output line to print out the current state of the variables on each iteration of the loop. That's a relatively old-school method of debugging. In the modern world, you'd walk through the code with a debugger where you can see the values of your local variables but introductory classes may not teach debugger usage initially.
How about
SQL> CREATE OR REPLACE PROCEDURE factorial_number (n NUMBER)
2 AS
3 factorial NUMBER := 1;
4 BEGIN
5 FOR i IN 1 .. n
6 LOOP
7 factorial := factorial * i;
8 END LOOP;
9
10 DBMS_OUTPUT.PUT_LINE (factorial);
11 END;
12 /
Procedure created.
SQL>
SQL> BEGIN
2 factorial_number (5);
3 END;
4 /
120
PL/SQL procedure successfully completed.
SQL>

Total ROWS selected in a LOOP ORACLE PL/SQL

I just want to iterate from 0 to n-1 in a oracle loop, just like this:
FOR X IN CLEEVECTOR LOOP
But am I wonder if it is possible to use a conditional like:
if X = TOTALROWS - 1 then
exit;
END IF:
There is some recomendation, or solution ?
Thank you for helping me out with this.
So you want to process all but the last value returned by the cursor. Since a cursor doesn't know how many rows it will return you can't just say "Break if this is the second-to-last row". But you can save the value to be processed, then process it on the next iteration, as in
DECLARE
strLast_ID VARCHAR2(1000) := NULL;
CURSOR CLEEVECTOR IS
select regexp_substr(p_checkboxes,'[^,]+', 1, level) ID
from dual
connect by regexp_substr(p_checkboxes, '[^,]+', 1, level) is not null;
BEGIN
FOR X IN CLEEVECTOR LOOP
IF strLast_ID IS NOT NULL THEN
-- code to process strLast_ID
END IF;
strLast_ID := X.ID;
END LOOP;
END;
Comment this extensively to explain what's going on so Those Who Follow After will be able to figure out what the intent was.

Issue with FOR loop applied with SUBSTR & INSTR operations

I've written a small program to find the position of last space in a string. If found, take all the characters before that space and keep on doind this operation until the result is of length less than or equal to 20.
But it seems the program is not working as expected.
Here is the program.
declare
v_building_nam varchar2(100) :='lnkilap Mahallesi Üntel Sok. B1Blok';
begin
FOR i in 1..10
LOOP
EXIT when length(v_building_nam) <=20;
v_building_nam := substr(v_building_nam,1,instr(v_building_nam,' ',-1));
END LOOP;
dbms_output.put_line(v_building_nam);
END;
Output :
lnkilap Mahallesi Üntel Sok.
It seems the operation just working for the first time and the loop then runs upto 10 times without the change in value. Could anyone please let me know if I'm doing anything wrong?
set serveroutput on;
declare
v_building_nam varchar2(100) :='lnkilap Mahallesi Üntel Sok. B1Blok';
begin
FOR I IN 1..10
LOOP EXIT WHEN LENGTH(V_BUILDING_NAM) <=20;
DBMS_OUTPUT.PUT_LINE(I);
v_building_nam := substr(v_building_nam,1,instr(v_building_nam,' ',-1)-1); --calculate previous position of space
DBMS_OUTPUT.PUT_LINE(V_BUILDING_NAM);
END LOOP;
dbms_output.put_line(v_building_nam);
END;
/
you are taking space as your last position you should take less 1 position
Isn't this the same as:
Find the first space in a string.
If the position > 20 take the first 20 characters. If the position < 20 take all characters up until the space.
declare
l_position pls_integer;
l_string varchar2(100) := 'Thisissomestringwith spaces';
begin
l_position := least(instr(l_string,' ')-1,20);
dbms_output.put_line(substr(l_string,1,l_position));
end;

NO_DATA_FOUND EXCEPTION in two cursors - Oracle PL/SQL

I need to be able to raise an exception as the title says. The exception I have currently gives me this error:
DBMS_OUTPUT.PUT_LINE (‘No rows found’);
*
ERROR at line 39:
ORA-06550: line 39, column 23:
PLS-00103: Encountered the symbol "`" when expecting one of the following:
( ) - + case mod new not null others <an identifier>
<a double-quoted delimited-identifier> <a bind variable>
table avg count current exists max min prior sql stddev sum
variance execute multiset the both leading trailing forall
merge year month DAY_ hour minute second timezone_hour
timezone_minute timezone_region timezone_abbr time timestamp
interval date
<a string literal with character set specification>
This is my code:
SET SERVEROUTPUT ON FORMAT WRAP SIZE 12000
Declare
v_model VARCHAR2(40);
v_carcategory VARCHAR2(40);
v_totalcars NUMBER;
v_maxdate DATE:=TO_DATE(1, 'J');
Cursor carcur IS
SELECT * FROM i_car;
CURSOR c1(v_car_registration VARCHAR2) IS
SELECT * from i_booking a
WHERE a.registration=v_car_registration;
Begin
For car_rec in carcur
LOOP
v_maxdate:=TO_DATE(1, 'J');
for rec in c1(car_rec.registration)
LOOP
IF rec.date_reserved > v_maxdate
then
v_maxdate:=rec.date_reserved ;
If car_rec.Cost <=50000 THEN v_carcategory := 'Budget Car';
End IF;
If car_rec.Cost BETWEEN 50000 AND 100000 THEN v_carcategory := 'Standard Car';
End IF;
If car_rec.Cost >=100000 THEN v_carcategory := 'Premium Car';
End If;
end IF;
v_totalcars := findtotalcarmodels (car_rec.model_name);
end loop;
DBMS_OUTPUT.PUT_LINE('Registration:'|| ' '|| car_rec.registration);
DBMS_OUTPUT.PUT_LINE('Cost:'|| ' $' || car_rec.Cost);
DBMS_OUTPUT.PUT_LINE('Model Name:'|| ' '|| car_rec.model_name);
DBMS_OUTPUT.PUT_LINE('Car Category:'|| ' '||v_carcategory);
DBMS_OUTPUT.PUT_LINE('Total number of Cars:'|| ' '||v_totalcars);
DBMS_OUTPUT.PUT_LINE('Most Recent Rental Date: '|| ' '||v_maxdate);
DBMS_OUTPUT.NEW_LINE;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE (‘No rows found’);
END;
/
I am not overly sure about the positioning of my exception. Any advice would be much appreciated.
The immediate problem is that your dbms_output.put_line call is using Microsoft curly quotes rather than the standard single-quote character to delimit the string. You need to use the standard character, not the Microsoft character (all your other strings appear to be using the standard character).
Taking a step back, it makes no sense to catch a no_data_found exception only to call dbms_output. There is no guarantee that data written to dbms_output will ever be sent to the client application or that the client application will ever display it to a user.
It also doesn't appear that you have any code that would potentially raise a no_data_found exception. Opening a cursor that returns 0 rows does not result in an exception. If you expected to receive exactly 1 row, you could write a SELECT INTO statement and that would raise an exception if anything other than 1 row was returned. If your goal here is to determine how many times you iterated through the loop, you could potentially use the %rowcount attribute of the cursor.
Finally, it would make thinks much clearer if you formatted your code so that lines were indented based on what block they were a part of. Code like
FOR rec IN cursor
LOOP
IF <<something>>
THEN
<<do something>>
END IF;
IF <<something else>>
THEN
<<something else>>
END IF;
<<more stuff>>
END LOOP;
is much easier to follow.

How to properly output PL/SQL

I have two blocks of code inside a .sql file. One block is a function and another is a procedure. In the first block, I'm running a query and printing it out to the screen (I'm using DBMS_OUTPUT.PUT_LINE() ) for each row in it's own line. Then the procedure has another query which needs to be printed on the same line (I'm using DBMS_OUTPUT.PUT() ). When I use DBMS_OUTPUT.PUT() for the second block, it screws up the first block for some reason and the first block never prints.
Here's a link to the code: http://pastebin.com/z29emmBJ
(The relevant part of the code is around lines: 97-103)
When I have DBMS_OUTPUT.PUT_LINE() being used inside of the procedure, everything displays properly, but when I have DBMS_OUTPUT.PUT() inside of the procedure, it looks like the function never gets called.
Here's what the output looks like with PUT_LINE(): http://i.imgur.com/AnCv9.png
Here's what the output looks like with just PUT(): http://i.imgur.com/Jv3SV.png
I think it has something to do with the buffer size, but I'm not exactly sure what/why.
Any help would be greatly appreciated!
Why don't you just append the results to a VARCHAR2 variable as needed, then put_line that string when the row is completed? That way you have control over the formatting.
Snippet of the code of your Second stored procedure:
FOR player IN rows LOOP
currentCount := maxCount;
DBMS_OUTPUT.PUT(player.FIRSTNAME || ' ' || player.LASTNAME || ':' || player.points || ' ');
--DBMS_OUTPUT.NEW_LINE();
END LOOP;
If you want that the resulting output appeared as a one line you should move DBMS_OUTPUT.NEW_LINE() outside the loop (after the loop). So your code would look like:
FOR player IN rows LOOP
currentCount := maxCount;
DBMS_OUTPUT.PUT(player.FIRSTNAME || ' ' || player.LASTNAME || ':' || player.points || ' ');
END LOOP;
DBMS_OUTPUT.NEW_LINE();
Keeping DBMS_OUTPUT.NEW_LINE(); inside the loop after DBMS_OUTPUT.PUT you just emulating DBMS_OUTPUT.PUT_LINE procedure.
SQL> create or replace procedure output1
2 is
3 l_str varchar2(100);
4 l_status number;
5 begin
6 for i in 1..7
7 loop
8 dbms_output.put('Text_' || To_char(i));
9 dbms_output.new_line;
10 end loop;
11 end;
12 /
Procedure created
SQL>
SQL> create or replace procedure output2
2 is
3 l_str varchar2(100);
4 l_status number;
5 begin
6 for i in 1..7
7 loop
8 dbms_output.put('Text_' || To_char(i));
9 end loop;
10 dbms_output.new_line;
11 end;
12 /
Procedure created
SQL> exec output1;
Text_1
Text_2
Text_3
Text_4
Text_5
Text_6
Text_7
PL/SQL procedure successfully completed
SQL> exec output2;
Text_1Text_2Text_3Text_4Text_5Text_6Text_7
PL/SQL procedure successfully completed
In your code:
SET serveroutput ON size 32000;
REM Change output file name TO proj3-NetID.OUT!
SPOOL proj3-hgeorge3.OUT;
exec DBMS_OUTPUT.enable('100000000');
If serveroutput option is used (set to ON) then there is no need of calling DBMS_OUTPUT.enable procedure. And if it happens to call DBMS_OUTPUT.enable then the value of numeric data type should be passed in as a parameter not a string. Yes there will be implicit conversion of data types but it's better to avoid it. And maximum size of the buffer is 1 million.