Skip to Main Content
  • Questions
  • PL/SQL procedure issue (bulk collect and insert on a dynamic table)

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, oracle_knw.

Asked: February 02, 2018 - 4:54 am UTC

Last updated: February 08, 2018 - 3:47 pm UTC

Version: Java(TM) Platform 1.7.0_40 Oracle IDE 4.0.0.12.84

Viewed 1000+ times

You Asked

Hi All,

Requirement : We need to pass table name as a parameter into the Procedure and also further I need to concatenate that value with a string to make a correct table name which will be available in database.

For ex :As per my code , if am giving the argument as

Execute PROCEDURE (10,'DE','CON','INC');

So by this I am trying to make a table in the Procedure as given below :

CON_SAP_TEMP_DE_CON_INC (Select table in Cursor)
CON_SAP_UNLOAD_DE_CON_IN (Insert Table during Insertion)

Please see my code Now :

create or replace PROCEDURE UNLOAD_CON_SAP_RESP_LOG 
(PROC_ARRAY_SIZE IN PLS_INTEGER DEFAULT 10000,OPCO_UPCASE IN VARCHAR2,V_SEGMENT IN VARCHAR2,V_EVENT_NAME IN VARCHAR2) 
AS 
V_TABLE_NAME varchar2(100) := OPCO_UPCASE||'_'||V_SEGMENT||'_'||V_EVENT_NAME; 
V_TABLE_NAME_TEST varchar2(100) := 'CON_SAP_UNLOAD'||'_'||V_TABLE_NAME; 
V_TABLE_NAME_TEST_TEMP varchar2(100) := 'CON_SAP_TEMP'||'_'||V_TABLE_NAME; 
sql_stmt_t_select VARCHAR2(32767); 
sql_stmt_t_insert VARCHAR2(32767); 

type rc is ref cursor; 
CON_SAP_CUR rc; 

TYPE CON_SAP_RESP IS TABLE OF CONTRACT_SAP_RESPONSE_LOG%ROWTYPE; 
V_CON_SAP_RESP CON_SAP_RESP; 

BEGIN 

sql_stmt_t_select := 'SELECT * FROM CON_SAP_TEMP WHERE CONTRACT_ID IN 
(SELECT CONTRACT_ID FROM '||V_TABLE_NAME_TEST_TEMP||')'; 

sql_stmt_t_insert := 'INSERT INTO '||V_TABLE_NAME_TEST||' VALUES V_CON_SAP_RESP(I)'; 

DBMS_OUTPUT.PUT_LINE(sql_stmt_t_select); 
DBMS_OUTPUT.PUT_LINE(sql_stmt_t_insert); 

DBMS_OUTPUT.PUT_LINE('PROCEDURE STARTS' || ' ' || TO_CHAR(SYSDATE,'DD-MON-YY HH24:MI:SS')); 

OPEN CON_SAP_CUR for sql_stmt_t_select; 
LOOP 
FETCH CON_SAP_CUR BULK COLLECT INTO V_CON_SAP_RESP LIMIT PROC_ARRAY_SIZE; 

FORALL I IN 1..V_CON_SAP_RESP.COUNT 
sql_stmt_t_insert; 
--INSERT INTO con_sap_unload_de_con_inc VALUES V_CON_SAP_RESP(I); 
EXIT WHEN CON_SAP_CUR%NOTFOUND; 
END LOOP; 
CLOSE CON_SAP_CUR; 

DBMS_OUTPUT.PUT_LINE('PROCEDURE ENDS' || ' ' || TO_CHAR(SYSDATE,'DD-MON-YY HH24:MI:SS')); 

EXCEPTION 
WHEN NO_DATA_FOUND THEN 
NULL; 
WHEN OTHERS THEN 
--Consider logging the error and then re-raise 
RAISE; 
END UNLOAD_CON_SAP_RESP_LOG; 
------------------------------------------ 


If you focus on insert , I have commented out one line "--INSERT INTO con_sap_unload_de_con_inc VALUES V_CON_SAP_RESP(I);"
, if I am passing this line , procedure is getting compiled and am not facing any issue because we are passing a hard coded table in this statement, but my requirement is to have table which I am resolving from V_TABLE in beginning.

Could you please help me in the Insert statement ?
What will be the correct way to write that Insert.

and Chris said...

You're not specifying what the value you're inserting is! You need to bind to I.

