Bulk collect limit with execute immediate - oracle

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

Related

Cursor/bulk collect

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;

Bulk Collection with dynamic sql

I have a requirement where I get the table name and column name dynamically, I need to fetch the data and insert/ update in a table using bulk collect. As far as I checked I will not be able to use FORALL for dynamic sql which use dynamic table/column name. Please suggest any workaround to insert the data in a collection by bulk
Declare
Type Type_xx is table of varchar2(200);
Lv_Coll type_xx:=type_xx();
Lv_Coll2 sys_refcursor;
Lv_tab varchar2(200):='C_Sample_1';
Lv_Col Varchar2(200):='SHORT_NAME';
Out_tab Varchar2(200):='Test';
Begin
OPEN Lv_Coll2 FOR 'Select '||Lv_Col||' from '||Lv_tab||'';
Loop
Fetch Lv_Coll2 bulk collect into Lv_Coll limit 100;
Exit when Lv_Coll.count < 100;
forall i in Lv_Coll.first..Lv_Coll.last
Execute Immediate 'insert into '||Out_tab||' values ('||Lv_Coll(i)||')';
End loop;
end;
It gives the error
ORA-06550: line 16, column 17:
PLS-00801: internal error [*** ASSERT
at file pdw4.c, line 620; Unknown expression Expr = 283.;
Xanon__0x2b21bbdd8__AB[16, 17]]
Database version is 11.2.0.4.0
Probably not a preferred solution, but you can put your whole bulk insert PL/SQL block inside an EXECUTE IMMEDIATE statement.
Here is a way to do that :
declare
type t_ntt is table of test1%rowtype index by pls_integer;
l_ntt t_ntt;
c_limit INTEGER := 100;
sqltext VARCHAR2(1000);
table_name VARCHAR2(30) := 'test1';
column_name VARCHAR2(30) := 'A';
c_cursor sys_refcursor;
begin
open c_cursor for 'select '|| column_name|| ' from ' || table_name ;
loop
fetch c_cursor bulk collect into l_ntt limit c_limit;
exit when l_ntt.count = 0;
dbms_output.put_line(l_ntt.count);
forall i in indices of l_ntt
insert into test values l_ntt(i);
end loop;
close c_cursor;
end;
I have not done the commit on it.

ORA-06504: PL/SQL: Return types of Result Set variables while execution

