Dynamic SQL and Bulk Insert - oracle

I want to use the execute immediate command, to insert collection below PROCEDURE
CREATE OR REPLACE PROCEDURE P_LOAD_HLD(
p_app_name IN VARCHAR2,
p_filename IN VARCHAR2,
p_rectype IN VARCHAR2,
p_tabname IN VARCHAR2,
o_status OUT Number)
IS
type t_tab is table of holding_template%rowtype;
v_tab t_tab;
process_size number := 10000;
dummy_cursor SYS_REFCURSOR;
BEGIN
o_status := 0;
null;
if p_rectype='my_test' THEN
OPEN dummy_cursor FOR
select *
from TAB_staging a
where a.record_type = p_filename;
end if;
LOOP
BEGIN
FETCH dummy_cursor BULK COLLECT INTO V_TAB LIMIT PROCESS_SIZE;
FORALL I IN V_TAB.FIRST .. V_TAB.LAST
execute immediate 'insert into '||p_tabname||' values v_tab(i))';
EXCEPTION WHEN OTHERS THEN
FOR I IN 1 .. V_TAB.COUNT
LOOP
BEGIN
EXECUTE IMMEDIATE ' INSERT INTO '||p_tabname || ' VALUES ( '||
'v_tab(i).k0,v_tab(i).k1,v_tab(i).k2,v_tab(i).k3,v_tab(i).k4';
COMMIT;
EXCEPTION WHEN OTHERS THEN
RAISE;
END;
END LOOP;
END;
END LOOP;
END P_LOAD_HLD ;
this gives me the error message.
[Error] PLS-00435 (742: 7): PLS-00435: DML statement without BULK
In-BIND cannot be used inside FORALL

Related

Use Input Parameter of procedure to declare %Rowtype record

I am trying to insert the data into a table using Bulk Collect.
Here is the code:
create or replace procedure insert_via_bulk_collect authid current_user
as
lc_status number;
cursor lc_old_tb_data is select * from old_table_name;
type lc_old_tb_type is table of old_table_name%rowtype;
lc_old_tb_row lc_old_tb_type;
begin
open lc_old_tb_data;
loop
fetch lc_old_tb_data bulk collect into lc_old_tb_row;
forall i in 1..lc_old_tb_row.count
insert into new_table_name values lc_old_tb_row(i);
commit;
exit when lc_old_tb_data%notfound;
end loop;
close lc_old_tb_data;
end insert_via_bulk_collect;
It's working for me. But I want to pass the table name dynamically.
like
insert_via_bulk_collect(new_tb_nm varchar2(30), old_tb_name varchar2(30))
But I am not able to use those variables in cursor and declaring the record of OLD_TABLE%rowtype.
Is there a way to do this?
Try below code
CREATE OR REPLACE PROCEDURE INSERT_VIA_BULK_COLLECT
(NEW_TABLE_NAME varchar2, OLD_TABLE_NAME varchar2)
as
l_str varchar2(4000);
begin
l_str := 'declare
CURSOR lc_old_tb_data IS SELECT * FROM ' || OLD_TABLE_NAME || ';
TYPE lc_old_tb_type IS TABLE OF '|| OLD_TABLE_NAME || '%rowtype;
lc_old_tb_row lc_old_tb_type;
begin
Open lc_old_tb_data;
loop
fetch lc_old_tb_data bulk collect into lc_old_tb_row;
FORALL i IN 1..lc_old_tb_row.count
INSERT INTO ' || NEW_TABLE_NAME || ' VALUES lc_old_tb_row(i);
COMMIT;
exit when lc_old_tb_data%notfound;
end loop;
close lc_old_tb_data;
end; ';
dbms_output.put_line (l_str);
EXECUTE IMMEDIATE l_str;
end insert_via_bulk_collect;
You can add Exception handing. Also it is recommended to add limit for fetching of data as below fetch lc_old_tb_data bulk collect into lc_old_tb_row l_size; you can put l_size value as per your requirement.
As mentioned in comments, it is better to use direct insert ... select .... In your case, you can combine this way with execute immediate:
create or replace procedure insert_via_bulk_collect(new_tb_nm varchar2, old_tb_name varchar2) is
begin
execute immediate 'insert into ' || new_tb_nm ||
'(<columns list>) select <columns list> from ' || old_tb_nm;
end;

