Cursor/bulk collect - oracle

I was trying to display hiredate using lead function with cursor however I'm getting this error message - PLS-00435: DML statement without BULK In-BIND cannot be used inside FORALL. Please guide me.
SET SERVEROUTPUT ON
DECLARE
CURSOR C_11 IS SELECT * FROM EMP;
TYPE DD IS TABLE OF EMP%ROWTYPE;
CC DD;
VV EMP.HIREDATE%TYPE;
GG EMP.HIREDATE%TYPE;
BEGIN
OPEN C_11;
LOOP
FETCH C_11 BULK COLLECT INTO CC LIMIT 1000;
FORALL I IN CC.FIRST..CC.LAST
SELECT HIREDATE,LEAD(HIREDATE) OVER(ORDER BY HIREDATE) INTO VV,GG FROM EMP;
DBMS_OUTPUT.PUT_LINE (VV ||' '||GG);
EXIT WHEN C_11%NOTFOUND;
END LOOP;
CLOSE C_11;
END;

PLS-00435: DML statement without BULK In-BIND cannot be used inside
FORALL.
FORALL statement is only used for INSERT / UPDATE or DELETE . It doesnot support SELECT.
Read the FORALL Restrictions:
https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/forall_statement.htm
Alternatively you can do it as shown below:
DECLARE
CURSOR c_11 IS
SELECT hiredate,
LEAD(hiredate) OVER( ORDER BY hiredate ) DT
FROM emp;
TYPE dd IS TABLE OF c_11%rowtype;
cc dd;
BEGIN
OPEN c_11;
LOOP
FETCH c_11 BULK COLLECT INTO cc LIMIT 100;
FOR i IN 1..cc.count
LOOP
dbms_output.put_line(cc(i).hiredate || ' -- ' || cc(i).dt);
END LOOP;
EXIT WHEN c_11%notfound;
END LOOP;
CLOSE c_11;
END;

Related

Bulk collect limit with execute immediate

For operating on millions of records I want to put a limit of 500 but the following code gives error.
Error report:
ORA-06550: line 6, column 49:
PLS-00103: Encountered the symbol "LIMIT" when expecting one of the following:
DECLARE
TYPE EMP_T IS TABLE OF NUMBER;
EMP_ID EMP_T;
QRY VARCHAR2(4000):='SELECT EMPLOYEE_ID FROM EMPLOYEES';
begin
execute immediate QRY bulk collect into EMP_ID LIMIT 500;
END;
That's not the way to use LIMIT clause. I think you can't use LIMIT clause in BULK COLLECT with EXECUTE IMMEDIATE. BULK COLLECT LIMIT in EXECUTE IMMEDIATE
Example:
DECLARE
TYPE EMP_T IS TABLE OF NUMBER;
EMP_ID EMP_T;
CURSOR c_data IS SELECT empid FROM EMPLOYEE;
begin
OPEN c_data;
LOOP
FETCH c_data
BULK COLLECT INTO EMP_ID LIMIT 100;
EXIT WHEN EMP_ID.count = 0;
-- Process contents of collection here.
DBMS_OUTPUT.put_line(EMP_ID.count || ' rows');
END LOOP;
CLOSE c_data;
END;
/

I am using cursor with a dynamic sql but when using further with bulk binding results are not coming

