How do I use select in loop continue when statement? - sql

I want to continue a oracle sql loop when I find more then one result in a query - so my simplified code:
declare
cursor foo_cursor select * from foo_table;
foo foo_cursor%ROWTYPE;
begin
open foo_cursor;
loop
fetch foo_cursor into foo;
exit when foo_cursor%NOTFOUND;
continue when ( -- the next query has entries or an entry,
-- but how do I do this?
select count(*) from bar_table where bar_column=foo.foo_column
group by bar_column having count(1)>1;
)
insert into uninterresting_table (some_column) VALUES
(foo.foo_column);
end loop;
close foo_cursor;
end;

It looks like you only want to act on the records in FOO_TABLE when at least two related records exists in BAR_TABLE. You can alter the definition of foo_cursor to take that requirement into account as shown below. That way you don't need to iteratively check for the existence of a record in BAR_TABLE each time.
declare
cursor foo_cursor is
select *
from foo_table foo
where exists (select 1 from bar_table bar
where bar.bar_column = foo.foo_column
having count(*) > 1);
foo foo_cursor%ROWTYPE;
begin
open foo_cursor;
loop
fetch foo_cursor into foo;
exit when foo_cursor%NOTFOUND;
insert into uninteresting_table (some_column) VALUES
(foo.foo_column);
end loop;
close foo_cursor;
end;
/
On the other hand if you are looking to skip records in FOO_TABLE that already have two or more records in BAR_TABLE, you can just invert the existence check and all else would be the same:
declare
cursor foo_cursor is
select *
from foo_table foo
where NOT exists (select 1 from bar_table bar
where bar.bar_column = foo.foo_column
having count(*) > 1);
foo foo_cursor%ROWTYPE;
begin
open foo_cursor;
loop
fetch foo_cursor into foo;
exit when foo_cursor%NOTFOUND;
insert into uninteresting_table (some_column) VALUES
(foo.foo_column);
end loop;
close foo_cursor;
end;
/
If you want to process all records in FOO_TABLE but do additional actions when two or more records exist in BAR_TABLE, you can still do that with a change to your foo_cursor:
declare
cursor foo_cursor is
select foo.*
, case when exists (select 1 from bar_table bar
where bar.bar_column = foo.foo_column
having count(*) > 1)
then 'Y'
else 'N'
end has_two_or_more
from foo_table foo;
foo foo_cursor%ROWTYPE;
begin
open foo_cursor;
loop
fetch foo_cursor into foo;
exit when foo_cursor%NOTFOUND;
continue when foo.has_two_or_more = 'Y';
insert into uninteresting_table (some_column) VALUES
(foo.foo_column);
end loop;
close foo_cursor;
end;
/

if the [select has ... one and more entries] i want go into the next iteration else do more stuff in this iteration.
If there are no records in bar_table you want to do some more processing otherwise you want to skip the processing. There's a way to do that: goto.
Oh yes :)
declare
cursor foo_cursor select * from foo_table;
foo foo_cursor%ROWTYPE;
n pls_integer;
begin
open foo_cursor;
loop
fetch foo_cursor into foo;
exit when foo_cursor%NOTFOUND;
select count(*) into n
from bar_table
where bar_column=foo.foo_column
group by bar_column having count(1)>1;
if n > 0 then
goto skip_point;
end if;
insert into uninterresting_table (some_column) VALUES
(foo.foo_column);
<< skip_point >>
end loop;
close foo_cursor;
end;
Obviously you could just put the whole skippable section into a branch of an IF .. ELSE statement, but where's the fun in that?

