Forall inserts with dynamic sql - sql

I have a set of insert commands stored in a table type variable (varchar2). The records are inserted into same columns of the same table.
How can I write forall loop to execute these insert queries?
Thanks

You can loop on nested tables as in the following example (from PL/SQL Collections and Records - Nested Tables)
FOR i IN names.FIRST .. names.LAST LOOP
DBMS_OUTPUT.PUT_LINE(names(i));
END LOOP;
and you can run a dynamically created query by means of the execute immediate construct.
Saying that, your problem can be resolved with the following example code.
declare
type table_varchar is table of varchar2(4000);
tv_inserts table_varchar := table_varchar();
begin
tv_inserts.extend();
tv_inserts(tv_inserts.count) := 'insert into test_table(col1, col2) values(1, 10)';
tv_inserts.extend();
tv_inserts(tv_inserts.count) := 'insert into test_table(col1, col2) values(2, 20)';
tv_inserts.extend();
tv_inserts(tv_inserts.count) := 'insert into test_table(col1, col2) values(3, 30)';
for ind in tv_inserts.first..tv_inserts.last
loop
execute immediate tv_inserts(ind);
end loop;
end;
The forall statement can be used with the execute immediate but fixing the table name in the string as in the example below
declare
type table_varchar is table of varchar2(4000);
tv_inserts table_varchar := table_varchar();
begin
tv_inserts.extend();
tv_inserts(tv_inserts.count) := '1';
tv_inserts.extend();
tv_inserts(tv_inserts.count) := '2';
tv_inserts.extend();
tv_inserts(tv_inserts.count) := '3';
for ind in tv_inserts.first..tv_inserts.last
loop
execute immediate 'insert into test_table(col1) values(:a)'
using tv_inserts(ind);
end loop;
end;
Note that this approach is not feasible without a previous processing of your input array.

Related

How to execute results of dbms_output.put_line

There is a table contains this kind of data: select to_char(sysdate,'day') from dual in a column. I want to get results of the every query that the table keeps.
My result set should be the result of select to_char(sysdate,'day') from dual query. So in this case it is a tuesday.
SO_SQL_BODY is Varchar2.
I wrote this code but it returns only table data.
CREATE or replace PROCEDURE a_proc
AS
CURSOR var_cur IS
select SO_SQL_BODY FROM SO_SUB_VARS group by SO_SQL_BODY;
var_t var_cur%ROWTYPE;
TYPE var_ntt IS TABLE OF var_t%TYPE;
var_names var_ntt;
BEGIN
OPEN var_cur;
FETCH var_cur BULK COLLECT INTO var_names;
CLOSE var_cur;
FOR indx IN 1..var_names.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(var_names(indx).SO_SQL_BODY);
END LOOP;
END a_proc;
DECLARE
res varchar2(4000);
sql_str varchar2(1000);
BEGIN
FOR r IN
(select SO_SQL_BODY FROM SO_SUB_VARS WHERE SO_SQL_BODY IS NOT NULL
)
LOOP
sql_str := r.SO_SQL_BODY;
EXECUTE immediate sql_str INTO res;
dbms_output.put_line(sql_str);
dbms_output.put_line('***********************');
dbms_output.put_line(res);
dbms_output.put_line('***********************');
END LOOP;
END;
/
Try this - iterate to not null records - execute them and print the result.This script works supposing the fact that SO_SQL_BODY contains a query which projects only one column.Also if the projection is with more than two columns then try to use a refcursor and dbms_sql package
İf var_names(indx).SO_SQL_BODY output is a runnable sql text;
CREATE or replace PROCEDURE a_proc
AS
CURSOR var_cur IS
select SO_SQL_BODY FROM SO_SUB_VARS group by SO_SQL_BODY;
var_t var_cur%ROWTYPE;
TYPE var_ntt IS TABLE OF var_t%TYPE;
var_names var_ntt;
BEGIN
OPEN var_cur;
FETCH var_cur BULK COLLECT INTO var_names;
CLOSE var_cur;
FOR indx IN 1..var_names.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(var_names(indx).SO_SQL_BODY);
EXECUTE IMMEDIATE var_names(indx).SO_SQL_BODY;
END LOOP;
END a_proc;
You don't need a full cursor for this example. An implicit one would make it a lot shorter.
create or replace procedure a_proc is
lReturnValue varchar2(250);
begin
for q in (select so_sql_body from so_sub_vars group by so_sql_body)
loop
execute immediate q.so_sql_body into lReturnValue;
dbms_output.put_line(lReturnValue);
end loop;
end a_proc;
You should add an exception handler that will care for cases where there is a bad SQL query in your table. Also note that executing querys saved in a database table is your entry point to SQL injection.

