Is there a way to restart a cursor? Oracle - sql

I am trying to do something such as:
for(int i = 0; i<10; i++)
{
for(int j = 0; j<10; j++)
{
Blah;
}
}
//As you can see each time that there is a different i, j starts at 0 again.
Using cursors in Oracle. But if I'm correct, after I fetch all rows from a cursor, it will not restart. Is there a way to do this?
Here is my sql:
CREATE OR REPLACE PROCEDURE SSACHDEV.SyncTeleappWithClientinfo
as
teleCase NUMBER;
CURSOR TeleAppCursor
is
Select
distinct(casenbr)
from TeleApp;
CURSOR ClientInfoCursor
is
Select casenbr
from clientinfo
where trim(cashwithappyn) is null;
BEGIN
open TeleAppCursor;
open ClientInfoCursor;
LOOP
fetch TeleAppCursor into teleCase;
EXIT when TeleAppCursor%NOTFOUND;
LOOP
fetch ClientInfoCursor into clientCase;
EXIT when ClientInfoCursor%NOTFOUND;
if clientCase = teleCase then
update ClientInfo
set cashwithappyn = (select cashwithappyn from teleapp where casenbr = clientCase)
where casenbr = clientCase;
break;
end if;
END LOOP;
END LOOP;
END;
I did check online and was unable to find anything on this.

Instead of restarting the Cursor you could use a table variable to store the results of sql statement and then loop over the table an arbitrary number of times.
Here's an example using the SQL Fiddle Sample data.
DECLARE
CURSOR c1 IS
SELECT id,
TYPE,
details
FROM supportcontacts;
TYPE contactrec
IS TABLE OF c1%ROWTYPE INDEX BY BINARY_INTEGER;
acontact c1%ROWTYPE;
contactlist CONTACTREC;
counter INTEGER;
BEGIN
counter := 0;
OPEN c1;
LOOP
FETCH c1 INTO acontact;
IF c1%FOUND THEN
counter := counter + 1;
END IF;
Contactlist(counter) := acontact;
IF c1%NOTFOUND THEN
EXIT;
END IF;
END LOOP;
CLOSE c1;
FOR i IN 1..5 LOOP
FOR j IN 1..counter LOOP
dbms_output.Put_line(Contactlist(j).type || ' ' || Contactlist(j).details);
END LOOP;
END LOOP;
END;
/
which outputs
Email admin#sqlfiddle.com
Twitter #sqlfiddle
Email admin#sqlfiddle.com
Twitter #sqlfiddle
Email admin#sqlfiddle.com
Twitter #sqlfiddle
Email admin#sqlfiddle.com
Twitter #sqlfiddle
Email admin#sqlfiddle.com
Twitter #sqlfiddle
Here's the SQL Fiddle but I can't figure out how to see the output from dbms_output

You don't need the second cursor at all, just use the set operations in Oracle to update the appropriate records without manually searching for them yourself:
DECLARE
v_teleCase TeleApp.teleCase%TYPE;
v_cashwithappyn TeleApp.cashwithappyn%TYPE
CURSOR TeleAppCursor
is
Select
distinct casenbr, cashwithappyn
from TeleApp;
BEGIN
open TeleAppCursor;
LOOP
fetch TeleAppCursor into v_teleCase, v_cashwithappyn;
EXIT when TeleAppCursor%NOTFOUND;
UPDATE ClientInfo
SET cashwithappyn = v_cashwithappyn
WHERE casenbr = v_teleCase
AND trim(cashwithappyn) is null;
END LOOP;
END;
It's also a good idea to not have variables with the same name as columns.

Related

Error while writing to array plsql how to fix? Extend doesn't work also

