How to properly output PL/SQL - 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.

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>

Number of rows inserted/updated in utl_file

How do i print number of rows in utl_file.
If i am using dbms_output.put_line('Total record'||'|'||SQL%ROWCOUNT);
and
If i am ​using dbms_output.put_line('Total record'||'|'||To_char(SQL%ROWCOUNT));
Compiler saying wrong argument is passed. nothing is reflecting
and
utl_file.put(file_handele,'total roecord' ||'|'|| SQL%ROWCOUNT);
only total record is reflecting.
please help new to psql
What do you call "number of rows in utl_file"?
You said that 1st and 2nd statements are invalid because compiler is complaining. It is not for me, so I kind of doubt what you claim:
SQL> begin
2 -- your 1st statement:
3 dbms_output.put_line('Total record'||'|'||SQL%ROWCOUNT);
4
5 -- your 2nd statement:
6 dbms_output.put_line('Total record'||'|'||To_char(SQL%ROWCOUNT));
7 end;
8 /
Total record|
Total record|
PL/SQL procedure successfully completed.
As of the 3rd statement:
SQL> declare
2 file_handele utl_file.file_type;
3 begin
4 file_handele := utl_file.fopen('EXT_DIR', 'test.txt', 'w');
5
6 -- your 3rd statement, literally, with typos:
7 utl_file.put(file_handele,'total roecord' ||'|'|| SQL%ROWCOUNT);
8
9 utl_file.fclose(file_handele);
10 end;
11 /
PL/SQL procedure successfully completed.
SQL> $type c:\temp\test.txt
total roecord|
SQL>
All 3 statements are OK (i.e. they don't fail, they don't raise an error), but the question is: which problem are you trying to solve? Display number of rows written into a file using UTL_FILE package? If so, you should have posted that piece of code as well.
Anyway: one option is to literally count them. For example:
SQL> declare
2 i number := 0; -- this is the counter
3 file_handele utl_file.file_type;
4 begin
5 file_handele := utl_file.fopen('EXT_DIR', 'test.txt', 'w');
6
7 for cur_r in (select dname from dept) loop
8 utl_file.put(file_handele, cur_r.dname || utl_tcp.crlf);
9 i := i + 1; -- increment the counter
10 end loop;
11
12 -- your 3rd statement, literally, with typos:
13 utl_file.put(file_handele,'total roecord' ||'|'|| i);
14
15 utl_file.fclose(file_handele);
16 end;
17 /
PL/SQL procedure successfully completed.
The result:
SQL> $type c:\temp\test.txt
ACCOUNTING
RESEARCH
SALES
OPERATIONS
total roecord|4 --> here's the total number of lines written into the file
SQL>

Unable to get large data i.e. XML from oracle table using Procedure