How to Improve data loading when field name is stored in a table

I need to make the following code efficient (speed -up). It is extremely slow.
I am updating and inserting into multiple tables and the field names to update are stored in a temp table with the layout below:
variablename
valueforvariablename
foreign_key1
foreign_key2
databasetable_name
Code:
DECLARE
TYPE t_char_array IS TABLE OF VARCHAR2(32) INDEX BY BINARY_INTEGER;
t_cursor SYS_REFCURSOR;
t_id t_char_array;
t_varnm t_char_array;
t_valuex t_char_array;
t_fpk1 t_num_array;
t_dbtable t_char_array;
t_fpk2 t_num_array;
t_row_count NUMBER :=0;
sql_stmt VARCHAR2(700);
sql_cursor VARCHAR2(300);
sql_inserr VARCHAR2(100);
BEGIN
sql_cursor :='SELECT id, varnm, valuex,fpk1,dbtable, fpk2 FROM '||&tableq;
OPEN t_cursor for sql_cursor;
LOOP
FETCH t_cursor
BULK COLLECT INTO t_id, t_varnm, t_valuex, t_fpk1, t_dbtable,t_fpk2
LIMIT 1000;
EXIT WHEN t_row_count = t_cursor%ROWCOUNT;
t_row_count := t_cursor%ROWCOUNT;
FOR i IN 1..t_id.count loop
if t_dbtable(i) =' Temp_table' then
sql_stmt:='UPDATE Temp_table SET '||t_varnm(i)||' =:1
WHERE pk = '||t_fpk1(i)||' and dictionary_pk = '||t_fpk2(i) ;
if t_vartype(i) ='D' then
EXECUTE IMMEDIATE sql_stmt using to_date(t_valuex(i),'YYYYMMDD');
else
EXECUTE IMMEDIATE sql_stmt using (t_valuex(i));
end if;
if sql%rowcount = 0 then
sql_stmt:='insert into Temp_table (pk, fpk2,'||t_varnm(i)||' )
VALUES (seq_steps.nextval,t_fpk2(i),t_valuex(i))';
end if;
end if;
end loop;
exit when t_id.count < 1000;
end loop;
commit;
Combine multiple INSERTs and UPDATEs into a smaller number of MERGEs. This approach will read from the input table more times but may significantly reduce the amount of dynamic SQL. This is actually more logical work but it may perform much better because it cuts down on the switches between SQL and PL/SQL.
begin
for merges in
(
select distinct id, varnm, vartype
from &tableq
where dbtable = ' Temp_table'
) loop
execute immediate '
merge into temp_table
using
(
select fpk1, fpk2,
'||case when mergest.vartype = 'D' then
'to_date(valuex, ''YYYYMMDD'')'
else 'valuex' end||'
from &tableq
where varnm = :merges_varnm
and vartype = :merges_vartype
) new_data
on
(
temp_table.pk = new_data.fpk1
and
temp_table.dictionary_pk = new_data.fpk2
)
when matched then update set
'||merges.varnm||' = new_data.valuex
when not matched then insert(pk, fpk2, '||merges.varnm||')
values(seq_steps.nextval, new_data.fpk2, valuex)
' using merges.varnm, merges.vartype;
end loop;
end;
/

Dynamic SQL LOOP