so I am trying to write to an array in PL/SQL, and I always get the subscript outside of limit error. I've seen similar posts and implemented everything based on those answers, I can't seem to find what I'm doing wrong. The line giving the error is "arr_quartosLivres(counter) := q.id;" I've tried to extend the array and it still doesn't work, however, either way, the look only runs 21 times (because there are only 21 values in the table quarto) so it shouldn't even need to be extended. Any help would be highly appreciated! Thank you
SET SERVEROUTPUT ON;
DECLARE
p_idReserva reserva.id%type := 408;
v_dataEntradaReserva reserva.data_entrada%type;
counter integer := 0;
type arr_aux IS varray(21) of quarto.id%type;
arr_quartosLivres arr_aux := arr_aux();
BEGIN
SELECT data_entrada INTO v_dataEntradaReserva FROM reserva WHERE id = p_idreserva;
FOR q IN (SELECT * FROM quarto)
LOOP
BEGIN
IF isQuartoIndisponivel(q.id, v_dataEntradaReserva)
THEN DBMS_OUTPUT.PUT_LINE('nao disponivel' || counter);
arr_quartosLivres(counter) := q.id;
ELSE DBMS_OUTPUT.PUT_LINE('disponivel' || counter);
END IF;
counter := counter + 1;
END;
END LOOP;
END;
The index values for varray begin with 1. Your logic is trying to use index value 0. Thus index out of range. BTW extend does not apply to varray, when declared a varray has a fixed size. You have 3 solutions: initialize counter to 1 instead of 0, or move incrementing it prior to its use as an index. Since as it stands you increment every time through the loop, even when the IF condition returns false and you do not use the counter as an index, leaving a NULL value in the array.But you use counter for 2 different purposes: Counting rows processed and index into the array. Since the row value may not be put into the array then your 3rd option is to introduce another variable for the index. Further there is no need for the BEGIN ... End block in the loop.
declare
p_idreserva reserva.id%type := 408;
v_dataentradareserva reserva.data_entrada%type;
counter integer := 0;
type arr_aux is varray(21) of quarto.id%type;
arr_quartoslivres arr_aux := arr_aux();
varray_index integer := 1 ; -- index varaibal for varray.
begin
select data_entrada into v_dataentradareserva from reserva where id = p_idreserva;
for q in (select * from quarto)
loop
if isquartoindisponivel(q.id, v_dataentradareserva)
then
dbms_output.put_line('nao disponivel' || counter || ' at index ' || varray_index);
arr_quartoslivres(varray_index) := q.id;
varray_index := varray_index + 1;
else
dbms_output.put_line('disponivel' || counter);
end if;
counter := counter + 1;
end loop;
end;

Having hard time figuring out the best attribute to use with cursor

