Logic of my Stored Procedure, Loop cursors - sql

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;

Related

Error(13,34): PLS-00201: identifier 'D.BNDNG_TYP' must be declared

I wrote a procedure where i am trying to insert value from source to destination table, i used bulk collect in order to execute the data for large amount of data.
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 d.bndng_typ%TYPE, BND_VAL d.bndng_val%TYPE, FINANCIAL_INST_ID d.financial_institution_id%TYPE, PRDCT_ID d.prdct_id%TYPE,
PRDCT_SUB_ID d.prdct_sub_id%TYPE, INSTRUMENT_ID d.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 ;
/
In above procedure, i removed some column names for security purpose.
I am getting two errors one is
PLS-00201: identifier 'D.BNDNG_TYP' must be declared and
PLS-00225: subprogram or cursor 'cur3' reference is out of scope
if any one can help to solve this.
In cursor, You cannot provide the type referring to alias from cursor query. You need to just provide the table name test1 instead of alias d.
CURSOR cur2( BND_TYPE test1.bndng_typ%TYPE,
BND_VAL test1.bndng_val%TYPE,
FINANCIAL_INST_ID test1.financial_institution_id%TYPE,
PRDCT_ID test1.prdct_id%TYPE,
PRDCT_SUB_ID test1.prdct_sub_id%TYPE,
INSTRUMENT_ID test1.instrmnt_id%TYPE)
IS SELECT
.....
This is untested as I don't have your tables and data, but as a first refactoring I would start with this type of structure:
create or replace procedure test2
as
cursor loan_data_cur
( 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
join ( select b.prdct_id
from test2 a
join test1 b
on b.ir_id = a.ir_id
and b.price_component_id = a.price_component_id
and b.financial_institution_id = a.financial_institution_id
group by b.prdct_id ) e
on d.prdct_id = e.prdct_id
where d.bndng_typ = loan_data_cur.bnd_type
and d.bndng_val = loan_data_cur.bnd_val
and d.financial_institution_id = loan_data_cur.financial_inst_id
and d.prdct_id = loan_data_cur.prdct_id
and d.prdct_sub_id = loan_data_cur.prdct_sub_id
and d.instrmnt_id = loan_data_cur.instrument_id;
begin
for loan_data in (
select distinct *
from test
)
loop
for loan_rate in loan_data_cur
( loan_data.bndng_typ
, loan_data.bndng_val
, loan_data.financial_institution_id
, loan_data.prdct_id
, loan_data.prdct_sub_id
, loan_data.instrmnt_id )
loop
update test t set t.seq_num = 1
where t.seq_num = 2
and t.pos_num = loan_data.pos_num
and t.pos_type = loan_data.pos_type
and t.price_component_id = loan_rate.price_component_id
and t.rprtd_till_dt = loan_data.rprtd_till_dt;
if sql%rowcount = 0 then
insert into test values (x, y, z);
end if;
end loop;
end loop;
commit;
end test2;
I would also looking at doing the join from test to test1 as a single cursor instead of explicitly with two separate cursors, as the optimiser might find a more efficient method, such as a hash join.
Probably the update/insert combination could be written as a single merge. Once you have a merge working, you might find you can apply it to the whole table in one shot and not need any cursor loops at all.
Bulk-collecting into an array could be useful if the number of row-by-row updates causes performance problems (if it's more than a few thousand, say). If so, you would want to structure it so that you could apply the updates/inserts using a forall construction, not more loops.

How do I use select in loop continue when statement?

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;
```

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.

Getting an error in sql, when executing code below.How to declare a table type in plsql. Am a beginner . Please suggest

create or replace procedure BAS_NUM_UPD is
cursor cur is
select distinct o.oi_b,mpr.pa_ke_i,ltrim(substr(convert_171_to_711(cp.p_t_num),1,7),'0') bs_nbr
from t_obj o, mat_pa_rel mp, cor_pa cp
where o.ob_t = 'something'
and o.oi_b = mp.oi_b
and mp.pa_ke_i = cp.pa_ke_i;
l_ba_num_at_i number(10) := get_attribute_id('Ba timber');
flag1 VARCHAR2(10);
type t1 is table of varchar2(10);
par_k t1;
BEGIN
for x in cur loop
BEGIN
select pa_ke_i into par_k from mat_pa_rel where oi_b=x.oi_b ;
if par_k.count=null then
insert into cs_val (oi_b, at_i, value, flag, ) values (x.oi_b, l_ba_num_at_i, null, 1);
end if;
select flag into flag1 from cs_val where at_i = l_ba_num_at_i and oi_b = x.oi_b
and value = x.bs_nbr;
EXCEPTION
when NO_DATA_FOUND THEN
insert into cs_val (oi_b, at_i, value, flag, )
values (x.oi_b, l_ba_num_at_i, x.bs_nbr, 1);
flag1 :='Nothing';
when OTHERS
then
raise_application_error(-20011,'Unknown Exception in PROCEDURE');
END;
end loop;
end BAS_NUM_UPD;
error:
PLS-00642: local collection types not allowed in SQL statements
You should get it running if you do a bulk collect
select pa_ke_i bulk collect into par_k from mat_pa_rel where oi_b=x.oi_b ;
Then I think the if is not right. I think you need to do
if par_k.count = 0 then
But to be honest you might just make a count
select count(*) into l_cnt from mat_pa_rel where oi_b=x.oi_b;
If l_cnt = 0 then ...
Of course l_cnt has to be defined.
You should create type t1 in the schema and not in the pl/sql block.