I am tring to create update statement for a column which datatype is CLOB.
For this i am fetching XML from table and writing it in oracle console for later use.
For some of the data it is working fine.
For some i am getting error as
Exception1 : CREATE_UPDATE_XML_QUERY('600264','700009');
--ORA-06502: PL/SQL: numeric or value error
I have changed datatype of V_XML,V_BLOCK nothing has worked
PROCEDURE CREATE_UPDATE_XML_QUERY
(
MY_ID NUMBER,
MY_ID2 NUMBER
) AS
V_SCREEN_VERSION NUMBER;
V_XML_ID NUMBER;
V_CNT NUMBER;
V_XML CLOB);
V_BLOCK CLOB;
BEGIN
SELECT XML,XMLID INTO V_XML,V_XML_ID
FROM XML_TABLE WHERE ENC_ID = MY_ID AND SCREEN_ID = MY_ID2 ; ----getting excption
V_BLOCK :=
'
SET SERVEROUTPUT ON;
DECLARE
V_XML CLOB ;
BEGIN
';
V_BLOCK := V_BLOCK||'V_XML := '''||V_XML||''';';
V_BLOCK := V_BLOCK||'
UPDATE XML_TABLE SET XML = '||'V_XML'||'
WHERE ENC_ID = '||MY_ID||' AND ENC_TYPE = ''P'' AND SCREEN_ID = '||MY_ID2||' AND XMLID = '||V_XML_ID||';
--DBMS_OUTPUT.PUT_LINE(''V_XML =>''||V_XML);
DBMS_OUTPUT.PUT_LINE(''ROWCOUNT =>''||SQL%ROWCOUNT);
END;
/';
DBMS_OUTPUT.PUT_LINE('--Printing Annomous Block the XML :->>');
DBMS_OUTPUT.PUT_LINE(V_BLOCK);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Exception1 : UPDATE_SCREEN_MASTER_XML('''||MY_ID||''','''||MY_ID2||''','''||V_XML_ID||'''); --'||SQLERRM);--'||SQLERRM);
END CREATE_UPDATE_XML_QUERY;
How can i avoid error.
Is it because my XML is too big.
Well, I've come up with a test case to reproduce this (Oracle 12.2.0.1), and you're right, the problem isn't the DBMS_OUTPUT line.
declare
v_clob clob;
xmlid number;
begin
-- initialize clob and make clob a string of length 32768
dbms_lob.createtemporary(v_clob, true);
for i in 1..32768 loop
v_clob := v_clob || 'x';
end loop;
dbms_output.put_line(length(v_clob));
-- testing:
v_clob := v_clob || 'x'; -- appending a varchar2 works fine
v_clob := v_clob || xmlid; -- appending a number gives ORA-06502
v_clob := v_clob || 'x' || xmlid; -- appending a string+number still gives ORA-06502
v_clob := v_clob || to_clob(xmlid); -- works fine
dbms_lob.append(v_clob, 'x' || xmlid); -- also works fine
dbms_output.put_line(length(v_clob));
dbms_output.put_line(substr(v_clob,1,32767));
end;
/
The problem seems to be that when you concatenate strings with pipes, Oracle can append 2 clobs together if one of them is over 32k, and it can implicitly convert a varchar2 to a clob and append them. But if you try to append a number to a clob over 32k, it fails. It understands how to append varchar2 and number, and clob and clob, and clob and varchar2. But it can't seem to automatically figure out how to do number -> varchar2 -> clob. You can fix it by wrapping the string in to_clob(), avoiding the issue with Oracle's implicit conversion.
Where you get the error depends on the length of your table's XML CLOB value.
If the XML is more than 32k then you'll see the error at line 27 in your code, from trying to concatenate a varchar2 string a number (as #kfinity demonstrated) onto a CLOB; behaviour that isn't explained in the documentation, but is presumably something to do with implicit conversion since just explicitly converting the numbers with to_char(MY_ID) (or to_clob(MY_ID)).
If the XML is less than, but close to, 32k then you'll just scrape past that, but the V_BLOCK CLOB still ends up as greater than 32k, then will still error at line 39 as dbms_output can't handle that.
You can avoid the first problem by using to_char() around the numeric variables, or by using dbms_lob.append instead of concatenation:
...
V_BLOCK :=
'
SET SERVEROUTPUT ON;
DECLARE
V_XML CLOB ;
BEGIN
';
dbms_lob.append(V_BLOCK, 'V_XML := '''||V_XML||''';');
dbms_lob.append(V_BLOCK, '
UPDATE XML_TABLE SET XML = '||'V_XML'||'
WHERE ENC_ID = '||MY_ID||' AND ENC_TYPE = ''P'' AND SCREEN_ID = '||MY_ID2||' AND XMLID = '||V_XML_ID||';
--DBMS_OUTPUT.PUT_LINE(''V_XML =>''||V_XML);
DBMS_OUTPUT.PUT_LINE(''ROWCOUNT =>''||SQL%ROWCOUNT);
END;
/');
...
And you can avoid the second problem, as long as your XML value contains line breaks, by splitting the CLOB into lines, as shown here, but with a slight modification to handle empty lines; with additional variables declared as:
V_BUFFER VARCHAR2(32767);
V_AMOUNT PLS_INTEGER;
V_POS PLS_INTEGER := 1;
then instead of:
DBMS_OUTPUT.PUT_LINE(V_BLOCK);
you can do:
WHILE V_POS < length(V_BLOCK) LOOP
-- read to next newline if there is one, rest of CLOB if not
IF dbms_lob.instr(V_BLOCK, chr(10), V_POS) > 0 THEN
V_AMOUNT := dbms_lob.instr(V_BLOCK, chr(10), V_POS) - V_POS;
IF V_AMOUNT = 0 THEN
V_BUFFER := null; -- first character is a new line (i.e. a blank line)
ELSE
dbms_lob.read(V_BLOCK, V_AMOUNT, V_POS, V_BUFFER);
END IF;
V_POS := V_POS + V_AMOUNT + 1; -- skip newline character
ELSE
V_AMOUNT := 32767;
dbms_lob.read(V_BLOCK, V_AMOUNT, V_POS, V_BUFFER);
V_POS := V_POS + V_AMOUNT;
END IF;
DBMS_OUTPUT.PUT_LINE(V_BUFFER);
END LOOP;
db<>fiddle
#VinayakDwivedi edited to add a function to use instead:
PROCEDURE print_clob_to_output (p_clob IN CLOB)
IS
v_offset NUMBER := 1;
v_chunk_size NUMBER := 10000;
BEGIN
LOOP
EXIT WHEN v_offset > DBMS_LOB.getlength (p_clob);
DBMS_OUTPUT.put_line (
DBMS_LOB.SUBSTR (p_clob, v_chunk_size, v_offset));
v_offset := v_offset + v_chunk_size;
END LOOP;
END print_clob_to_output;
... but this will introduce extra line breaks every 10000 characters.
However, it's worth noting that the PL/SQL block you are generating within that ends up with a line like:
V_XML := '<original xml from table>';
and if that generated code is run it will also error if the original XML is more that 32k. Really that generated code also needs to be broken up to reconstruct your CLOB in chunks - i.e. a loop that takes 32k at a time and concatenates/appends those chunks to reconstitute the full value. It also has whitespace at the start of each line so the DECLARE etc. and more importantly the final / are not at the start of their respective lines, which will also cause problems when trying to run it as-is.
Check out:
https://www.techonthenet.com/oracle/errors/ora06502.php
This suggests that there may be 3 possible causes to this error
Number to big - I doubt if this is your issue, as the max numbers are pretty huge
Conversion error - you are trying to convert a non number to a number
Assigning NULL to a NOT NULL constrained variable - self explantory
without knowing more of the context it is not really possible to determine which of these is your issue.
Hope this helps!

ERROR: Reference the counter as the target of an assignment - PL/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;
/

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;