With PL/SQL oracle, I am using cursor with a dynamic sql but when using further with bulk binding results are not coming, but when using without bulk binding it works. Please suggest what am i missing here, sample below is a code snippet for your reference, although it is not the exact code but will give you an overview what i am trying to do.
cur_data SYS_REFCURSOR;
TYPE sample_data IS RECORD
(
col1 VSAMPLEDATA.COL1%TYPE,
col2 VSAMPLEDATA.COL2%TYPE
);
TYPE reclist IS TABLE OF sample_data ;
rec reclist;
Begin
p_query_string:='SELECT * from VSAMPLEDATA where COL2=:pVal';
OPEN cur_data FOR p_query_string USING 'DATA1';
LOOP
FETCH cur_data
BULK COLLECT INTO rec LIMIT 1000;
EXIT WHEN
rec.COUNT = 0;
FOR indx IN 1 .. rec.COUNT
LOOP
doing something;
END LOOP
END LOOP
CLOSE cur_data ;
You can avoid dynamic SQL and build a simpler, safer parametric cursor; for example, reflecting the structure of your code:
DECLARE
TYPE sample_data IS RECORD(col1 VSAMPLEDATA.COL1%TYPE, col2 VSAMPLEDATA.COL2%TYPE);
TYPE reclist IS TABLE OF sample_data;
--
rec reclist;
--
CURSOR cur_data(param IN VARCHAR2) IS
SELECT *
FROM VSAMPLEDATA
WHERE COL2 = param;
BEGIN
OPEN cur_data('DATA1');
LOOP
FETCH cur_data BULK COLLECT INTO rec LIMIT 1000;
EXIT WHEN rec.COUNT = 0;
FOR indx IN 1 .. rec.COUNT
LOOP
DBMS_OUTPUT.put_line(rec(indx).col1 || ' - ' || rec(indx).col2);
END LOOP;
END LOOP;
CLOSE cur_data;
END;
Test case:
create table VSAMPLEDATA(col1, col2) as
select 1, 'DATA1' from dual union all
select 2, 'DATA2' from dual
The result:
1 - DATA1
You are using sys_refcursor in wrong way.Also there is no need to use cursor in your case. sys_refcursor can be used to pass cursors from and to a stored procedure. SYS_REFCURSOR is a pre-declared weak ref cursor.
Also you are not executing the query so no result when using a bind variable. Execute Immediate is missing while using a binding the variable. See below example:
declare
TYPE sample_data IS RECORD
(
col1 EMPLOYEE.EMPLOYEE_ID%type,
col2 EMPLOYEE.FIRST_NAME%type
);
TYPE reclist IS TABLE OF sample_data;
rec reclist;
p_query_string varchar2(1000);
BEGIN
p_query_string := 'SELECT EMPLOYEE_ID,FIRST_NAME from EMPLOYEE where EMPLOYEE_ID=:pVal';
Execute immediate p_query_string BULK COLLECT INTO rec USING 1;
FOR indx IN 1 .. rec.COUNT
LOOP
--doing something;
dbms_output.put_line(rec(indx).col1);
END LOOP;
End;
Hi everyone this one has been resolved. It was always working, only problem was with the underlying query which had some conditional checks due to that results were not coming.

Table or view does not exist for a nested implicit cursor

DECLARE
sql_stmt varchar2(400);
cursor c1 is SELECT view_name from all_views where owner = 'owner1' AND view_name like 'IRV_%' OR view_name like 'RD_%' order by view_name;
BEGIN
for i IN c1 loop
sql_stmt := 'create table new_table as select * FROM owner1.view1 minus select * FROM owner2.view1';
dbms_output.put_line(sql_stmt);
execute immediate sql_stmt;
for ii IN (SELECT * from new_table) loop
dbms_output.put_line('inner loop');
end loop; -- for ii
execute immediate 'drop table new_table';
exit when c1%NOTFOUND;
end loop; -- for i
END;
I get in the script output:
ORA-06550: line 9, column 32:
PL/SQL: ORA-00942: table or view does not exist
Line 9 is: for ii IN (SELECT * from new_table) loop
Thank you in advance.
Ok HERE IS MY NEW CODE: Thank you GurV for your help.
DECLARE
CURSOR c1
IS
SELECT view_name
FROM all_views
WHERE owner = 'DBA_A'
AND view_name LIKE 'IRV_%'
OR view_name LIKE 'RD_%'
ORDER BY view_name;
BEGIN
FOR i IN c1
LOOP
FOR ii IN ('select * FROM DBA_A.' || i.VIEW_NAME || ' minus select * FROM DBA_B.' || i.VIEW_NAME)
LOOP
dbms_output.put_line('inner loop');
-- put the results from the select in the FOR ii loop in a listAgg string
-- do stuff
END LOOP; -- for ii
END LOOP; -- for i
END;
Error generated at END LOOP; -- for ii
PLS-00103: Encountered the symbol "END" when expecting one of the following:
The dbms_output.put_line shows the proper select is generated.
Thanks again.
Rich
Your code is not aware that you'll be creating a table dynamically (how could it?).
Generally, The PL/SQL Compiler will check your block for the following before executing it:
Check syntax.
Check semantics and security, and resolve names.
Generate (and optimize) bytecode (a.k.a. MCode).
It's on the 2nd Step where your code fails because There isn't a table named new_table prior to compile time.
I think there is no need of Dynamic SQL here. Also, you do not need to put an exit condition when using a for loop on cursor. You can do this:
DECLARE
CURSOR c1
IS
SELECT view_name
FROM all_views
WHERE owner = 'owner1'
AND view_name LIKE 'IRV_%'
OR view_name LIKE 'RD_%'
ORDER BY view_name;
BEGIN
FOR i IN c1
LOOP
FOR ii IN (select * FROM owner1.view1 minus select * FROM owner2.view1)
LOOP
DBMS_OUTPUT.put_line('Hey there');
-- do more stuff
END LOOP; -- for ii
END LOOP; -- for i
END;

