Error While generating pascal's triangle in plSQL - sql

well I'm pretty new to plSQL and I can't seem to figure out some errors I'm getting while trying to generate a pascal's triangle
Here's the code:
SET SERVEROUTPUT ON;
DECLARE
N NUMBER;
I NUMBER;
J NUMBER;
K NUMBER;
L NUMBER;
T NUMBER;
S NUMBER;
BEGIN
DBMS_OUTPUT.PUT_LINE('ENTER THE LIMIT');
N:=&N;
I:=0;
T:=N-2;
S:=0;
L:=0;
WHILE (I<N) LOOP
J:=0;
WHILE (J<T) LOOP
DBMS_OUTPUT.PUT(' ');
J:=J+1;
END LOOP;
K:=0;
WHILE (K<=S) LOOP
IF (K<=I) THEN
DBMS_OUTPUT.PUT(K+1);
L:=K;
ELSE
L:=L-1;
DBMS_OUTPUT.PUT(L+1);
END IF;
K:=K+1;
END LOOP;
DBMS_OUTPUT.PUT_LINE('');
T:=T-1;
S:=S+2;
END LOOP;
END;
The errors are
Error report -
ORA-20000: ORU-10027: buffer overflow, limit of 1000000 bytes
ORA-06512: at "SYS.DBMS_OUTPUT", line 32
ORA-06512: at "SYS.DBMS_OUTPUT", line 97
ORA-06512: at line 29
20000. 00000 - "%s"
*Cause: The stored procedure 'raise_application_error'
was called which causes this error to be generated.
*Action: Correct the problem as described in the error message or contact
the application administrator or DBA for more information.

Neither I nor N are ever updated, so you have an infinite loop. Other than that, the error is indicating that the DBMS_OUTPUT buffer is being exceeded. It's default buffer size is 1,000,000 bytes, though you can adjust that with an appropriate DBMS_OUTPUT.ENABLE call or SET SERVEROUTPUT SIZE statement.

The problem you're having has to do with SQL*Plus, the Oracle SQL client, rather than PL/SQL, the programming language. Change the line which says SET SERVEROUTPUT ON to
SET SERVEROUTPUT ON SIZE UNLIMITED
This error may, however, indicate you've got an infinite loop, so be careful. Note that I never appears to be incremented and N is never decremented, so the loop started at WHILE (I<N) LOOP may not terminate.
Best of luck.

Related

Toad oracle 10.5 [duplicate]

I have this anonymous PL/SQL block which calculates and prints a value return from a table.
DECLARE
U_ID NUMBER :=39;
RETAIL BINARY_FLOAT:=1;
FLAG NUMBER;
BEGIN
SELECT NVL(RETAIL_AMOUNT,1),UNIT_ID INTO RETAIL, FLAG FROM UNITS WHERE UNIT_ID=U_ID;
LOOP
SELECT NVL(MAX(UNIT_ID),U_ID) INTO FLAG FROM UNITS WHERE FATHER_ID=FLAG;
IF FLAG=U_ID THEN EXIT; END IF;
SELECT RETAIL* RETAIL_AMOUNT INTO RETAIL FROM UNITS WHERE UNIT_ID=FLAG;
EXIT WHEN FLAG=U_ID;
END LOOP;
DBMS_OUTPUT.PUT_LINE( RETAIL);
END;
This block work correctly, but I wanted to do the same thing using a PL/SQL Function
I wrote the function as follow:
CREATE OR REPLACE FUNCTION GET_UNIT_RETAIL(U_ID NUMBER)
RETURN NUMBER
IS
RETAIL BINARY_FLOAT:=1;
FLAG NUMBER;
BEGIN
SELECT NVL(RETAIL_AMOUNT,1),UNIT_ID
INTO RETAIL, FLAG
FROM UNITS
WHERE UNIT_ID=U_ID;
LOOP
SELECT NVL(MAX(UNIT_ID),U_ID)
INTO FLAG
FROM UNITS
WHERE FATHER_ID=FLAG;
IF FLAG=U_ID THEN
EXIT;
END IF;
SELECT RETAIL* RETAIL_AMOUNT
INTO RETAIL
FROM UNITS
WHERE UNIT_ID=FLAG;
EXIT WHEN FLAG=U_ID;
END LOOP;
RETURN NUMBER;
END;
/
When I try to execute the above code to save the function to the database, the environment (SQL*PLUS) hangs for a long time and at the end returns this error:
ERROR at line 1:
ORA-04021: timeout occurred while waiting to lock object
What is the problem ??? Please !
Sounds like ddl_lock problem
Take a look at
dba_ddl_locks to see who is "blocking" a create or replace.
Also try to create under different name - and see what happens.
The problem was because the Object GET_UNIT_RETAIL was busy by other environment
Here is the answer:
https://community.oracle.com/thread/2321256