But what you should do instead is ditch the loop. Make this a straight insert from select:

  sql_stmt_t_select := 'INSERT INTO '||V_TABLE_NAME_TEST||' (cols)
    SELECT cols FROM CON_SAP_TEMP WHERE CONTRACT_ID IN 
      (SELECT CONTRACT_ID FROM '||V_TABLE_NAME_TEST_TEMP||')'; 


Also: do you really need dynamic SQL? Static SQL is far safer.

If you really must use dynamic SQL, then ensure you sanitize user input with dbms_assert:

https://oracle-base.com/articles/10g/dbms_assert_10gR2

Otherwise you leave yourself exposed to SQL injection attacks!

Rating

  (2 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Cursor Use

Vipul, February 02, 2018 - 11:55 am UTC

Hi Chris,

Thanks for you response.

In our case we will have millions of data and we want to insert with Cursor ,BULK COLLECT in smaller Chunks, that is why we are using Cursor.

Isn't it a risk to insert millions of data in one go directly without any loop and limit.

Also , tell us if how can we write Static SQL but Table need to be dynamic in our case.
Chris Saxon
February 05, 2018 - 10:57 am UTC

Isn't it a risk to insert millions of data in one go directly without any loop and limit.

No more of a risk than doing bulk collect. You're still processing the same amount of data. And insert ... select ... is easier to code. Reducing the chance of mistakes.

And insert ... select ... will almost certainly be quicker.

Also , tell us if how can we write Static SQL but Table need to be dynamic in our case.

If you can avoid dynamic SQL you should. If the number of possible input table is "small", you can test the parameter and run the appropriate SQL. For example:

if V_TABLE_NAME_TEST = 'T1' then
  insert into t1 select ...
elsif V_TABLE_NAME_TEST = 'T2' then
  insert into t2 select ...
elsif V_TABLE_NAME_TEST = 'T3' then
  insert into t3 select ...
elsif ...

end if;


Only use dynamic SQL if you really must use it. Particularly when accepting table names as parameters. This vastly increases your SQL injection risks.

Query Running for Long Time

Vipul, February 08, 2018 - 2:15 pm UTC

Hi Chris,

We have used the direct insert method that you suggested previously and somehow it is taking huge time to insert the data on the dynamic table(also subquery table is also dynamic here).
We have tested for the below Count of Records and see the analysis below :

V_TABLE_TEMP : Count is : 5 million
(CON_SAP_TEMP_2_101)

CON_SAP_TEMP = COUNT is : 200 million

SELECT RETRIEVAL COUNT is : 30 million (this will gonna insert in table 'V_TABLE_UNLOAD=CON_SAP_UNLOAD_2_101')

Time Taken : More than 5 hours and later on I killed the SQL session as this is of no use if it will take this much time

EXECUTE UNLOAD_CON_SAP_RESP_LOG('2','101')
/

CREATE OR REPLACE PROCEDURE UNLOAD_CON_SAP_RESP_LOG
(V_OPCO IN VARCHAR2,V_EVENT_TYPE_ID IN VARCHAR2)
AS
V_TABLE varchar2(100) := V_OPCO||'_'||V_EVENT_TYPE_ID;
V_TABLE_TEMP varchar2(100) := 'CON_SAP_TEMP'||'_'||V_TABLE;
V_TABLE_UNLOAD varchar2(100) := 'CON_SAP_UNLOAD'||'_'||V_TABLE;
SQL_INSERT VARCHAR2(32767);

BEGIN
SQL_INSERT := 'INSERT INTO '||V_TABLE_UNLOAD||'
(SELECT * FROM CON_SAP_TEMP WHERE CONTRACT_ID IN
(SELECT CONTRACT_ID FROM '||V_TABLE_TEMP||'))';

EXECUTE IMMEDIATE SQL_INSERT;
COMMIT;
DBMS_OUTPUT.PUT_LINE('DATA INSERTED');
END UNLOAD_CON_SAP_RESP_LOG;
/

Could you please suggest us any other way to insert the data in less time , assuming table to be dynamic and which will take less time to insert ?
Also have noticed when I use Bulk Collect in Cursor (insert table was static in this case),
it was taking less time as compared to direct insert as I am doing right now (with dynamic table for insert).

Chris Saxon
February 08, 2018 - 3:47 pm UTC

To suggest improvements we need to know what the database is doing. To find this out, trace your code execution:

https://blogs.oracle.com/sql/how-to-create-an-execution-plan#tkprof

Or profile it:

https://oracle-base.com/articles/11g/plsql-hierarchical-profiler-11gr1

Once you've got this information post your findings and we'll see how we can help.

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library