Oracle Bulk Collect with Limit and For All Not Processing All Records Correctly

I need to process close to 60k records of an Oracle table through a stored procedure. The processing is that for each such row, I need to delete and update a row in a second table and insert a row in a third table.
Using cursor looping, the procedure takes around 6-8 hours to complete. If I switch to Bulk Collect with Limit, the execution time is reduced but processing is not correct. Following is the bulk collect version of the procedure
create or replace procedure myproc()
is
cursor c1 is select col1,col2,col3 from tab1 where col4=3;
type t1 is table of c1%rowtype;
v_t1 t1;
begin
open c1;
loop
fetch c1 bulk collect into v_t1 limit 1000;
exit when v_t1.count=0;
forall i in 1..v_t1.count
delete from tab2 where tab2.col1=v_t1(i).col1;
commit;
forall i in 1..v_t1.count
update tab2 set tab2.col1=v_t1(i).col1 where tab2.col2=v_t1(i).col2;
commit;
forall i in 1..v_t1.count
insert into tab3 values(v_t1(i).col1,v_t1(i).col2,v_t1(i).col3);
commit;
end loop;
close c2;
end;
For around 20k of these records, the first delete operation is processed correctly but subsequent update and insert is not processed. For the remaining 40k records all three operations are processed correctly.
Am I missing something? Also what is the maximum LIMIT value I can use with Bulk Collect?
You should try using SAVE EXCEPTIONS clause of FORALL, something like (untested):
create or replace procedure myproc
as
cursor c1 is select col1,col2,col3 from tab1 where col4=3;
type t1 is table of c1%rowtype;
v_t1 t1;
dml_errors EXCEPTION;
PRAGMA exception_init(dml_errors, -24381);
l_errors number;
l_errno number;
l_msg varchar2(4000);
l_idx number;
begin
open c1;
loop
fetch c1 bulk collect into v_t1 limit 1000;
-- process v_t1 data
BEGIN
forall i in 1..v_t1.count SAVE EXCEPTIONS
delete from tab2 where tab2.col1=v_t1(i).col1;
commit;
EXCEPTION
when DML_ERRORS 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;
-- log these to a table maybe, or just output
end loop;
END;
exit when c1%notfound;
end loop;
close c2;
end;

Bulk Collect Oracle

Can bulk collect be done if data is getting inserted into table A from Table B and while selecting data, substr, instr, trunc functions has been used on columns fetched?
INSERT INTO A
SELECT
DISTINCT
SUBSTR(b.component, 1, INSTR(b.component, ':', 1) - 1),
TRUNC(c.end_dt, 'DDD'),
FROM
B b,
C c
WHERE
TRUNC(c.end_dt)=TRUNC(b.last_Date, 'DDD') ;
How can I insert data into table A using bulk collect?
The only reason you'd use Bulk Collect and FORALL to insert rows is when you absolutely need to process/insert rows in chunks. Otherwise always use SQL.
DECLARE
CURSOR c_data IS
SELECT * FROM source_tab;
--
TYPE t_source_tab IS TABLE OF source_tab%ROWTYPE;
l_tab t_source_tab;
v_limit NUMBER:= 1000;
BEGIN
OPEN c_data;
LOOP
FETCH c_data BULK COLLECT INTO l_tab LIMIT v_limit;
EXIT WHEN l_tab.count = 0;
-- Insert --
FORALL i IN l_tab.first .. l_tab.last
INSERT INTO destination_tab VALUES l_tab(i);
COMMIT;
-- prints number of rows processed --
DBMS_OUTPUT.PUT_LINE ('Inserted ' || SQL%ROWCOUNT || ' rows:');
-- Print nested table of records - optional.
-- May overflow the buffer and slow down the performance if you process many rows.
-- Use for testing only and limit the rows with Rownum or Row_Number() in cursor query:
FOR i IN l_tab.FIRST..l_tab.LAST -- or l_tab.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE (l_tab(i).hire_date ||chr(9)||l_tab(i).last_name ||chr(9)||l_tab(i).first_name);
END LOOP;
END LOOP;
CLOSE c_data;
END
/

Resources