raise_application_error exception handling in subprocedures

I have multiple stored procedures calling multiple stored procedures in my database. To give a small example, I've constructed a fictionalised version of a few of them below. In my example, a Java program calls calculate_bill, which calls calculate_commission, which calls update_record.
I'm hoping to get some advice on how best to propagate the error messages up the stack to the calling Java layer, so the user gets a precise error message corresponding to wherever the error has occurred.
I'm really quite stuck on this. I've played around in my example with raise_application_error and just continually shuffling it up the stack. Is the way I'm doing it below remotely correct? Or is one raise_application_error in the relevant procedure enough, with no pragma exception init etc needed?
To give an idea of what I mean, in the example below, if a user entered a number which corresponded to a record which couldn't be updated because it didn't exist, I'd like them to get the message:
"Error calculating bill. Error calculating commission. No record exists to be updated" or something to that effect.
So two questions:
What is the best practice, most efficient, most tidy way to pass error messages up the stack for the end user in the application layer?
Does anyone have any suggestions on tidier output from the code, i.e. the best way to concatenate these errors to make them more meaningful? I'm really open to any suggestions on how to make this work best as I have absolutely no prior experience in this.
Example:
(Error in code):
-20000 : Error in top level procedure
-20001 : Error in middle level procedure
-20002 : Error in bottom level procedure
Java code:
try {
// call calculate_bill
exception (SQLException ex)
// output oracle code and relevant message.
Oracle code:
create or replace procedure calculate_bill(in_num NUMBER)
is
error_calculating_commission EXCEPTION;
error_updating_record EXCEPTION;
PRAGMA EXCEPTION_INIT (error_calculating_commission, -20001);
PRAGMA EXCEPTION_INIT (error_updating_record , -20002);
begin
if in_num > 2 then
calculate_commission(in_num);
else
raise_application_error(-20000, 'Error calculating bill. ' || 'Record number doesn''t exist.', false);
end if;
exception
when error_calculating_commission then
raise_application_error(SQLCODE, 'Error calculating bill. ' || SQLERRM, false);
when error_updating_record then
raise_application_error(SQLCODE, 'Error calculating bill. ' || SQLERRM, false);
when others then
raise_application_error(-20000, 'Unknown error encountered calculating bill.', false);
end;
create or replace procedure calculate_commission(in_num NUMBER)
is
begin
if in_num < 30 then
raise_application_error(-20001, 'Number too small to calculate commission.', false);
elsif in_num >= 30 and < 40 then
declare
error_storing_record EXCEPTION;
PRAGMA EXCEPTION_INIT (error_storing_record , -20002);
begin
update_record(in_num);
exception
when error_storing_record then
raise_application_error(SQLCODE, 'Error calculating commission. ' || SQLERRM, false);
when others then
raise_application_error(-20001, 'Unknown error encountered calculating commission.', false);
else
raise_application_error(-20001, 'Number too large to calculate commission', false);
end if;
end;
create or replace procedure update_record(in_num NUMBER)
is
begin
//some SQL query with a where clause, where in_num equals something
exception
when no_data_found then
raise_application_error(-20002, 'No record exists to be updated', false);
when others then
raise_application_error(-20002, 'Unknown error encountered updating record.', false);
end if;
end;
Note: I know this example is a little contrived. I was just trying to keep it brief.
The way I would implement this is to use RAISE_APPLICATION_ERROR where the error actually originates, then leave it unhandled in the other layers (or, if you want to do logging in the database, catch it in the OTHERS section, then re-raise using RAISE rather than RAISE_APPLICATION_ERROR.
You're OTHERS sections are still perpetuating the problem raised in your previous question: when an unknown error occurs you're replacing it with a generic, unhelpful message. To paraphrase Tom Kyte, "a WHEN OTHERS that does not end in RAISE is a bug`.
With no logging in the database, I would re-write the provided code like this:
create or replace procedure calculate_bill(in_num NUMBER)
is
begin
if in_num > 2 then
calculate_commission(in_num);
else
raise_application_error(-20000, 'Error calculating bill. ' || 'Record number doesn''t exist.', false);
end if;
end;
create or replace procedure calculate_commission(in_num NUMBER)
is
begin
if in_num < 30 then
raise_application_error(-20001, 'Number too small to calculate commission.', false);
elsif in_num >= 30 and in_num < 40 then
update_record(in_num);
else
raise_application_error(-20001, 'Number too large to calculate commission', false);
end if;
end;
create or replace procedure update_record(in_num NUMBER)
is
v_stub number;
begin
select 1 into v_stub from dual where 1 = 0;
exception
when no_data_found then
raise_application_error(-20002, 'No record exists to be updated', false);
end;
Below is sample input and the stack traces they created:
exec calculate_bill(0)
ORA-20000: Error calculating bill. Record number doesn't exist.
ORA-06512: at "SCHEMANAME.CALCULATE_BILL", line 7
ORA-06512: at line 1
exec calculate_bill(10)
ORA-20001: Number too small to calculate commission.
ORA-06512: at "SCHEMANAME.CALCULATE_COMMISSION", line 5
ORA-06512: at "SCHEMANAME.CALCULATE_BILL", line 5
ORA-06512: at line 1
exec calculate_bill(35)
ORA-20002: No record exists to be updated
ORA-06512: at "SCHEMANAME.UPDATE_RECORD", line 8
ORA-06512: at "SCHEMANAME.CALCULATE_COMMISSION", line 7
ORA-06512: at "SCHEMANAME.CALCULATE_BILL", line 5
ORA-06512: at line 1
exec calculate_bill(100)
ORA-20001: Number too large to calculate commission
ORA-06512: at "SCHEMANAME.CALCULATE_COMMISSION", line 9
ORA-06512: at "SCHEMANAME.CALCULATE_BILL", line 5
ORA-06512: at line 1
If you were to add error logging to these procedures, it would be a simple matter of adding an OTHERS clause to the EXCEPTION sections:
WHEN OTHERS THEN
my_logging (SQLCODE, SQLERRM, DBMS_UTILITY.format_error_backtrace ());
RAISE;
The new exception created by your RAISE_APPLICATION_ERROR calls will be handled transparently by the RAISE.

UTL_FILE.WRITE_ERROR when calling utl_file.put in a loop

I have below code in my PL/SQL procedure, which I called in API_XXX.put(it calls utl_file.put) in a while loop. And the l_xmldoc is CLOB from a function of getReportXML, which returns the xml clob.
the code I write to write xml into a file is like:
l_offset := 1;
WHILE (l_offset <= l_length)
LOOP
l_char := dbms_lob.substr(l_xmldoc,1,l_offset);
IF (l_char = to_char(10)) ---I also tried if (l_char=chr(10)) but it did not work
THEN
API_XXXX.new_line(API_XXX.output, 1);
ELSE
API_XXXX.put(fnd_API_XXX.output, l_char);
END IF;
l_offset := l_offset + 1;
END LOOP;
Please note that the API_XXX is the existing package which I am not able to modify, and this api calls fflush in the end of put.
API_XXX.put's part is like below("WHICH" is the first param):
elsif WHICH = API_XXX.OUTPUT then
temp_file := OUT_FNAME;
utl_file.put(F_OUT, BUFF);
utl_file.fflush(F_OUT);
API_XXX.new_line is like(LINES is the number of lines to write):
elsif WHICH = API_XXX.OUTPUT then
temp_file := OUT_FNAME;
utl_file.new_line(F_OUT, LINES);
utl_file.fflush(F_OUT);
I notice a that the put/new_line procedure in my customer's side will sometimes raise UTL_FILE.WRITE_ERROR for unknown reason(maybe due to the l_length is too large(up to 167465)) in the while loop from my customer.
I read Oracle PL/SQL UTL_FILE.PUT buffering
. And I found that this is the same cause, my l_xmldoc is really large and when I loop it, I found that it is without a new line terminator so the buffer is up to 32767 even though I fflush every time.
So, how should I convert the l_xmldoc into a varchar with new line terminator.
PS: I confirmed that my customer is using Oralce 11g
Post the Oracle Version you are using! Or we can just guess around...
Your fflush will not work as you expect - From the documentation:
FFLUSH physically writes pending data to the file identified by the file handle. Normally, data being written to a file is buffered. The FFLUSH procedure forces the buffered data to be written to the file. The data must be terminated with a newline character.
tbone is abolutely right the line TO_CHAR(10) is wrong! Just try SELECT TO_CHAR(10) FROM DUAL; you will get 10 which you then compare to a single character. A single character will never be '10' since 10 has two characters!
Your problem is most likely a buffer-overflow with too large XML-Files, but keep in mind, also other problems on the target system can lead to write_errors, which should be handled.
Solutions
Quick&Dirty: Since you don't seem to care about performance anyways you can just close the file every X byte and reopen it with A for append. So just add to the loop:
IF MOD( l_offset, 32000 ) = 0
THEN
UTL_FILE.FCLOSE( f_out );
UTL_FILE.FOPEN( out_fpath, out_fname, f_out, 'a', 32767 );
END IF;
Use the right tool for the right job: UTL_FILE is not suited for handling complex data. The only usecase for UTL_FILE are small newline-separated lines of text. For everything else you should write RAW bytes! (Which will also allow you porper control over ENCODING, which is currently just mini-vanilly-lucky-guess)
Write a Java-Stored-Procedure with NIO-Filechannels - fast, safe, nice... But be careful, your program might run 10 times as fast!
Just a guess, but instead of "to_char(10)" you might try chr(10) to determine/write a newline. Not sure if this will solve your problem, but sometimes very long lines (without newlines) can cause issues.
For example:
declare
l_clob clob;
l_char char;
begin
l_clob := 'Line 1' || chr(10) || 'Line 2' || chr(10);
for i in 1 .. DBMS_LOB.GETLENGTH(l_clob)
loop
l_char := dbms_lob.substr(l_clob, 1, i);
if (l_char = chr(10)) then
--if (l_char = to_char(10)) then
dbms_output.put_line('Found a newline at position ' || i);
end if;
end loop;
end;
Notice the difference between chr(10) and to_char(10). Easy enough to test if this solves your problem anyway.

Character string buffer too small error in Oracle Stored Procedure

I am getting an error in an Oracle 11g stored procedure. The error is...
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
It is happening at line 31, the line that contains out_cnt_tot := 0; I'm really not sure why there is anything wrong with that line. Another programmer created this procedure and I'm really not familiar with SQL procedures. Can anyone help me figure this out?
create or replace
PROCEDURE "FIP_BANKREC_PREP"
(
in_file_date in varchar2,
in_bank_code in varchar2,
out_cnt_apx_miss_no out integer,
out_cnt_prx_miss_no out integer,
out_cnt_apx_no_mtch out integer,
out_cnt_prx_no_mtch out integer,
out_cnt_ap_dup out integer,
out_cnt_pr_dup out integer,
out_cnt_bad out integer,
out_cnt_ap_load out integer,
out_cnt_pr_load out integer,
out_cnt_ap_not_load out integer,
out_cnt_pr_not_load out integer,
out_cnt_tot out integer,
out_message out varchar2
) as
file_date date;
ap_acct_no varchar2(16);
pr_acct_no varchar2(16);
-- ------------------------------------------------------
-- begin logic
-- ------------------------------------------------------
begin
file_date := to_date(in_file_date,'yyyymmdd');
out_cnt_tot := 0; --- THE ERROR IS ON THIS LINE ---
out_message := 'Test Message';
select brec_acct_code into ap_acct_no
from MSSU.zwkfi_bankrec_accts
where brec_acct_bank = in_bank_code
and brec_acct_type = 'AP';
select brec_acct_code into pr_acct_no
from MSSU.zwkfi_bankrec_accts
where brec_acct_bank = in_bank_code
and brec_acct_type = 'PR';
// The rest of the procedure...
Simple demo of the scenario mentioned in comments:
create or replace procedure p42(out_message out varchar2) as
begin
out_message := 'Test message';
end p42;
/
If I call that with a variable that is declared big enough, it's fine. I have a 12-char variable, so assigning a 12-char value is not a problem:
declare
msg varchar2(12);
begin
p42(msg);
end;
/
anonymous block completed
But if I make a mistake and make the caller's variable too small I get the error you're seeing:
declare
msg varchar2(10);
begin
p42(msg);
end;
/
Error report:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "STACKOVERFLOW.P42", line 3
ORA-06512: at line 4
06502. 00000 - "PL/SQL: numeric or value error%s"
*Cause:
*Action:
The error stack shows both the line in the procedure that errored (line 3), and the line in the caller that triggered it (line 4). Depending on where you're calling it you might not have the whole stack, of course.
You mentioned that there would be various error messagesin the future. You need to make sure that anything that ever calls this defines the variables to be big enough to cope with any of your messages. If they were stored in a table you could semi-automate that, otherwise it'll be a manual code review check.
OK, saw your c# comment after posting this. It looks like you're calling this constructor; that doesn't say what default size it gets, but not unreasonable to think it might be 1. So you need to call this constructor instead to specify the size explicitly:
OracleParameter(String, OracleType, Int32)
Initializes a new instance of the OracleParameter class that uses the parameter name,
data type, and length.
... something like:
OracleParameter prm15 = new OracleParameter("out_str_message",
OracleDbType.Varchar2, 80);
Unless there's a way to reset the size after creation, which I can't see. (Not something I've ever used!).

Monitoring long-running PL/SQL block

I have a fairly time intensive PL/SQL block that builds fingerprints from molecular structures. I would like to print output to SQL*Plus console to provide feedback on how many structures have been processed. I can do this with dbms_output.put_line
However everytime that is called a new line is written. I want to overwrite the line.
For example, currently I have the below.
Structure x of y processed
Structure x of y processed
Structure x of y processed
Structure x of y processed
Eventually I fill up the buffer as I'm dealing with thousands of structure records.
Is there a method I can use that will just overwrite the last output line?
Using DBMS_OUTPUT means that SQL*Plus will display nothing until the entire PL/SQL block is complete and will then display all the data currently in the buffer. It is not, therefore, an appropriate way to provide an ongoing status.
On the other hand, Oracle does provide a package DBMS_APPLICATION_INFO that is specifically designed to help you monitor your running code. For example, you could do something like
CREATE PROCEDURE process_structures
AS
<<other variable declarations>>
rindex BINARY_INTEGER;
slno BINARY_INTEGER;
totalwork NUMBER := y; -- Total number of structures
worksofar NUMBER := 0; -- Number of structures processed
BEGIN
rindex := dbms_application_info.set_session_longops_nohint;
FOR i IN (<<select structures to process>>)
LOOP
worksofar := worksofar + 1;
dbms_application_info.set_session_longops(
rindex => rindex,
slno => slno,
op_name => 'Processing of Molecular Structures',
sofar => worksofar ,
totalwork => totalwork,
target_desc => 'Some description',
units => 'structures');
<<process your structure with your existing code>>
END LOOP;
END;
From a separate SQL*Plus session, you can then monitory progress by querying the V$SESSION_LONGOPS view
SELECT opname,
target_desc,
sofar,
totalwork,
units,
elapsed_seconds,
time_remaining
FROM v$session_longops
WHERE opname = 'Processing of Molecular Structures';
You may also send messages to a named pipe and have another process read the message from the pipe.
procedure sendmessage(p_pipename varchar2
,p_message varchar2) is
s number(15);
begin
begin
sys.dbms_pipe.pack_message(p_message);
exception
when others then
sys.dbms_pipe.reset_buffer;
end;
s := sys.dbms_pipe.send_message(p_pipename, 0);
if s = 1
then
sys.dbms_pipe.purge(p_pipename);
end if;
end;
function receivemessage(p_pipename varchar2
,p_timeout integer) return varchar2 is
n number(15);
chr varchar2(200);
begin
n := sys.dbms_pipe.receive_message(p_pipename, p_timeout);
if n = 1
then
return null;
end if;
sys.dbms_pipe.unpack_message(chr);
return(chr);
end;
I don't think you can. As far as I understood the dbms_output it just doesn't work that way.
I recommend you use put to echo a single dot and a newline every 1000 or so entries to see that something is happening and write into a table or sequence the current position so you can have a look if you want to know.