Why do I see no output from this PL/SQL block? - sql

I'd like a code sample.
I'm tryng this:
DECLARE
var NUMBER;
BEGIN
/*N.B. for loop variables in pl/sql are new declarations, with scope only inside the loop */
FOR var IN 0 .. 10 LOOP
DBMS_OUTPUT.put_line(var);
END LOOP;
IF (var IS NULL) THEN
DBMS_OUTPUT.put_line('var is null');
ELSE
DBMS_OUTPUT.put_line('var is not null');
END IF;
END;
and getting no output (though I know it's not a infinite loop). Why is this one not printing?
edit: The not-printing code was fixed via the database manager interface.

A LOOP without an EXIT statement is one way to generate an infinite loop in PL/SQL
BEGIN
LOOP
null;
END LOOP;
END;
You could also write a WHILE loop that never ends
BEGIN
WHILE( true )
LOOP
NULL;
END LOOP;
END;

If your problem is that you are getting no output, then you may not have enabled DBMS OUTPUT yet. You can do that with:
set serveroutput on

A loop containing a DBMS_OUTPUT.PUT_LINE will not be infinite (if serveroutput is enabled) as, eventually, it will fill the entire output buffer or the available memory. The limit used to be about 1 million bytes so would get hit quite quickly. If it goes to fill up the entire computer memory, that can take quite some time.
On infinite loops, I went through a bad patch of forgetting to go to the next element in a table.
DECLARE
type typ_tab is table of varchar2(10) index by pls_integer;
t_tab typ_tab;
v_ind number;
BEGIN
t_tab(10) := 'A';
t_tab(20) := 'B';
v_ind := t_tab.first;
WHILE v_ind IS NOT NULL LOOP
dbms_output.put_line(t_tab(v_ind));
v_ind := t_tab.next(v_ind); --Forget this and it loops forever
END LOOP;
END;
Once they get into such a loop, the session may need to be killed by the DBA.

Don't know why would you need it, but:
BEGIN
WHILE 1 = 1
LOOP
NULL;
END LOOP;
END;

Related

[PLSQL]How to know the exact row which raise SAVE EXCEPTION in BULK COLLECT

I'm trying to update rows from a bulk collect with limit using forall statement. However I couldn't get the exact row which raise the exception.
-- Cursor which get the rows to update
-- Creation of the table TAB_REQ
BEGIN
OPEN c_REQ;
LOOP
FETCH c_REQ BULK COLLECT INTO TAB_REQ LIMIT 50000;
BEGIN
FORALL ii in 1 .. TAB_REQ.count SAVE EXCEPTIONS
-- Update statement where exception will be raise at id=164588
EXCEPTION
WHEN OTHERS
THEN
l_errors := sql%BULK_EXCEPTIONS.count;
FOR i IN 1 .. l_errors
LOOP
l_errno := sql%BULK_EXCEPTIONS(i).ERROR_CODE;
l_msg := sqlerrm(-l_errno);
l_idx := sql%BULK_EXCEPTIONS(i).ERROR_INDEX;
DBMS_OUTPUT.PUT_LINE(l_errno||' : '||l_msg||' - '||l_idx||', id_ext: '|| TAB_REQ(i).id_ext);
END LOOP;
END;
exit when TAB_REQ.COUNT =0;
END LOOP;
CLOSE c_REQ;
The result is that the id printed out in exception does not correspond to the id that raise error in update loop, and the number of rows updated has 50000 less, while is exactly the limit number in bulk collect.
Anyone knows the raison?
You need to use sql%bulk_exceptions. You can see an example here. Keep in mind the errors are generic (meaning for errors such as "cannot insert null into X" you won't get the column name when using sqlerrm.).
To catch the errors you must define the exception before the execution block (or at the package level), then handle it:
DECLARE
e_forall EXCEPTION;
PRAGMA EXCEPTION_INIT (e_forall, -24381);
BEGIN
... bulk insert code that generates the exception ...
EXCEPTION
WHEN e_forall THEN
FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
DBMS_OUTPUT.put_line ('Error '
|| ' on index '
|| SQL%BULK_EXCEPTIONS(i).ERROR_INDEX
|| SQLERRM (-1 * SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
END LOOP;
END;
/
What does the code generate on your side that prevents you from seeing the error/cause?
UPDATE:
OK, now that you updated the code + gave the output I think I found it.
The problem is here: TAB_REQ(i).id_ext
Try: TAB_REQ(l_idx).id_ext
ERROR_INDEX obtained from the array index is the actual record id, not the array index. So using i on the record will always return a wrong record. You need the index recovered from i.

Collections are going into loop

I want to update the table, however, collections are going into a loop. Need to update 500 000 records, but it is taking a lot of time..if something can be done..
CREATE OR REPLACE PROCEDURE PROC_ACCOUNT_STATUS AS
CURSOR C1 IS
SELECT ACCOUNTS1,
abs((PREVIOUS_DELINQUENCIES - CURRENT_DELINQUENCIES)) AS DIFF_DEL
FROM TEMP_LOAN;
TYPE COLL_ACCOUNT_STATUS IS TABLE OF C1%ROWTYPE;
COLL_STAB1 COLL_ACCOUNT_STATUS := COLL_ACCOUNT_STATUS();
COLL_STAB2 COLL_ACCOUNT_STATUS := COLL_ACCOUNT_STATUS();
BEGIN
OPEN C1;
LOOP
FETCH C1 BULK COLLECT INTO COLL_STAB2 LIMIT 500;
EXIT WHEN COLL_STAB2.COUNT = 0;
COLL_STAB1 := COLL_STAB2;
END LOOP;
CLOSE C1;
FOR I IN 1..COLL_STAB1.COUNT
LOOP
IF(COLL_STAB1(I).DIFF_DEL>=30) AND(COLL_STAB1(I).DIFF_DEL>=31) THEN
COLL_STAB1.EXTEND();
COLL_STAB1(COLL_STAB1.COUNT):=COLL_STAB1(I);
END IF;
END LOOP;
FORALL I IN 1..COLL_STAB1.COUNT
UPDATE TEMP_LOAN
SET ACCOUNT_STATUS = 'STAB'
WHERE ACCOUNTS1 = COLL_STAB1(I).ACCOUNTS1;
COLL_STAB1.DELETE;
COLL_STAB2.DELETE;
COMMIT;
END;
What is wrong with a straightforward update?
update temp_loan
set ACCOUNT_STATUS = 'STAB'
WHERE SUBSTR((PREVIOUS_DELINQUENCIES - CURRENT_DELINQUENCIES),2) >= 31
Possibly this is not the answer you want, especially if (as per my comment) the logic you posted in the question is not representative of the logic you actually are implementing.

How to execute dynamic sql into cursor in Oracle?

I have problem with execute dynamic sql statement into sys_refcursor in my stored procedure.
I have looked in documentation and I think that I build my procedure properly, but I still do not know why error occurs.
Please look below, what I created:
CREATE TABLE REKOM_CROSS_PROM (
LINIA_PROD VARCHAR2(20),
ID_REKOM_OFERTA VARCHAR2(20),
PRICE NUMBER,
MAX_PRICE NUMBER
);
/
CREATE OR REPLACE TYPE prodType AS OBJECT (
p_line VARCHAR2(20)
,p_price NUMBER
);
/
CREATE OR REPLACE TYPE prodTypeList IS TABLE OF prodType;
/
CREATE OR REPLACE PROCEDURE my_proc (prodLines IN prodTypeList ,rekom OUT SYS_REFCURSOR)
IS
v_pLine VARCHAR2(20);
v_query VARCHAR2(4000);
BEGIN
v_query := 'SELECT ID_REKOM_OFERTA FROM REKOM_CROSS_PROM WHERE
LINIA_PROD=NULL';
FOR i IN 1 .. prodLines.COUNT
LOOP
v_pLine := prodLines(i).p_line;
v_query := v_query || ' UNION ALL SELECT ID_REKOM_OFERTA FROM
REKOM_CROSS_PROM WHERE LINIA_PROD=''' || v_pLine || '''';
END LOOP;
OPEN rekom FOR v_query;
END my_proc;
/
And when I want to call the procedure, error occur.
set serveroutput on
declare
type1 prodTypeList := prodTypeList(prodType('test1',1), prodType('test2', 20));
rc SYS_REFCURSOR;
row varchar2(200);
BEGIN
MY_PROC(type1, rc);
fetch rc into row;
while (rc%found) loop
dbms_output.put_line(row);
end loop;
close rc;
end;
I get the message:
ORA-20000: ORU-10027: buffer overflow, limit of 1000000 bytes
*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.
Can sombody help me to resolve the problem?
You have an infinite loop. That means you're calling dbms_output.put_line forever - or until it runs out of buffer space, and throws that exception.
BEGIN
MY_PROC(type1, rc);
-- fetch first row from result set
fetch rc into row;
-- check if last fetch found something - always true
while (rc%found) loop
dbms_output.put_line(row);
end loop;
close rc;
end;
Every time around the loop you're checking the result of that first fetch, which stays true (assuming there is any data). You need to fetch each time round the loop:
BEGIN
MY_PROC(type1, rc);
-- fetch first row from result set
fetch rc into row;
-- check if last fetch found something
while (rc%found) loop
dbms_output.put_line(row);
-- fetch next row from result set
fetch rc into row;
end loop;
close rc;
end;
Or perhaps more commonly, only fetch inside the loop, and stop when nothing is found, using %notfound rather than %found:
BEGIN
MY_PROC(type1, rc);
loop
-- fetch row from result set
fetch rc into row;
-- check if last fetch found something
exit when rc%notfound;
dbms_output.put_line(row);
end loop;
close rc;
end;
Not related to your current issue, but the predicate WHERE LINIA_PROD=NULL is never true; null isn't equal to (or not equal to) anything else. You need to use WHERE LINIA_PROD IS NULL instead.

Why does my explicit cursor fetch only specific rows from my database in PL/SQL?

I am very new to PL/SQL and I am trying to use an explicit cursor to iterate over my database, FLEX_PANEL_INSPECTIONS. I would like to fetch each row from the database in turn using an explicit cursor, and depending on the randomly generated 'status' of a given 'panel' in the row, assign the panel a new status value within an if / else statement. The status of the panel is random but Boolean - it is either 1 or 0.
However, when I view the DBMS output, I note that the fetch does not retrieve all values from the database - only those who have a status value of 1. I have included the core code below.
I would be very grateful if anybody is able to help me find a solution, or explain the root cause of my problem, thanks!
create or replace procedure FLEX_SUMMARY_STATUS_PROCEDURE as
old_panel_status number;
new_panel_status number;
cursor panel_cursor is
select FLEX_PANEL_STATUS
from FLEX_PANEL_INSPECTIONS;
begin
open panel_cursor;
loop
fetch panel_cursor into old_panel_status;
exit when panel_cursor%notfound;
if old_panel_status = 0
then new_panel_status := 2;
elsif old_panel_status = 1
then new_panel_status := 3;
--More conditional loops follow (but are irrelevant for this question).
dbms_output.put_line(old_panel_status);
--Test output
--This displays all of the 1's that were randomly generated in the original table.
--It does not display any of the 0's that were generated.
end if;
end loop;
close panel_cursor;
close sensor_cursor;
end FLEX_SUMMARY_STATUS_PROCEDURE;
/
In addition to the main error, which has already been fixed in the accepted answer, the below code shows the newer loop construct. Instead of rec, you can choose any variable name you like. On each iteration, it contains a row (usually with more than one column).
create or replace procedure FLEX_SUMMARY_STATUS_PROCEDURE as
new_panel_status number;
cursor panel_cursor is
select FLEX_PANEL_STATUS
from FLEX_PANEL_INSPECTIONS;
begin
for rec in panel_cursor loop
if rec.flex_panel_status = 0 then
new_panel_status := 2;
elsif rec.flex_panel_status = 1 then
new_panel_status := 3;
--More conditional loops follow (but are irrelevant for this question)
end if;
dbms_output.put_line(rec.flex_panel_status);
end loop;
end FLEX_SUMMARY_STATUS_PROCEDURE;
/
You can even get rid if the explicit cursor if you like:
for rec in (
select FLEX_PANEL_STATUS
from FLEX_PANEL_INSPECTIONS
) loop
If you didn't make a mistake when removing the additional elseif clauses, the issue is in the location of your dbms_output.put_line.
It's located inside the else part, so will only trigger when this clause is called. Move it below the END IF and make sure to use proper indentation, which makes such things way easier to spot.
create or replace procedure FLEX_SUMMARY_STATUS_PROCEDURE as
old_panel_status number;
new_panel_status number;
cursor panel_cursor is
select FLEX_PANEL_STATUS
from FLEX_PANEL_INSPECTIONS;
begin
open panel_cursor;
loop
fetch panel_cursor into old_panel_status;
exit when panel_cursor%notfound;
if old_panel_status = 0
then new_panel_status := 2;
elsif old_panel_status = 1
then new_panel_status := 3;
--More conditional loops follow (but are irrelevant for this question)
end if;
dbms_output.put_line(old_panel_status);
end loop;
close panel_cursor;
close sensor_cursor;
end FLEX_SUMMARY_STATUS_PROCEDURE;
/

Oracle PL/SQL - Are NO_DATA_FOUND Exceptions bad for stored procedure performance?

I'm writing a stored procedure that needs to have a lot of conditioning in it. With the general knowledge from C#.NET coding that exceptions can hurt performance, I've always avoided using them in PL/SQL as well. My conditioning in this stored proc mostly revolves around whether or not a record exists, which I could do one of two ways:
SELECT COUNT(*) INTO var WHERE condition;
IF var > 0 THEN
SELECT NEEDED_FIELD INTO otherVar WHERE condition;
....
-or-
SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND
....
The second case seems a bit more elegant to me, because then I can use NEEDED_FIELD, which I would have had to select in the first statement after the condition in the first case. Less code. But if the stored procedure will run faster using the COUNT(*), then I don't mind typing a little more to make up processing speed.
Any hints? Am I missing another possibility?
EDIT
I should have mentioned that this is all already nested in a FOR LOOP. Not sure if this makes a difference with using a cursor, since I don't think I can DECLARE the cursor as a select in the FOR LOOP.
I would not use an explicit cursor to do this. Steve F. no longer advises people to use explicit cursors when an implicit cursor could be used.
The method with count(*) is unsafe. If another session deletes the row that met the condition after the line with the count(*), and before the line with the select ... into, the code will throw an exception that will not get handled.
The second version from the original post does not have this problem, and it is generally preferred.
That said, there is a minor overhead using the exception, and if you are 100% sure the data will not change, you can use the count(*), but I recommend against it.
I ran these benchmarks on Oracle 10.2.0.1 on 32 bit Windows. I am only looking at elapsed time. There are other test harnesses that can give more details (such as latch counts and memory used).
SQL>create table t (NEEDED_FIELD number, COND number);
Table created.
SQL>insert into t (NEEDED_FIELD, cond) values (1, 0);
1 row created.
declare
otherVar number;
cnt number;
begin
for i in 1 .. 50000 loop
select count(*) into cnt from t where cond = 1;
if (cnt = 1) then
select NEEDED_FIELD INTO otherVar from t where cond = 1;
else
otherVar := 0;
end if;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.70
declare
otherVar number;
begin
for i in 1 .. 50000 loop
begin
select NEEDED_FIELD INTO otherVar from t where cond = 1;
exception
when no_data_found then
otherVar := 0;
end;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:03.06
Since SELECT INTO assumes that a single row will be returned, you can use a statement of the form:
SELECT MAX(column)
INTO var
FROM table
WHERE conditions;
IF var IS NOT NULL
THEN ...
The SELECT will give you the value if one is available, and a value of NULL instead of a NO_DATA_FOUND exception. The overhead introduced by MAX() will be minimal-to-zero since the result set contains a single row. It also has the advantage of being compact relative to a cursor-based solution, and not being vulnerable to concurrency issues like the two-step solution in the original post.
An alternative to #Steve's code.
DECLARE
CURSOR foo_cur IS
SELECT NEEDED_FIELD WHERE condition ;
BEGIN
FOR foo_rec IN foo_cur LOOP
...
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END ;
The loop is not executed if there is no data. Cursor FOR loops are the way to go - they help avoid a lot of housekeeping. An even more compact solution:
DECLARE
BEGIN
FOR foo_rec IN (SELECT NEEDED_FIELD WHERE condition) LOOP
...
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END ;
Which works if you know the complete select statement at compile time.
#DCookie
I just want to point out that you can leave off the lines that say
EXCEPTION
WHEN OTHERS THEN
RAISE;
You'll get the same effect if you leave off the exception block all together, and the line number reported for the exception will be the line where the exception is actually thrown, not the line in the exception block where it was re-raised.
Stephen Darlington makes a very good point, and you can see that if you change my benchmark to use a more realistically sized table if I fill the table out to 10000 rows using the following:
begin
for i in 2 .. 10000 loop
insert into t (NEEDED_FIELD, cond) values (i, 10);
end loop;
end;
Then re-run the benchmarks. (I had to reduce the loop counts to 5000 to get reasonable times).
declare
otherVar number;
cnt number;
begin
for i in 1 .. 5000 loop
select count(*) into cnt from t where cond = 0;
if (cnt = 1) then
select NEEDED_FIELD INTO otherVar from t where cond = 0;
else
otherVar := 0;
end if;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:04.34
declare
otherVar number;
begin
for i in 1 .. 5000 loop
begin
select NEEDED_FIELD INTO otherVar from t where cond = 0;
exception
when no_data_found then
otherVar := 0;
end;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.10
The method with the exception is now more than twice as fast. So, for almost all cases,the method:
SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND....
is the way to go. It will give correct results and is generally the fastest.
If it's important you really need to benchmark both options!
Having said that, I have always used the exception method, the reasoning being it's better to only hit the database once.
Yes, you're missing using cursors
DECLARE
CURSOR foo_cur IS
SELECT NEEDED_FIELD WHERE condition ;
BEGIN
OPEN foo_cur;
FETCH foo_cur INTO foo_rec;
IF foo_cur%FOUND THEN
...
END IF;
CLOSE foo_cur;
EXCEPTION
WHEN OTHERS THEN
CLOSE foo_cur;
RAISE;
END ;
admittedly this is more code, but it doesn't use EXCEPTIONs as flow-control which, having learnt most of my PL/SQL from Steve Feuerstein's PL/SQL Programming book, I believe to be a good thing.
Whether this is faster or not I don't know (I do very little PL/SQL nowadays).
Rather than having nested cursor loops a more efficient approach would be to use one cursor loop with an outer join between the tables.
BEGIN
FOR rec IN (SELECT a.needed_field,b.other_field
FROM table1 a
LEFT OUTER JOIN table2 b
ON a.needed_field = b.condition_field
WHERE a.column = ???)
LOOP
IF rec.other_field IS NOT NULL THEN
-- whatever processing needs to be done to other_field
END IF;
END LOOP;
END;
you dont have to use open when you are using for loops.
declare
cursor cur_name is select * from emp;
begin
for cur_rec in cur_name Loop
dbms_output.put_line(cur_rec.ename);
end loop;
End ;
or
declare
cursor cur_name is select * from emp;
cur_rec emp%rowtype;
begin
Open cur_name;
Loop
Fetch cur_name into Cur_rec;
Exit when cur_name%notfound;
dbms_output.put_line(cur_rec.ename);
end loop;
Close cur_name;
End ;
May be beating a dead horse here, but I bench-marked the cursor for loop, and that performed about as well as the no_data_found method:
declare
otherVar number;
begin
for i in 1 .. 5000 loop
begin
for foo_rec in (select NEEDED_FIELD from t where cond = 0) loop
otherVar := foo_rec.NEEDED_FIELD;
end loop;
otherVar := 0;
end;
end loop;
end;
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.18
The count(*) will never raise exception because it always returns actual count or 0 - zero, no matter what. I'd use count.
The first (excellent) answer stated -
The method with count() is unsafe. If another session deletes the row that met the condition after the line with the count(*), and before the line with the select ... into, the code will throw an exception that will not get handled.
Not so. Within a given logical Unit of Work Oracle is totally consistent. Even if someone commits the delete of the row between a count and a select Oracle will, for the active session, obtain the data from the logs. If it cannot, you will get a "snapshot too old" error.