Dynamic SQL is not my friend, basically the idea is that I can use the procedure with the "p_in_table" paramter to get the number of rows contained in the table.
CREATE OR REPLACE PROCEDURE how_many_rows(p_in_table VARCHAR2)
IS
TYPE cur_cur IS REF CURSOR;
v_cur_cur cur_cur;
v_rowcount NUMBER(28);
v_cur_txt VARCHAR2(299);
BEGIN
v_cur_txt := 'SELECT * FROM ' || p_in_table;
OPEN v_cur_cur FOR v_cur_txt;
LOOP
v_rowcount := v_cur_cur%ROWCOUNT;
EXIT WHEN v_cur_cur%NOTFOUND;
END LOOP;
CLOSE v_cur_cur;
dbms_output.put_line(v_rowcount);
END;
Would preciate it if someone would tell me what am I doing wrong?
The problem is that you not iterating through cursor - no fetch statement or something like that, so, basically, you have an infinite loop. To avoid this you need to do something like this:
CREATE OR REPLACE PROCEDURE how_many_rows
(p_in_table VARCHAR2) IS
TYPE cur_cur IS REF CURSOR;
v_cur_cur cur_cur;
v_rowcount NUMBER(28);
v_cur_txt VARCHAR2(299);
v_row SOME_TABLE%ROWTYPE; --add row variable
BEGIN
v_cur_txt := 'SELECT * FROM '|| p_in_table;
OPEN v_cur_cur FOR v_cur_txt;
LOOP
v_rowcount := v_cur_cur%ROWCOUNT;
FETCH v_cur_cur INTO v_row; --fetch a row in it
EXIT WHEN v_cur_cur%NOTFOUND;
END LOOP;
CLOSE v_cur_cur;
DBMS_OUTPUT.PUT_LINE(v_rowcount);
END;
But, as you can see, to do this you need to know, what table you're quering, so this is not general solution. Maybe there is a workaround for this, but i suggest, you use more simple and efficient approach, for example with EXECUTE IMMEDIATE:
CREATE OR REPLACE PROCEDURE HOW_MANY_ROWS(p_in_table VARCHAR2)
IS
v_tmp NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT COUNT(1) FROM ' || p_in_table INTO v_tmp;
DBMS_OUTPUT.PUT_LINE(v_tmp);
END;
Ok, I gave a thought on how to achieve this using your way, and here is what i've ended up with - just fetch ROWNUM from your table, every table has it and you know it's type - NUMBER. So this procedure will work in general case:
CREATE OR REPLACE PROCEDURE how_many_rows
(p_in_table VARCHAR2) IS
TYPE cur_cur IS REF CURSOR;
v_cur_cur cur_cur;
v_rowcount NUMBER(28);
v_cur_txt VARCHAR2(299);
v_row NUMBER; --add rownum variable
BEGIN
v_cur_txt := 'SELECT ROWNUM FROM '|| p_in_table; --select only rownum from target table
OPEN v_cur_cur FOR v_cur_txt;
LOOP
v_rowcount := v_cur_cur%ROWCOUNT;
FETCH v_cur_cur INTO v_row; --fetch rownum in it
EXIT WHEN v_cur_cur%NOTFOUND;
END LOOP;
CLOSE v_cur_cur;
DBMS_OUTPUT.PUT_LINE(v_rowcount);
END;

returning into clause with dynamic sql

I have copied and pasted this from http://www.oracle-base.com/articles/misc/dml-returning-into-clause.php
But this code goes on and on while executing ...
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF t1.id%TYPE;
l_tab t_tab;
BEGIN
EXECUTE IMMEDIATE 'UPDATE t1
SET description =' ||'description '||'
RETURNING id INTO :l_tab'
RETURNING BULK COLLECT INTO l_tab;
FOR i IN l_tab.first .. l_tab.last LOOP
DBMS_OUTPUT.put_line('UPDATE ID=' || l_tab(i));
END LOOP;
COMMIT;
END;
Any mistypes or errors in the code ?
You have to add a / at the end of the PL/SQL block to execute it.

creating a procedure for nested tables