Passing table name inside proc to store its row count into a variable

I want to write a simple pl/sql procedure which will take a table name as input and it will store the table row count into a variable . I have written the bellow code :
CREATE OR REPLACE procedure ATT_REP.proc_compare2(table_name IN varchar2)
is
cnt NUMBER(30);
begin
execute immediate 'select count(*) from '||table_name||' into '||cnt ;
dbms_output.put_line(cnt);
end;
/
while executing i am getting 'PLS-00357: Table,View Or Sequence reference 'CES_ODS.ENTITY' not allowed in this context' error.
Please suggest what am i doing wrong . How can i make it working .
Try the following:
CREATE OR REPLACE PROCEDURE ATT_REP.proc_compare2 (table_name IN VARCHAR2)
IS
cnt NUMBER (30);
sql_stmt VARCHAR2 (200);
BEGIN
sql_stmt := 'select count(*) from ' || table_name;
EXECUTE IMMEDIATE sql_stmt INTO cnt;
DBMS_OUTPUT.put_line (cnt);
END;
/

Oracle : Procedure throws Invalid Datatype Error during execution ORA-00902: invalid datatype

CREATE TABLE T1 (EMP_NAME VARCHAR2 (40));
INSERT INTO t1
VALUES ('Vinoth');
COMMIT;
CREATE TABLE T2 (EMP_NAME VARCHAR2 (40));
CREATE OR REPLACE PACKAGE TEST_PKG_V
AS
PROCEDURE P_MAIN (p_status OUT VARCHAR2);
TYPE T1_TYPE IS RECORD (EMP_NAME T1.EMP_NAME%TYPE);
TYPE T1_TBL IS TABLE OF T1_TYPE;
END TEST_PKG_V;
/
CREATE OR REPLACE PACKAGE BODY TEST_PKG_V
AS
PROCEDURE P_MAIN (p_status OUT VARCHAR2)
IS
LV_T1_TBL T1_TBL := T1_TBL ();
CURSOR T1_CUR
IS
(SELECT EMP_NAME FROM t1);
BEGIN
OPEN T1_CUR;
LOOP
FETCH T1_CUR
BULK COLLECT INTO LV_T1_TBL
LIMIT 10000;
INSERT INTO t2 (EMP_NAME)
SELECT EMP_NAME FROM TABLE (LV_T1_TBL);
EXIT WHEN T1_CUR%NOTFOUND;
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS
THEN
p_status := 'FAIL';
RAISE;
END P_MAIN;
END TEST_PKG_V;
/
DECLARE
VAR VARCHAR2(4000);
BEGIN
TEST_PKG_V.P_MAIN(VAR);
END;
While executing the procedure throws the ORA-00902: invalid datatype.
If I comment out the insert statement inside the procedure, it is running perfectly fine. What is the problem here and help me with resolution.
For you to be able to do it like that, the types have to be created outside of your package. Here is the corrected version. Downside is, types beeing created, if you alter your table to change the type of the column, you might forget to modify the type.
CREATE TYPE T1_TYPE AS OBJECT ( EMP_NAME VARCHAR2(40));
CREATE TYPE T1_TBL AS TABLE OF T1_TYPE;
CREATE OR REPLACE PACKAGE TEST_PKG_V
AS
PROCEDURE P_MAIN (p_status OUT VARCHAR2);
END TEST_PKG_V;
/
CREATE OR REPLACE PACKAGE BODY TEST_PKG_V
AS
PROCEDURE P_MAIN (p_status OUT VARCHAR2)
IS
LV_T1_TBL T1_TBL;
CURSOR T1_CUR
IS
(SELECT T1_TYPE(EMP_NAME) EMP_NAME FROM t1);
BEGIN
OPEN T1_CUR;
LOOP
FETCH T1_CUR
BULK COLLECT INTO LV_T1_TBL
LIMIT 10000;
INSERT INTO t2 (EMP_NAME)
SELECT EMP_NAME FROM TABLE (LV_T1_TBL);
EXIT WHEN T1_CUR%NOTFOUND;
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS
THEN
p_status := 'FAIL';
RAISE;
END P_MAIN;
END TEST_PKG_V;
/
If you want your types to be generic and adapt to your table, I think you will have to do it something like that :
CREATE OR REPLACE PACKAGE TEST_PKG_V AS
TYPE T1_TYPE IS RECORD (EMP_NAME T1.EMP_NAME%TYPE);
TYPE T1_TBL IS TABLE OF T1_TYPE;
PROCEDURE P_MAIN (p_status OUT VARCHAR2);
FUNCTION GET_T1 RETURN T1_TBL PIPELINED;
END TEST_PKG_V;
/
CREATE OR REPLACE PACKAGE BODY TEST_PKG_V IS
CURSOR T1_CUR IS (SELECT EMP_NAME FROM t1);
FUNCTION GET_T1 RETURN T1_TBL PIPELINED IS
LV_T1_TBL T1_TBL;
BEGIN
OPEN T1_CUR;
FETCH T1_CUR BULK COLLECT INTO LV_T1_TBL;
CLOSE T1_CUR;
FOR IDX IN 1..LV_T1_TBL.COUNT LOOP
PIPE ROW (LV_T1_TBL(IDX));
END LOOP;
END;
PROCEDURE P_MAIN (p_status OUT VARCHAR2) IS
BEGIN
INSERT INTO t2 (EMP_NAME) SELECT EMP_NAME FROM TABLE (GET_T1);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
p_status := 'FAIL';
RAISE;
END P_MAIN;
END TEST_PKG_V;
/
alternatively, you could do it by iterating directly on your cursor :
CREATE OR REPLACE PACKAGE TEST_PKG_V AS
TYPE T1_TYPE IS RECORD (EMP_NAME T1.EMP_NAME%TYPE);
TYPE T1_TBL IS TABLE OF T1_TYPE;
PROCEDURE P_MAIN (p_status OUT VARCHAR2);
END TEST_PKG_V;
/
CREATE OR REPLACE PACKAGE BODY TEST_PKG_V IS
PROCEDURE P_MAIN (p_status OUT VARCHAR2) IS
CURSOR T1_CUR IS (SELECT EMP_NAME FROM t1);
BEGIN
FOR CURRENT_ROW IN T1_CUR LOOP
INSERT INTO t2 (EMP_NAME) VALUES (CURRENT_ROW.EMP_NAME);
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
p_status := 'FAIL';
RAISE;
END P_MAIN;
END TEST_PKG_V;
/