Using this attribute %NOTFOUND with this cursor is giving me issues. I also tried using %FOUND. Is There a better option? It keeps returning the Else even if the cursor comes back with something in it or not. I want it to send the first email if the cursor has something and the second one if the cursor has nothing.
CURSOR crs IS
select *
from user_objects
where status = 'INVALID';
BEGIN
OPEN crs;
Loop
FETCH crs bulk collect INTO messages limit 10;
EXIT WHEN message. count = '0';
End IF;
for indx in messages.FIRST .. messages.LAST
loop
email_body := email_body||CHR(10)||'OBJECT NAME: '||messages(indx).OBJECT_NAME||', OBJECT TYPE: '||messages(indx). OBJECT_TYPE||', STATUS: '||messages(indx).STATUS;
end loop;
END LOOP;
IF CRS%NOTFOUND THEN
Email_body := email_body||CHR(10)|| '' ||CHR(10)||'All of these objects are invalid in your database. Please troubleshoot the issue. Thank you.'
DBMS_OUTPUT_LINE(email_body||'Invalid obj.';
ELSE
Email_body := 'There are no invalid objects in your database. Thank You.';
DBMS_OUTPUT_LINE(email_body||'No Invalid obj.';
END IF;
You can try this for test (i think more readable and shorter):
declare
email_body varchar2( 4000 ) := '';
l_invalid_objects_list varchar2( 4000 ) := '';
begin
l_invalid_objects_list := '';
for i in ( select OBJECT_NAME, OBJECT_TYPE, STATUS from user_objects where status = 'INVALID' and rownum <= 10 ) --limited to 10 records
loop
l_invalid_objects_list := l_invalid_objects_list||CHR(10)||'OBJECT NAME: '||i.OBJECT_NAME||', OBJECT TYPE: '||i. OBJECT_TYPE||', STATUS: '||i.STATUS;
end loop;
if length( l_invalid_objects_list ) > 0 then
email_body := email_body||CHR(10)|| '' ||CHR(10)||l_invalid_objects_list||CHR(10)|| '' ||CHR(10)||'All of these objects are invalid in your database. Please troubleshoot the issue. Thank you.';
DBMS_OUTPUT.PUT_LINE(email_body||'Invalid obj.');
else
Email_body := 'There are no invalid objects in your database. Thank You.';
DBMS_OUTPUT.PUT_LINE(email_body||'No Invalid obj.');
end if;
end;
I'm assuming you want to use a bulk operation and that you want to do this in batches of 10 even though it might be overkill for the functionality.
So . . . you need to declare a table type based on the cursor (ROWTYPE) then declare a variable based on the type.
You want to add to the email_body for each object and when you've looped through the lot it looks like you want another message saying if there were any invalid objects.
Using %NOTFOUND is not right here as you exit your loop when the messages.COUNT = 0. When it is 0 the cursor will always be %NOTFOUND so you're always getting the INVALID option.
I've used a boolean that is set to TRUE as soon as you have at least one invalid object and that is checked instead of the %NOTFOUND.
DECLARE
CURSOR crs
IS
SELECT *
FROM user_objects
WHERE status = 'INVALID';
TYPE work_recs IS TABLE OF crs%ROWTYPE;
messages work_recs;
lbol_invalid_objects BOOLEAN := FALSE;
email_body VARCHAR2(2000);
BEGIN
OPEN crs;
LOOP
FETCH crs BULK COLLECT INTO messages LIMIT 10;
DBMS_OUTPUT.PUT_LINE('COUNT - '||messages.COUNT);
EXIT WHEN messages.COUNT = '0';
lbol_invalid_objects := TRUE;
FOR indx IN messages.FIRST .. messages.LAST
LOOP
email_body := email_body||CHR(10)||'OBJECT NAME: '||messages(indx).OBJECT_NAME||', OBJECT TYPE: '||messages(indx). OBJECT_TYPE||', STATUS: '||messages(indx).STATUS;
END LOOP;
END LOOP;
IF lbol_invalid_objects THEN
email_body := email_body||CHR(10)|| '' ||CHR(10)||'All of these objects are invalid in your database. Please troubleshoot the issue. Thank you.'
dbms_output.put_line('Invalid obj.');
ELSE
email_body := 'There are no invalid objects in your database. Thank You.';
dbms_output.put_line('No Invalid obj.');
END IF;
END;

How to dynamically store the value of a loop and return it once the for loop breaks?

I have a code something like this
FOR K IN (SELECT E.COLUMN_VALUE
FROM TABLE (SELECT CAST(LEAVE_HOLIDAY_CAL_PKG_NEW.GES_LEV_COLUMN_TO_ROWS_FNC(V_CAL_SUBTSR,
',') AS
LEV_TABLE_OF_VARCHAR_TYP)
FROM DUAL) E) LOOP
V_CAL_DESC := CASE WHEN K.COLUMN_VALUE = 'W' THEN 'Project Weekend-' || TO_CHAR((V_DATE1 + V_ITERATION), 'Day') WHEN K.COLUMN_VALUE = 'H' THEN 'Holiday' ELSE 'Working day' END;
IF K.COLUMN_VALUE = 'H' THEN
FETCH C_HOLIDAY_CURSR
INTO V_HOLIDAY_ID;
SELECT H.HOLIDAY_DESC
INTO V_CAL_DESC
FROM LEAVE.LEV_NEW_EMP_HOLIDAY_DTLS H
WHERE H.HOLIDAY_ID = V_HOLIDAY_ID;
END IF;
INSERT INTO LEV_CAL_TEMP_TBL
VALUES
(IN_PERSON_ID, K.COLUMN_VALUE, V_DATE1 + V_ITERATION, V_CAL_DESC);
COMMIT;
V_ITERATION := V_ITERATION + 1;
END LOOP;
OPEN C_CAL_CURSR FOR
select *from LEV_CAL_TEMP_TBL;
So every time this for loop runs one row is inserted in the table GES_LEV_CAL_TEMP_TBL.
But I wanted to avoid this as this might affect the performance of the system,So is there any way I can store those value somewhere
and return the same in cursor once the FOR loop ends.
Thanks a lot in advance.
A cursor FOR LOOP is row-by-row a.k.a slow-by-slow process.
If you must do it in PL/SQL, then use the BULK COLLECT and FORALL statement.
Declare a collection type:
For example,
TYPE t_lev_cal_temp_tbl IS TABLE OF lev_cal_temp_tbl%ROWTYPE;
l_lev_cal t_lev_cal_temp_tbl := t_lev_cal_temp_tbl();
FORALL insert statement:
For example,
FORALL i IN l_lev_cal.first .. l_lev_cal.last
INSERT INTO lev_cal_temp_tbl VALUES l_tab(i);

pl sql query takes more time to execute

I found this PL/SQL at my workplace and I couldn't find the reason why this script takes so much time to execute:
DECLARE
query VARCHAR(500);
ref_cur REFCURSOR;
product_listH VARCHAR(1000):='';
product_listA VARCHAR(1000):='';
product_listP VARCHAR(1000):='';
product VARCHAR(100):='';
begin
query := ' select hotelname
from sch1.resconfirmsv rr,
sch1.reshoteldetailssv hd,sch2.respkgconfirmsv r '||
' where rr.id = hd.resconfirmid and
hd.resconfirmid = r.hotelconfirmid and
r.id = ' || m_resconfirmid || '';
OPEN ref_cur FOR EXECUTE query;
LOOP
FETCH ref_cur INTO product;
IF NOT FOUND THEN
EXIT; -- exit loop
END IF;
product_listH := product_listH||''||trim(COALESCE(product,'-'))||',<br>';
END LOOP;
product_listH := rtrim(trim(product_listH),',<br>');
CLOSE ref_cur;
query := ' select distinct programname
from sch1.resconfirmsv rr,
sch3.resactivitysv a,
sch3.resprogramsv hx,
sch2.respkgconfirmsv r '||
' where rr.id = hx.resconfirmid and
hx.id=a.resprogramid and
hx.resconfirmid = r.activitiesconfirmid and
r.id = ' || m_resconfirmid || '';
OPEN ref_cur FOR EXECUTE query;
LOOP
FETCH ref_cur INTO product;
IF NOT FOUND THEN
EXIT; -- exit loop
END IF;
product_listA := product_listA||''||trim(COALESCE(product,'-'))||',<br>';
END LOOP;
product_listA := rtrim(trim(product_listA),',<br>');
CLOSE ref_cur;
product_listP := product_listH || ',<br>' || product_listA;
product_listP := rtrim(trim(product_listP),',<br>');
product_listP = ltrim(rtrim(product_listP,',<br>'),',<br>');
RETURN product_listP;
end;
without this script total run-time is 12.176 sec and with this script it takes up to 18.802 sec.means this gets at least 6 seconds to execute. All the needed columns are indexed. Anybody can tell me where the places need to be more optimize in this query?
Why declaring the cursor as a seperate varchar?
Instead i'd use the normal declaration of a cursor, it takes time to analyze the query to be executed by Oracle, so (between the declare- and Begin-labels of your current code:
cursor ref_cur as
select distinct programname
from sch1.resconfirmsv rr,
sch3.resactivitysv a,
sch3.resprogramsv hx,
sch2.respkgconfirmsv r '||
where rr.id = hx.resconfirmid and
hx.id=a.resprogramid and
hx.resconfirmid = r.activitiesconfirmid and
r.id = m_resconfirmid;
Now you can use
For x in ref_cur loop
The same thing for query#2.
Cheers

Oracle : how to fetch data from dynamic query?

I have a program to generate dynamic query string based on input. This query may select from any tables or joined tables in my DB, and the column names and number of columns are unknown.
Now with this query string as the only input, I want to fetch all data from the result and output them line by line, is there any way to do this ?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Thank Thinkjet for the reference. I have solved the problem, to help the others, here is the piece of code I used:
DECLARE
v_curid NUMBER;
v_desctab DBMS_SQL.DESC_TAB;
v_colcnt NUMBER;
v_name_var VARCHAR2(10000);
v_num_var NUMBER;
v_date_var DATE;
v_row_num NUMBER;
p_sql_stmt VARCHAR2(1000);
BEGIN
v_curid := DBMS_SQL.OPEN_CURSOR;
p_sql_stmt :='SELECT * FROM emp';
DBMS_SQL.PARSE(v_curid, p_sql_stmt, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS(v_curid, v_colcnt, v_desctab);
-- Define columns:
FOR i IN 1 .. v_colcnt LOOP
IF v_desctab(i).col_type = 2 THEN
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_num_var);
ELSIF v_desctab(i).col_type = 12 THEN
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_date_var);
ELSE
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_name_var, 50);
END IF;
END LOOP;
v_row_num := dbms_sql.execute(v_curid);
-- Fetch rows with DBMS_SQL package:
WHILE DBMS_SQL.FETCH_ROWS(v_curid) > 0 LOOP
FOR i IN 1 .. v_colcnt LOOP
IF (v_desctab(i).col_type = 1) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_name_var);
ELSIF (v_desctab(i).col_type = 2) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_num_var);
ELSIF (v_desctab(i).col_type = 12) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_date_var);
END IF;
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(v_curid);
END;
/
You can do that with DBMS_SQL package.
Update
To get more detailed reference about DBMS_SQL go here.
If you are building your string within PL/SQL, you can run it with EXECUTE IMMEDIATE. <- link. Use the BULK COLLECT INTO and output the collection.
<PRE>
DECLARE
RUN_S CLOB;
IGNORE NUMBER;
SOURCE_CURSOR NUMBER;
PWFIELD_COUNT NUMBER DEFAULT 0;
L_DESCTBL DBMS_SQL.DESC_TAB2;
Z_NUMBER NUMBER;
BEGIN
RUN_S := ' SELECT 1 AS VAL1,
2 AS VAL2,
CURSOR (SELECT 11 AS VAL11,
12 AS VAL12
FROM DUAL) AS CUR1,
CURSOR (SELECT 11 AS VAL11,
12 AS VAL12
FROM DUAL) AS CUR2
FROM DUAL';
SOURCE_CURSOR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(SOURCE_CURSOR, RUN_S, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(SOURCE_CURSOR, PWFIELD_COUNT, L_DESCTBL); -- get record structure
FOR I IN 1 .. PWFIELD_COUNT LOOP
DBMS_OUTPUT.PUT_LINE('Col ' || I || ' Type:' || L_DESCTBL(I).COL_TYPE);
IF L_DESCTBL(I).COL_TYPE = 2 THEN
DBMS_SQL.DEFINE_COLUMN(SOURCE_CURSOR, I, Z_NUMBER);
END IF;
NULL;
END LOOP;
IGNORE := DBMS_SQL.EXECUTE(SOURCE_CURSOR);
LOOP
IF DBMS_SQL.FETCH_ROWS(SOURCE_CURSOR) > 0 THEN
FOR I IN 1 .. PWFIELD_COUNT LOOP
IF L_DESCTBL(I).COL_TYPE IN (2) THEN
DBMS_SQL.COLUMN_VALUE(SOURCE_CURSOR, I, Z_NUMBER);
DBMS_OUTPUT.PUT_LINE('Col ' || I || ' Value:' || Z_NUMBER);
END IF;
END LOOP;
ELSE
EXIT;
END IF;
END LOOP;
END;
</PRE>