I created an Object and procedure as below and while execution i get the below error.
ORA-06504: PL/SQL: Return types of Result Set variables or query do
not match ORA-06512: at line 8
CREATE OR REPLACE TYPE OBJ_TST AS OBJECT
(
COl_ID NUMBER (30, 0),
Col_DATE TIMESTAMP (6)
);
/
create or replace TYPE OBJ_TBL AS TABLE OF OBJ_TST;
/
CREATE OR REPLACE PROCEDURE TST_OBJ (input_date IN DATE,
out_cur OUT SYS_REFCURSOR )
AS
l_tab OBJ_TBL := OBJ_TBL ();
BEGIN
SELECT OBJ_TST (ti.col_id, ti.col_date)
BULK COLLECT INTO l_tab
FROM MY_TBL ti
WHERE ti.create_date BETWEEN input_date AND input_date + 1;
Open o_cur for select col_id,col_date from table(l_tab);
END TST_OBJ;
/
Execution brings me the above mentioned error. MY_TBL has column data type of (col_id and col_date) same as of my object.
DECLARE
a SYS_REFCURSOR;
var1 OBJ_TBL;
BEGIN
TST_OBJ (input_date => '21-Aug-2017', out_cur => a);
FETCH a bulk collect INTO var1;
For rec in 1..var1.count
LOOP
DBMS_OUTPUT.put_line (var1(rec).col_id ||' '|| var1(rec).Col_DATE);
END LOOP;
END;
/
ORA-06504: PL/SQL: Return types of Result Set variables or query do
not match ORA-06512: at line 8
However when i excute like this it works fine:
DECLARE
a SYS_REFCURSOR;
var1 NUMBER;
var2 TIMESTAMP (6);
BEGIN
TST_OBJ (i_date => '21-Aug-2017', out_cur => a);
LOOP
FETCH a INTO var1, var2;
EXIT WHEN a%NOTFOUND;
DBMS_OUTPUT.put_line (var1 ||' '|| var2);
END LOOP;
END;
Can anyone please suggest whats wrong here ?
You're using a table collection expression to unnest your table collection:
Open out_cur for select col_id,col_date from table(l_tab);
The query is returning two relational columns, not a single object, so your cursor has two columns too. Trying to bulk collect two relational columns into a single object in your anonymous block is throwing the exception.
You could, I suppose, recombine them as objects:
Open out_cur for select OBJ_TST(col_id,col_date) from table(l_tab);
or if you don't want to explicitly list the column/field names:
Open out_cur for select cast(multiset(select * from table(l_tab)) as obj_tbl) from dual;
But then in your example having the table type is a bit pointless, and you can just do:
CREATE OR REPLACE PROCEDURE TST_OBJ (input_date IN DATE,
out_cur OUT SYS_REFCURSOR )
AS
BEGIN
Open out_cur for
SELECT OBJ_TST (ti.col_id, ti.col_date)
FROM MY_TBL ti
WHERE ti.create_date BETWEEN input_date AND input_date + 1;
END TST_OBJ;
/
But I image you have some other use for the collection inside the function - modifying it before querying and returning it. Or you could make the second argument of OBJ_TBL type instead of a ref cursor, so the caller doesn't have to bulk collect that into its own local collection itself.
DECLARE
a SYS_REFCURSOR;
var1 OBJ_TBL;
BEGIN
TST_OBJ (input_date => '21-Aug-2017', out_cur => a);
FETCH a bulk collect INTO var1;
For rec in 1..var1.count
LOOP
DBMS_OUTPUT.put_line (var1(rec).col_id ||' '|| var1(rec).Col_DATE);
END LOOP;
END;
/
The cursor a has two columns and you are trying to bulk collect them into a single variable. Oracle will not wrap them in a OBJ_TST object and it can't match them.
Why use cursors at all:
CREATE OR REPLACE PROCEDURE TST_OBJ (
input_date IN DATE,
out_objs OUT OBJ_TBL
)
AS
BEGIN
SELECT OBJ_TST( col_id, col_date)
BULK COLLECT INTO out_objs
FROM MY_TBL
WHERE create_date BETWEEN input_date AND input_date + 1;
END TST_OBJ;
/
Then you can just do:
DECLARE
var1 OBJ_TBL;
BEGIN
TST_OBJ (
input_date => DATE '2017-08-21',
out_objs => var1
);
For rec in 1..var1.count LOOP
DBMS_OUTPUT.put_line (var1(rec).col_id ||' '|| var1(rec).Col_DATE);
END LOOP;
END;
/

For Loop in Oracle / Toad

I am new to Oracle and PL/SQL and am trying to do the following.
I am returning the column names from a table name that is stored in a variable
variable v_table varchar2(100)
begin
select 'mytable' into :v_table from dual;
end;
select column_name from all_tab_columns where table_name = :v_table
This returns a rowset
column_name
colname1
colname2
colname3
I would like to loop through the returned rowset and get some stats for each column.
select count distinct(colname1), min(colname1), max(colname1)
from :v_table
group by min(colname1), max(colname1)
However I cannot figure out how to loop through this rowset.
By using below you can display column names of a table and append your sql query as per your requirement...
declare
v_table varchar2(100):='TABLE_NAME';
TYPE NT_VAR1 IS TABLE OF VARCHAR2(30);
TYP_NT NT_VAR1;
begin
select column_name BULK COLLECT INTO TYP_NT from all_tab_columns where table_name =v_table
order by column_id;
FOR I IN 1..TYP_NT.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE('COLUMN '||I||': '||TYP_NT(I));
END LOOP;
end;
Note: You should run select query dynamically means with the use of execute immediate why because you are passing table_name at run time.
Edit
After editing your query in for loop it will look like below.
declare
v_table varchar2(100):='TABLE_NAME';
TYPE NT_VAR1 IS TABLE OF VARCHAR2(30);
TYP_NT NT_VAR1;
var2 varchar2(2000);
var3 varchar2(2000);
begin
select column_name BULK COLLECT INTO TYP_NT from all_tab_columns where table_name =v_table
order by column_id;
FOR I IN 1..TYP_NT.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE('COLUMN '||I||': '||TYP_NT(I));
EXECUTE IMMEDIATE 'SELECT to_char(min('||TYP_NT(I)||')),to_char(max('||TYP_NT(I)||')) from '||v_table into var2,var3;
dbms_output.put_line(var2||' '||var3);
END LOOP;
end;
Note: Find the max data length of a column present in your table and assign same data type to var2 and var3 variables.