How can I make an entire PL/SQL code block dynamic with bind variables?

Background
I'm trying to make a re-usable PL/SQL procedure to move data from one
database to another.
For this purpose, I'm using dynamic SQL.
The procedure executes perfectly if I use a REPLACE with placeholders.
However, for security reasons, I want to use bind variables.
Question
How can I make an entire PL/SQL code block dynamic (with bind
variables)? If I use a REPLACE instead of the bind variables, it works
fine.
How to replicate
To replicate this in your database, create the following procedure as it is:
create or replace procedure move_data(i_schema_name in varchar2, i_table_name in varchar2, i_destination in varchar2) as
l_sql varchar2(32767);
l_cursor_limit pls_integer := 500;
l_values_list varchar2(32767);
begin
select listagg('l_to_be_moved(i).' || column_name, ', ') within group (order by column_id)
into l_values_list
from all_tab_cols
where owner = i_schema_name and
table_name = i_table_name and
virtual_column = 'NO';
l_sql := q'[
declare
l_cur_limit pls_integer := :l_cursor_limit;
cursor c_get_to_be_moved is
select :i_table_name.*, :i_table_name.rowid
from :i_table_name;
type tab_to_be_moved is table of c_get_to_be_moved%rowtype;
l_to_be_moved tab_to_be_moved;
begin
open c_get_to_be_moved;
loop
fetch c_get_to_be_moved
bulk collect into l_to_be_moved limit l_cur_limit;
exit when l_to_be_moved.count = 0;
for i in 1.. l_to_be_moved.count loop
begin
insert into :i_table_name#:i_destination values (:l_values_list);
exception
when others then
dbms_output.put_line(sqlerrm);
l_to_be_moved.delete(i);
end;
end loop;
forall i in 1.. l_to_be_moved.count
delete
from :i_table_name
where rowid = l_to_be_moved(i).rowid;
for i in 1..l_to_be_moved.count loop
if (sql%bulk_rowcount(i) = 0) then
raise_application_error(-20001, 'Could not find ROWID to delete. Rolling back...');
end if;
end loop;
commit;
end loop;
close c_get_to_be_moved;
exception
when others then
rollback;
dbms_output.put_line(sqlerrm);
end;]';
execute immediate l_sql using l_cursor_limit, i_table_name, i_destination, l_values_list;
exception
when others then
rollback;
dbms_output.put_line(sqlerrm);
end;
/
And then you can execute the procedure with the following:
begin
move_data('MySchemaName', 'MyTableName', 'MyDatabaseLinkName');
end;
/
Due to many reasons(inability to generate an appropriate execution plan, security checking, etc.) Oracle does not allow identifiers binding (table names, schema names, column names and so on). So if it's really necessary, the only way is to hard code those identifiers after some sort of validation (to prevent SQL injection).
If I understand well, you could try a trick, by using a dynamic SQL inside a dynamic SQL.
setup:
create table tab100 as select level l from dual connect by level <= 100;
create table tab200 as select level l from dual connect by level <= 200;
create table tabDest as select * from tab100 where 1 = 2;
This will not work:
create or replace procedure testBind (pTableName in varchar2) is
vSQL varchar2(32000);
begin
vSQL := 'insert into tabDest select * from :tableName';
execute immediate vSQL using pTableName;
end;
But this will do the trick:
create or replace procedure testBind2 (pTableName in varchar2) is
vSQL varchar2(32000);
begin
vSQL := q'[declare
vTab varchar2(30) := :tableName;
vSQL2 varchar2(32000) := 'insert into tabDest select * from ' || vTab;
begin
execute immediate vSQL2;
end;
]';
execute immediate vSQL using pTableName;
end;
I think you can do it simpler.
create or replace procedure move_data(i_schema_name in varchar2, i_table_name in varchar2, i_destination in varchar2) as
l_sql varchar2(32767);
begin
select listagg('l_to_be_moved(i).' || column_name, ', ') within group (order by column_id)
into l_values_list
from all_tab_cols
where owner = i_schema_name and
table_name = i_table_name and
virtual_column = 'NO';
l_sql := 'insert into '||i_destination||'.'||i_table_name||' select * from '||i_schema_name||'.'||i_table_name;
execute immediate l_sql;
end;
If you are concerned about SQL-Injection, have a look at package DBMS_ASSERT. This PL/SQL package provides function to validate properties of input values.