The following codes successfully creates a procedure using sql*plus
CREATE OR REPLACE PROCEDURE input_order (pat_id in char, vis_vdate in date, vis_act in number,
vac_vacc in char)
AS
BEGIN
DBMS_OUTPUT.PUT_LINE ('Insert attempted');
insert into vaccinations(pid,vdate,action,vaccinated) values(pat_id,vis_vdate,vis_act,vac_vacc);
DBMS_OUTPUT.PUT_LINE ('Insert succeeded');
EXCEPTION
WHEN others THEN DBMS_OUTPUT.PUT_LINE ('error');
DBMS_OUTPUT.PUT_LINE ('Insert rejected');
END;
/
However my intension is to create a similar procedure which includes populating a nested table as an attribute of a table
for example: supposing 'vis_act' is a nested table
with type vis_set_t
and attributes visname and visurname
i tried it this way but kept getting errors
CREATE OR REPLACE PROCEDURE input_order (pat_id in char, vis_vdate in date, vis_act in
vis_set_t, vac_vacc in char)
AS
BEGIN
DBMS_OUTPUT.PUT_LINE ('Insert attempted');
insert into vaccinations(pid,vdate,vis_set_t(visname,visurname),vaccinated) values
(pat_id,vis_vdate,vis_act,vac_vacc);
DBMS_OUTPUT.PUT_LINE ('Insert succeeded');
EXCEPTION
WHEN others THEN DBMS_OUTPUT.PUT_LINE ('error');
DBMS_OUTPUT.PUT_LINE ('Insert rejected');
END;
/
General insert example.
As I mentioned in comments I'm not sure what are you trying to accomplish in your procedure. This is general insert example. Feel free to ask more questions...:
DECLARE
TYPE EmpTabTyp IS TABLE OF scott.emp%ROWTYPE;
emp_tab EmpTabTyp:= EmpTabTyp();
--
PROCEDURE insert_emp(p_col_typ IN EmpTabTyp)
IS
BEGIN
SELECT * BULK COLLECT INTO emp_tab FROM scott.emp;
--
FOR i IN p_col_typ.first .. p_col_typ.last
LOOP
-- Insert your values into your table --
INSERT Into emp_test (empno, ename) VALUES (p_col_typ(i).empno, p_col_typ(i).ename);
-- Optionally display values inserted - do not do this if table has many rows --
--DBMS_OUTPUT.put_line('INSERTED :'|| p_col_typ(i).empno||chr(9)||p_col_typ(i).ename);
END LOOP;
DBMS_OUTPUT.put_line('Total rows inserted :'||p_col_typ.Count);
END insert_emp;
BEGIN
insert_emp(emp_tab);
END;
/
In addition to my comment above:
DECLARE
TYPE EmpTabTyp IS TABLE OF scott.emp%ROWTYPE;
emp_tab EmpTabTyp:= EmpTabTyp();
PROCEDURE display_emp (p_col_typ IN EmpTabTyp)
IS
BEGIN
SELECT * BULK COLLECT INTO emp_tab FROM scott.emp;
--
FOR i IN p_col_typ.first .. p_col_typ.last LOOP
DBMS_OUTPUT.put_line(p_col_typ(i).empno||chr(9)||p_col_typ(i).ename);
END LOOP;
END display_emp;
BEGIN
display_emp(emp_tab);
END;
/
DECLARE
l_names DBMS_UTILITY.maxname_array; -- or DBMS_UTILITY.name_array
PROCEDURE show_contents (names_in IN DBMS_UTILITY.maxname_array)
IS
BEGIN
FOR indx IN names_in.FIRST .. names_in.LAST
LOOP
DBMS_OUTPUT.put_line (names_in (indx));
END LOOP;
END;
BEGIN
l_names (1) := 'Picasso';
l_names (2) := 'Keefe';
l_names (3) := 'Dali';
show_contents (l_names);
END;
/
-- returning collection type example:
DECLARE
TYPE t_emptbl IS TABLE OF scott.emp%rowtype;
v_emptbl t_emptbl;
ret_val t_emptbl;
Function getEmployeeList Return t_emptbl
IS
BEGIN
SELECT * bulk collect INTO v_emptbl FROM scott.emp;
-- Print nested table of records:
FOR i IN 1 .. v_emptbl.COUNT LOOP
DBMS_OUTPUT.PUT_LINE (v_emptbl(i).empno);
END LOOP;
RETURN v_emptbl;
END;
BEGIN
ret_val:= getEmployeeList;
END;
/