Get the count of each table

I was trying to create a procedure to fetch the total count of rows of all tables corresponding to a schema.
I am proceeding with a cursor which store the total list of tables and and the same is iterate further. Even though the functionality is not tested.The procedure creation compiled with the following errors.
create or replace
PROCEDURE PROC_TABLE_COUNT
AS
table_count NUMBER;
CURSOR total_tables
IS
SELECT table_name FROM dba_tables WHERE owner = 'OWNER_NAME';
BEGIN
FOR i IN total_tables
LOOP
SELECT COUNT (*) INTO table_count FROM dba_tables db where db.table_name = i.table_name;
END LOOP;
END PROC_TABLE_COUNT;
1)Error(7,6): PL/SQL: SQL Statement ignored
2)Error(7,33): PL/SQL: ORA-00942: table or view does not exist
3)Error(11,1): PL/SQL: SQL Statement ignored
4)Error(11,76): PL/SQL: ORA-00904: "I"."TABLE_NAME": invalid identifier
5)Error(11,76): PLS-00364: loop index variable 'I' use is invalid
Question 1:
Is the error 2(at dba_tables) is due to the grant being denied? By right clicking on the procedure name ,I tried to assign the privilege to debug and execute. But still the error persists.
Question 2:
Regarding the invalid identifier. Why is this error coming?
UPDATE:
As per one of the valuable comment I have changed the query and the compile error is gone. Now there is an issue in the logic.
create or replace procedure proc_tab_count as
table_count NUMBER;
CURSOR total_tables
IS
SELECT table_name FROM user_tables;
BEGIN
FOR i IN total_tables
LOOP
SELECT COUNT (*) INTO table_count FROM user_tables WHERE db.table_name = i.table_name; --Wrong logic here
DBMS_OUTPUT.put_line(i.table_name||'-COUNT:'||table_count);
END LOOP;
end proc_tab_count;
output is coming like:
Table1 -COUNT:1
Table2 -COUNT:1
Table3 -COUNT:1
Table3 -COUNT:1
Table4 -COUNT:1
Table5 -COUNT:1
Guess, you want to count rows in all your tables, and we need a dynamic SQL here.
EXCEUTE IMMEDIATE is used to frame the dynamic SQL making the OWNER.TABLE_NAME dynamic.
create or replace procedure proc_tab_count as
table_count NUMBER;
CURSOR total_tables
IS
SELECT table_name FROM user_tables;
BEGIN
FOR i IN total_tables
LOOP
/* Handle Exceptions */
BEGIN
EXECUTE IMMEDIATE 'SELECT COUNT (*) FROM '|| i.table_name INTO table_count;
DBMS_OUTPUT.put_line(i.table_name||'-COUNT:'||table_count);
EXCEPTION WHEN OTHERS
THEN
DBMS_OUTPUT.put_line('Error while Querying '||i.table_name||'-Error:'||SQLERRM);
END;
END LOOP;
end proc_tab_count;
I'm not so sure that the accepted answer works. Does user_tables contain owner? Otherwise, it's spot on.
Anyway. Another approach using SYS_REFCURSOR:
CREATE OR REPLACE PROCEDURE proc_table_count
CURSOR total_tables IS
SELECT table_name
FROM user_tables
ORDER BY table_name;
table_count NUMBER;
v_sql VARCHAR2(100);
v_cursor SYS_REFCURSOR;
BEGIN
FOR i IN total_tables
LOOP
BEGIN
v_sql := 'SELECT COUNT (*) FROM '||i.table_name;
OPEN v_cursor FOR v_sql;
FETCH v_cursor INTO table_count;
CLOSE v_cursor;
DBMS_OUTPUT.put_line(i.table_name||'-COUNT:'||table_count);
EXCEPTION WHEN OTHERS
THEN
DBMS_OUTPUT.put_line('Error while Querying '||i.table_name||'-Error:'||SQLERRM);
END;
END LOOP;
end proc_tab_count;

Resources