Oracle PL/SQL Ref Cursor Function Missing Char

When I run this code, the result should be 636790 but 63679 is only returned. Beating brains out on this one!!! Why the missing digit? Source table and column contain correct number of 636790.
create or replace package jt_types
as
type ttest is ref cursor;
end;
CREATE OR REPLACE FUNCTION jt_test
RETURN jt_types.ttest
IS
o_return jt_types.ttest;
BEGIN
OPEN o_return FOR
SELECT offn_id
FROM jt_offn_4
where offn_ID in (636790)
ORDER BY offn_id;
RETURN o_return;
END jt_test;
DECLARE
l_ids jt_types.ttest;
l_id NUMBER;
BEGIN
l_ids := jt_test;
LOOP
FETCH l_ids INTO l_id;
EXIT WHEN l_ids%NOTFOUND;
dbms_output.put_line('Rec: ' || l_id);
END LOOP;
CLOSE l_ids;
END;
The easiest way to do is to return sys_refcursor, here is the code:
create or replace
function jt_test
return sys_refcursor
is
o_return sys_refcursor;
begin
open o_return for
select offn_id
from jt_offn_4
where offn_ID = 636790;
return o_return;
end jt_test;
declare
l_ids sys_refcursor;
l_id number;
begin
l_ids := jt_test;
loop
fetch l_ids into l_id;
dbms_output.put_line('Rec: ' || l_id);
exit when l_ids%NOTFOUND;
end loop;
close l_ids;
end;

Resources