So thanks to #APC and #Sentinel -- I still made my version work, but your answers gave me the right dirrections:
declare
cursor foo_cursor select * from foo_table;
foo foo_cursor%ROWTYPE;
n pls_integer
begin
open foo_cursor;
loop
fetch foo_cursor into foo;
exit when foo_cursor%NOTFOUND;
begin
select count(1) into n from bar_table where bar_column=foo.foo_column
group by bar_column;
exception when NO_DATA_FOUND then continue;
end
continue when (n>1);
insert into uninterresting_table (some_column) VALUES
(foo.foo_column);
-- do some more stuff
end loop;
close foo_cursor;
end;
```

Related

PLS-00341: declaration of cursor 'cur2' is incomplete or malformed || PL/SQL: ORA-00918: column ambiguously defined

I wrote a procedure where i am trying to insert value from source to destination table, i used bulk collect in order to execute large volume of data.
While executing the below procedure, its showing cursor cus2 declaration is incomplete and its showing some column ambiguously defined in the cursor.
create or replace PROCEDURE TEST2 (
p_array_size IN NUMBER
) IS
CURSOR cur1 IS SELECT DISTINCT
*
FROM
test
CURSOR cur3 IS SELECT * FROM test;
CURSOR cur2(BND_TYPE cr_loan_prima_rate_orig.bndng_typ%TYPE, BND_VAL cr_loan_prima_rate_orig.bndng_val%TYPE, FINANCIAL_INST_ID cr_loan_prima_rate_orig.financial_institution_id%TYPE,
PRDCT_ID cr_loan_prima_rate_orig.prdct_id%TYPE,PRDCT_SUB_ID cr_loan_prima_rate_orig.prdct_sub_id%TYPE, INSTRUMENT_ID cr_loan_prima_rate_orig.instrmnt_id%TYPE) IS SELECT
d.*
FROM
test1 d,
(
SELECT
b.prdct_id,
FROM
test2 a,
test1 b
WHERE
a.ir_id = b.ir_id
AND a.price_component_id = b.price_component_id
AND a.financial_institution_id = b.financial_institution_id
GROUP BY
b.prdct_id,
) e
WHERE
d.prdct_id = e.prdct_id
AND d.bndng_typ = BND_TYPE
AND d.bndng_val = BND_VAL
AND d.financial_institution_id = FINANCIAL_INST_ID
AND d.prdct_id = PRDCT_ID
AND d.prdct_sub_id = PRDCT_SUB_ID
AND d.instrmnt_id = INSTRUMENT_ID ;
TYPE loan_data_tbl IS TABLE OF cur1%rowtype INDEX BY PLS_INTEGER;
loan_data loan_data_tbl;
TYPE loanrate_tbl IS TABLE OF cur2%rowtype INDEX BY BINARY_INTEGER;
loan_rate loanrate_tbl;
BEGIN
DECLARE
v_noofDays NUMBER:=0;
currentDt DATE;
BEGIN
SELECT * INTO currentDt FROM dt;
BEGIN
IF cur1%Isopen Then
Close cur1;
End IF;
IF cur2%Isopen Then
Close cur2;
End IF;
OPEN cur1;
LOOP
FETCH cur1 BULK COLLECT INTO loan_data LIMIT p_array_size;
EXIT WHEN loan_data.COUNT = 0;
FOR i IN 1..loan_data.COUNT
LOOP
OPEN cur3;
OPEN cur2(loan_data(i).bndng_typ, loan_data(i).bndng_val,loan_data(i).financial_institution_id,
loan_data(i).prdct_id, loan_data(i).prdct_sub_id, loan_data(i).instrmnt_id);
loop
FETCH cur2 BULK COLLECT INTO loan_rate LIMIT p_array_size;
EXIT WHEN loan_rate.COUNT = 0;
FOR j IN 1..loan_rate.COUNT
LOOP
IF(cur3.POS_NUM = loan_data(i).POS_NUM AND cur3.POS_TYPE = loan_data(i).POS_TYPE
AND cur3.PRICE_COMPONENT_ID = loan_rate(j).PRICE_COMPONENT_ID
AND cur3.RPRTD_TILL_DT = loan_data(i).RPRTD_TILL_DT) THEN
update test SET SEQ_NUM=1,
WHERE SEQ_NUM=2;
ELSE
INSERT INTO test VALUES (
....
....
);
END IF;
COMMIT;
END LOOP;
END LOOP;
CLOSE cur2;
CLOSE cur1;
END LOOP;
END LOOP;
CLOSE cur1;
END;
END;
End ;
/
For me everything looks good, not able to find the exact mistake.can anyone help me out with this.

Logic of my Stored Procedure, Loop cursors

Let's see if i can make this clear. Basically what i want to do and i don't know how is this: inside my loop how can i iterate those 2 cursors? After fetching those rows i want to insert in those 2 tables as you can see in the snippet :
CREATE OR REPLACE PROCEDURE add_docs
IS
dom_doc DOM_DOCUMENT.DOMAIN_DOC%TYPE;
type_doc_pk TYPE_DOCS.TYPE_DOC_PK%TYPE;
type_doc TYPE_DOCS.TYPE_DOCUMENT%TYPE;
user_code TYPE_DOCS.USERCODE%TYPE;
result_code ECM_TIPO_DOCS.CODIGO_RESULTADO%TYPE;
LS_LOCAL INTEGER;
l_id INTEGER;
cursor get_local
is
SELECT ls_local_pk FROM rt_local_ls
rc_loc c_loc%ROWTYPE;
cursor get_docs
is
SELECT DOM_DOCUMENT.DOMAIN_DOC INTO dom_doc,
TYPE_DOCS.TYPE_DOC_PK INTO type_doc_pk,
TYPE_DOCS.TYPE_DOCUMENT INTO type_doc,
TYPE_DOCS.USERCODE INTO user_code,
TYPE_DOCS.CODE_RESULT INTO result_code
FROM TYPE_DOCS
JOIN DOM_TDOC_SIS
ON TYPE_DOCS.TYPE_DOC_PK = DOM_TDOC_SIS.TYPE_DOC_PK
JOIN DOM_DOCUMENT
ON DOM_TDOC_SIS.DOMAIN_DOC_PK = DOM_DOCUMENT.DOMAIN_DOC_PK
WHERE DOM_DOCUMENT.DOMAIN_DOC_PK IN (2, 10)
AND NOT EXISTS
(
SELECT 1
FROM TP_DOC_MAP
WHERE TP_DOC_MAP.LS_LOCAL_PK = LS_LOCAL ----this is the variable that i have to iterate it's what i am getting from the other cursor
AND TP_DOC_MAP.LS_SYSTEM_PK = 3
AND TP_DOC_MAP.ACTIVE = 1
AND TP_DOC_MAP.CODE = TYPE_DOCS.TYPE_DOC_PK
);
BEGIN
OPEN get_local;
FETCH get_local INTO rc_loc
IF get_local%FOUND
THEN
for md_local in get_local
LOOP
OPEN get_docs;
FETCH get_docs INTO....
---now this is where i don't know how to do inside this loop i want to repeat the cursor get_docs for each row in the cursor get_local
--and then insert with the values fetched for each iteration
INSERT INTO TP_DOC VALUES (
type_doc_pk,
type_doc,
1,
SYSDATE,
NULL)
RETURNING id INTO l_id;
INSERT INTO TP_DOC_MAP VALUES (
l_id,
LS_LOCAL,
3,
type_doc_pk,
1,
sysdate,
NULL
);
END LOOP
END IF;
END add_docs;
how can i do this? for each LS_LOCAL that exists it will have to run the sele
for each LS_LOCAL that exists, it will have to run the select in the cursor get_docs with the LS_LOCAL variable.
Embedded cursor FOR loops are one option. Here's how (I removed irrelevant parts of code to make it as simple as possible):
begin
for cur_l in (select ls_local_pk from rt_local_ls)
loop
for cur_d in (select domain_doc,
type_doc_pk, ...
from type_docs join dom_tdoc_sis ...
)
loop
insert into tp_doc ...
insert into tp_doc_map ...
end loop;
end loop;
end;

Why is always cursor already open?

I want to get all messages who version is greater than the input and belongs to the same version group. So I created a cursor to loop through the messageHeader table and get all the rows using a cursor. But I get a cursor already open exception. What am I doing wrong here. I have opened and closed the cursor outside the loop.
create or replace
PROCEDURE ALLOW_SCHEDCALC (scheduleID IN NUMBER, flag OUT NUMBER)
is
messageHeaderIDS Number(20,0);
gasDay timestamp;
headerCount number(20);
cursor mHeaderCurs IS SELECT message_header.ID INTO messageHeaderIDS FROM Message_header
where version_group_id =(select DISTINCT version_group_id from message_header where ID= scheduleID)
AND message_header.version > (select DISTINCT version from message_header where ID = scheduleID);
begin
IF mHeaderCurs %ISOPEN THEN
CLOSE mHeaderCurs ;
END IF;
open mHeaderCurs;
FOR mHeader in mHeaderCurs
Loop
--DBMS_OUTPUT.put_line (mHeader.ID);
SELECT COUNT(*) into headerCount FROM nomination_process_queue where ID=mHeader.ID;
IF headerCount > 0
then
flag:=0;
else
flag:=1;
end if;
end loop;
close mHeaderCurs;
--SELECT VALID_FROM INTO gasDay FROM message_header where ID = scheduleID;
end ALLOW_SCHEDCALC;
When you use the FOR loop over a cursor, e.g. FOR mHeader in mHeaderCurs, the Oracle PL/SQL engine handles the cursor open/close for you.
You don't need to open or close the cursor at all.
P.S. this code never made sense in the first place, since this is at the start of the procedure so by definition the cursor cannot be open:
begin
IF mHeaderCurs %ISOPEN THEN
CLOSE mHeaderCurs ;
END IF;
Try this code it may help you :-
create or replace PROCEDURE ALLOW_SCHEDCALC (scheduleID IN NUMBER, flag OUT NUMBER)
is
messageHeaderIDS Number(20,0);
gasDay timestamp;
headerCount number(20);
cursor mHeaderCurs IS SELECT message_header.ID INTO messageHeaderIDS
FROM Message_header
where version_group_id =(select DISTINCT version_group_id from message_header
where ID= scheduleID)
AND message_header.version >
(select DISTINCT version from message_header where ID = scheduleID);
begin
FOR mHeader in mHeaderCurs loop exit when mHeaderCurs%notfound;
--DBMS_OUTPUT.put_line (mHeader.ID);
SELECT COUNT(*) into headerCount FROM nomination_process_queue where ID=mHeader.ID;
IF headerCount > 0
then
flag:=0;
else
flag:=1;
end if;
end loop;
--SELECT VALID_FROM INTO gasDay FROM message_header where ID = scheduleID;
end ALLOW_SCHEDCALC;/

PL/SQL: ORA-00904: : invalid identifier

I am running the following SP but getting the error c1.pyid is invalid identifier. I am trying to use two different query results from one cursor. If there is any other way of using IF-else clause in a cursor, i am open to that too.
CREATE OR REPLACE
PROCEDURE FIX_DOCUMENT_RECORDS ( i_flag in varchar)
AS
Op_ID VARCHAR(8);
Op_Name VARCHAR(32);
skill VARCHAR(32);
temp_count VARCHAR(8);
temp_status VARCHAR(8):='Submitted';
QRYSTR VARCHAR2(400);
TYPE REF_CUR IS REF CURSOR;
c1 REF_CUR;
BEGIN
IF (i_flag='1') THEN
QRYSTR:='SELECT *
FROM dims_doc_master
WHERE concat_prod_id IS NULL
OR documenttypeid IS NULL
AND (pystatuswork = temp_status);';
ELSE
QRYSTR:='SELECT *
FROM dims_doc_master
WHERE (documentimageid IS NULL
AND p8id IS NULL)
AND (pystatuswork = temp_status);';
END IF;
open c1 FOR QRYSTR;
LOOP
BEGIN
DBMS_OUTPUT.PUT_LINE('loop begin');
UPDATE DIMS_DOC_MASTER
SET pystatuswork ='Cancelled',
documentstatus ='Cancelled',
cancellationdate='31-JAN-14',
cancelledbysid = c1.pxcreateoperator,
cancelreason ='Cancelled due to corruption.'
WHERE pyid =c1.pyid;
DBMS_OUTPUT.PUT_LINE('After updation'||c1.pyid );
--Begin PC_DOCUMENT UPDATION
UPDATE PC_DOCUMENT
SET pystatuswork ='Cancelled',
cancellationdate='31-JAN-14'
WHERE pyid =c1.pyid;
--Begin insert into History
--Select Operator name and ID
SELECT skill
INTO skill
FROM operator_map_skill
WHERE pyuseridentifier=c1.pxcreateoperator
AND rownum =1;
INSERT
INTO DIMS_DOC_HIST
(
DIMS_DOC_ID,
DOC_CHG_USR,
DOC_CHG_DT,
DOC_NEW_STS,
DOC_CHG_CMNT,
CRE_TS,
ROLE,
RSN_DESC,
TARGETROLE,
DOC_CHG_USR_ID,
DOC_ASG_USR_ID,
DOC_ASG_USR,
PREVSTATUS,
PREVSTATUSDT,
ASSIGNEDTODT,
TODISPLAY,
ACTIVITY_NAME
)
VALUES
(
c1.pyid,
'DIMS',
systimestamp,
'Cancelled',
'Cancelled due to corruption',
'31-JAN-14',
skill,
NULL,
skill,
c1.pxcreateoperator,
c1.pxcreateoperator,
c1.pxcreateopname,
'Submitted',
NULL,
systimestamp,
'Y',
'Updation through Script'
);
dbms_output.put_line
(
'Document ID= '||c1.pyid
)
;
SELECT COUNT(*)
INTO temp_count
FROM PC_ASSIGN_WORKBASKET
WHERE pxrefobjectinsname=c1.pyid;
IF(temp_count IS NOT NULL) THEN
DELETE FROM PC_ASSIGN_WORKBASKET WHERE pxrefobjectinsname=c1.pyid;
ELSE
DELETE FROM PC_ASSIGN_WORKLIST WHERE pxrefobjectinsname=c1.pyid;
END IF;
COMMIT;
END;
END LOOP;
CLOSE c1;
END;
You seem confusing cursor and fetched row.
In your current procedure: you open a cursor, do a loop (which looks to be endless since there is no EXIT statement), and after the loop you close the cursor (but it looks it will never happen)
To fetch results from a cursor, do the following:
CREATE OR REPLACE PROCEDURE ...
...
c1 REF_CUR;
ddm_record dims_doc_master%rowtype;
BEGIN
...
OPEN c1;
LOOP
FETCH c1 INTO ddm_record;
EXIT WHEN c1%NOTFOUND;
...
DBMS_OUTPUT.PUT_LINE('Document ID= ' || ddm_record.pyid); -- not c1.pyid
END LOOP;
CLOSE c1;
END;
/
Inspired from examples here: http://plsql-tutorial.com/plsql-explicit-cursors.htm
Try embedding the flag in your where clause:
open c1 FOR
SELECT *
FROM dims_doc_master
WHERE (i_flag='1' AND
(concat_prod_id IS NULL
OR documenttypeid IS NULL
AND (pystatuswork = temp_status))
OR (i_flag<>'1' AND
(documentimageid IS NULL
AND p8id IS NULL)
AND (pystatuswork = temp_status));
The logic can probably be simplified but logically that would work.

INSERT and UPDATE a record using cursors in oracle

I have 2 tables- student and studLoad both having 2 fields studID and studName. I want to load data from student table into stuLoad table.
If the data already exists in the studLoad table, then it should be updated else it should be inserted. following is my code to do so:
create or replace procedure studentLoad is
v_id student.studID%type;
v_name student.studName%type;
v_sn studLoad.studName%type;
cursor cur_load is
select * from student;
begin
open cur_load;
loop
fetch cur_load into v_id,v_name;
exit when cur_load%notfound;
select studName into v_sn from studLoad where studID = v_id;
if(v_sn!= v_name) then
update studLoad set studName= v_name where studID= v_id;
else
insert into studLoad values(v_id,v_name);
dbms_output.put_line(v_id || ' ' || v_name);
end if;
end loop;
close cur_load;
end;
It's not working. the rows in studLoad table are noT updated. How do I solve this? In SQL server we use IF EXISTS(select...from stuLoad..) to check if the record exists in the table, is there a way to do the same in Oracle? if yes then please let me know the same.
This is a highly inefficient way of doing it. You can use the merge statement and then there's no need for cursors, looping or (if you can do without) PL/SQL.
MERGE INTO studLoad l
USING ( SELECT studId, studName FROM student ) s
ON (l.studId = s.studId)
WHEN MATCHED THEN
UPDATE SET l.studName = s.studName
WHERE l.studName != s.studName
WHEN NOT MATCHED THEN
INSERT (l.studID, l.studName)
VALUES (s.studId, s.studName)
Make sure you commit, once completed, in order to be able to see this in the database.
To actually answer your question I would do it something like as follows. This has the benefit of doing most of the work in SQL and only updating based on the rowid, a unique address in the table.
It declares a type, which you place the data within in bulk, 10,000 rows at a time. Then processes these rows individually.
However, as I say this will not be as efficient as merge.
declare
cursor c_data is
select b.rowid as rid, a.studId, a.studName
from student a
left outer join studLoad b
on a.studId = b.studId
and a.studName <> b.studName
;
type t__data is table of c_data%rowtype index by binary_integer;
t_data t__data;
begin
open c_data;
loop
fetch c_data bulk collect into t_data limit 10000;
exit when t_data.count = 0;
for idx in t_data.first .. t_data.last loop
if t_data(idx).rid is null then
insert into studLoad (studId, studName)
values (t_data(idx).studId, t_data(idx).studName);
else
update studLoad
set studName = t_data(idx).studName
where rowid = t_data(idx).rid
;
end if;
end loop;
end loop;
close c_data;
end;
/
If you would like to use your procedure, consider to change some lines:
create or replace procedure studentLoad is
v_id student.studID%type;
v_name student.studName%type;
v_sn studLoad.studName%type;
cursor cur_load is
select * from student;
begin
open cur_load;
loop
fetch cur_load into v_id,v_name;
exit when cur_load%notfound;
begin
select studName into v_sn from studLoad where studID = v_id;
if(v_sn!= v_name) then
update studLoad set studName= v_name where studID= v_id;
end if;
exception
when no_data_found then
insert into studLoad values(v_id,v_name);
end;
dbms_output.put_line(v_id || ' ' || v_name);
end loop;
close cur_load;
end;
I think it should work, didn't test it.