How to execute a stored procedure with parameters from a variable? - oracle

I have a variable which contains a name of a stored procedure. I want to execute this procedure with dynamic sql, so I did this :
var_procedure is the variable which contain the name of my stored procedure. firstparameter and secondone are my varchar parameters for the stored procedure.
execute immediate var_procedure||'('''||firstparameter||''','''||secondone||''')';
It's not working and I have this basic error printed :
ORA-00900: invalid SQL statement
Do you have a solution ?

Let try it step by step.
This is your procedure.
create or replace procedure my_proc (a varchar2, b varchar2)
as
begin
dbms_output.put_line ('a= '||a||' b = '||b);
end;
/
This is the way how to call it directly in PL/SQL
begin
my_proc('x','y');
end;
/
This is the Wrong Way how to execute is dynamically.
Why? Concatenating parameters in the statement is a bad practice enabling SQL injection.
declare
a varchar2(5) := 'a';
b varchar2(5) := 'b';
begin
execute immediate 'begin
my_proc('''||a||''','''||b||''');
end;';
end;
/
This is the right way using bind variables:
declare
a varchar2(5) := 'a';
b varchar2(5) := 'b';
begin
execute immediate 'begin
my_proc(:a,:b);
end;' USING a,b;
end;
/
To pass the name of the procedure dynamically you have to concatenate the statement, so take some care to avoid SQL Injection if the parameter is not under your control (minimum is to limit the length to 30 characters).
declare
proc_name varchar2(30) := 'my_proc';
a varchar2(5) := 'a';
b varchar2(5) := 'b';
begin
execute immediate 'begin '||
proc_name||'(:a,:b);
end;' USING a,b;
end;
/

Related

Using collection inside dynamic sql

I am trying to pass dbms_sql.number_table from one procedure to another and then using it inside a dynamic plsql block. But the below code throws error as:
Error(6,17): PLS-00306: wrong number or types of arguments in call to '||'
create or replace
procedure proc1( v_in_table_name varchar2,
v_in_column_name varchar2,
v_in dbms_sql.number_table)
as
plsql_block varchar2(4000);
begin
plsql_block:='declare
begin
FORALL INDX IN 1 ..'||v_in.count||' SAVE EXCEPTIONS
UPDATE '||v_in_table_name||'
Set '||v_in_column_name||'=123 WHERE col2='||v_in||'(INDX)||;
end';
execute immediate plsql_block;
end proc1;
You should make two changes:
First(and its case of error) its when you concatinate string with collection.
If you want to send collection into pl/sql block you shoud use the param.
And second you should add ; in the end of dynamic pl\sql:
create or replace
procedure proc1(v_in dbms_sql.number_table)
as
plsql_block varchar2(4000);
begin
plsql_block:='declare
l_collect dbms_sql.number_table := :number_table ;
begin
FORALL INDX IN 1 ..'||v_in.count||' SAVE EXCEPTIONS
UPDATE table_name
Set col1=123 WHERE col2=l_collect(INDX);
end;';
execute immediate plsql_block using v_in;
end proc1;
But after all changes I would like to ask: Are you realy need to use dynamic pl\sql?
There is no need for using a dynamic block. Also i dont think it can work as well. You can use this way;
create or replace
procedure proc1(v_in dbms_sql.number_table)
as
plsql_block varchar2(4000);
l_collect dbms_sql.number_table;
begin
l_collect := v_in;
FORALL INDX IN 1 ..l_collect.count SAVE EXCEPTIONS
UPDATE table_name
Set col1=123
WHERE col2=l_collect(INDX);
commit;
end proc1;
Execution:
SQL> DECLARE
var DBMS_SQL.number_table;
BEGIN
var (1) := 1;
var (2) := 2;
var (3) := 3;
proc1 (var);
END;
/
PL/SQL procedure successfully completed.
EDIT: As per your edit the code becomes like:
create or replace procedure proc1 (v_in_table_name varchar2,
v_in_column_name varchar2,
v_in dbms_sql.number_table)
as
plsql_block varchar2 (4000);
begin
plsql_block := q'[ FORALL INDX IN 1 ..v_in.count SAVE EXCEPTIONS
UPDATE :table_name
Set :col1=123
WHERE col2=v_in(INDX)]';
execute immediate plsql_block using v_in_table_name, v_in_column_name;
commit;
end proc1;

PLSQL Dynamic SQL Bind Variable by Name

I want to execute an sql statement dynamically in my procedure. As far as I could see, the binding is done according to the order of the usage.
Is there a way in which I can say something like
:a --> par_a_
There is a differnce between execute immediate 'plsql code' and execute immediate 'sql', In dynamic pl/sql oracle will use real bindes and you may specify it once in correct order and it will replace all binds if there are repeted. with sql you should spesify all binds instead of repeting it.
declare
l_sql varchar2(4000) := 'select :a from dual union all select :a from dual';
l_pl_sql varchar2(4000) := 'begin dbms_output.put_line(:a); dbms_output.put_line(:a); end;';
type t_tab_str is table of varchar2(4000);
l_res t_tab_str ;
begin
execute immediate l_sql bulk collect into l_res using '1','2';
for i in 1.. l_res.last loop
dbms_output.put_line(l_res(i));
end loop;
execute immediate l_pl_sql using '1';
end;
you may use dbms_sql and it function bind
declare
l_sql varchar2(4000) := 'select :a from dual union all select :a from dual';
type t_tab_str is table of varchar2(4000);
l_res t_tab_str ;
l_sql_id number;
l_ret number;
type curtype is ref cursor;
l_cursor curtype ;
begin
dbms_sql.parse(l_sql_id ,l_sql,dbms_sql.native);
dbms_sql.bind_variable(l_sql_id,'a','1');
l_ret := dbms_sql.execute(l_sql_id);
l_cursor := dbms_sql.to_refcursor(l_sql_id);
fetch l_cursor bulk collect into l_res;
for i in 1.. l_res.last loop
dbms_output.put_line(l_res(i));
end loop;
end;
Seems to me what you are after is USING keyword.
Below is example from oracle documentation.
DECLARE
plsql_block VARCHAR2(500);
new_deptid NUMBER(4);
new_dname VARCHAR2(30) := 'Advertising';
new_mgrid NUMBER(6) := 200;
new_locid NUMBER(4) := 1700;
BEGIN
-- Dynamic PL/SQL block invokes subprogram:
plsql_block := 'BEGIN create_dept(:a, :b, :c, :d); END;';
/* Specify bind arguments in USING clause.
Specify mode for first parameter.
Modes of other parameters are correct by default. */
EXECUTE IMMEDIATE plsql_block
USING IN OUT new_deptid, new_dname, new_mgrid, new_locid;
END;
/
https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/dynamic.htm

Update statement error in SQL procedure

I am writing a stored procedure to update data in a table where I will pass a string with the new data (col1='new values', col2='new 2 values'). But when I am compiling my stored procedure , i am getting an error :- "missing equal sign".
Even i tried doing it in a different way (commented code in proc) but that is also giving an error.
CREATE OR REPLACE PROCEDURE "MY_UPDATE_PROC"(update_values IN VCHAR2,myid IN INT)
sqlStmt VARCHAR2(1024);
BEGIN
UPDATE MY_TEST_TABLE SET update_values WHERE (TEST_Id = myid);
--sqlStmt := 'UPDATE MY_TEST_TABLE SET ' || update_values || ' WHERE TEST_Id = ' ||myid ;
-- EXECUTE sqlStmt;
END;
Try this (untested):
CREATE OR REPLACE PROCEDURE "MY_UPDATE_PROC"(update_values IN VARCHAR2, myid IN NUMBER) AS
sqlStmt VARCHAR2(1024);
BEGIN
sqlStmt := 'UPDATE MY_TEST_TABLE SET ' || update_values || ' WHERE TEST_Id = ' || myid;
EXECUTE IMMEDIATE sqlStmt;
END;
/
The datatype of your first parameter should be VARCHAR2 (maybe just a typo in your post)
Syntax of simple update statement in Oracle is:
Update <table_name>
set <column_name> = some_value
where <conditions..>
You update statement is missing = some_value part that you need to provide.
CREATE OR REPLACE PROCEDURE "MY_UPDATE_PROC"(P_update_values IN CHARVAR2, p_myid IN INT)
BEGIN
UPDATE MY_TEST_TABLE
SET col1 = p_update_values
WHERE TEST_Id = p_myid;
END;
/
Using Dynamic SQL, although not required in this case:
CREATE OR REPLACE PROCEDURE "MY_UPDATE_PROC"(p_update_values IN VARCHAR2, p_myid IN NUMBER) AS
sqlStmt VARCHAR2(1024);
BEGIN
sqlStmt := 'UPDATE MY_TEST_TABLE SET col1 = :a WHERE TEST_Id = :b';
EXECUTE IMMEDIATE sqlStmt USING p_update_values, p_myid;
END;
/
Things to be noted:
1) Always use meaningful and different names that are other than column names for the parameters.
2) Always use bind variables, :a and :b in above examples, to avoid SQL Injections and improve overall performance if you are going to call this procedure multiple times.

How to assign a table name to a variable and using the same inside immediate execute statement

I am trying to assign table name to a variable and to use the same inside execute immediate statement , please help me with the syntax inside execute statement
scenario is
I am trying to pass a table name as parameter in the procedure and am using them inside execute immediate to write into some other table .
please suggest me the syntax for in parameters and for the execute immediate statement.
My Procedure:
CREATE OR REPLACE PROCEDURE tep
AS
v_sql VARCHAR2(1024) ;
a NUMBER;
b VARCHAR2(30) :='t_stg1_non_silicon_purchace_re';
BEGIN
v_sql := q'[SELECT COUNT(*) FROM b ]';
EXECUTE immediate v_sql INTO a;
DBMS_OUTPUT.PUT_LINE( TO_CHAR(v_sql));
DBMS_OUTPUT.PUT_LINE( TO_CHAR(a));
END;
/
You haven't specified what exactly you want to do with the table name in the execute immediate so see example below of how to "CREATE" a table.
DECLARE
vTableName VARCHAR2(40 CHAR);
vSQL VARCHAR2(1000 CHAR);
BEGIN
vTableName:='MY_TABLE_NAME';
vSQL:='CREATE TABLE ' || vTableName || ' (COL1 NUMBER, COL2 NUMBER)';
EXECUTE IMMEDIATE vSQL;
END;
/
This query may satisfy your requirement. Thanks
CREATE OR REPLACE PROCEDURE tep
AS
v_sql VARCHAR2(1024) ;
a NUMBER;
b VARCHAR2(30) :='t_stg1_non_silicon_purchace_re';
BEGIN
v_sql := 'SELECT COUNT(*) FROM'||' '||b;
EXECUTE immediate v_sql INTO a;
DBMS_OUTPUT.PUT_LINE( TO_CHAR(v_sql));
DBMS_OUTPUT.PUT_LINE( TO_CHAR(a));
END;

PL/SQL Before-After-Pattern

I would like to use a before-after-pattern approach with PL/SQL (PSEUDO CODE):
Pattern method:
procedure doIt(DO_SOMETHING)
is
l_cnt pls_integer := 1;
begin
loop
begin
DO_SOMETHING;
exit;
exception
when exception changed then
if l_cnt = 2 then
--raise exception...
else
l_cnt := l_cnt + 1;
end if;
end;
end loop;
end;
And execute it like this:
begin
doIt(execute immediate sql_statement using in or out);
end;
As you can see, I would like to use different dynamic sql statements (execute immediate with one or more in and out variables) but always the same before-after-pattern approach.
Have someone an idea how I can solve this problem?
This solution is not so nice, but it works. Perhaps you can start from it and make something better:
I used 2 parameters in the doIt function-
1) the command to execute immediate
2) the arguments as an anydata type
In the execute immediate command I've put all the logic from translating the anydata to some type which I created to wrap the IN OUT parameters.
here is the code:
a type like this should be created for every different command:
create or replace type some_type as object(a number, b number);
/
this is the procedure :
create or replace procedure doIt(aa in varchar2, param IN OUT anydata) is
begin
execute immediate aa using in out param;
end doIt;
/
and this is how I call it (in this example I just selected count(*) from dual into some OUT param):
declare
i number;
prm some_type;
ad anydata;
a number;
b number;
begin
prm := new some_type(a,b);
ad := anydata.convertobject(prm);
doIt('declare prmAd anydata := :0; prm1 some_type; x number; begin x := prmAd.getobject(prm1); select count(*) into prm1.a from dual; :0 := anydata.convertobject(prm1); end;', ad);
i := ad.GetObject(prm);
dbms_output.put_line(prm.a);
end;
basically you can add to the doIt procedure what ever you want, and run with it any command.
I guess you can make things nices- move some of the execute immediate string to the doIt procedure, maybe declare the type better and so on.
I can't find much information on Before/After pattern but looking at what your trying to achieve it could look something like this:
create or replace
procedure doIt(stmt in varchar2, param in varchar2)
is
l_cnt pls_integer := 1;
begin
loop
begin
execute immediate stmt using param;
exit;
exception
when others then
if l_cnt = 2 then
raise;
else
l_cnt := l_cnt + 1;
end if;
end;
end loop;
end;
/
-- run it
exec doIt('insert into my_table (col1) values ( :val1 )', 'richard' );
The problem here is that if you want to pass two parameters then you will have to override 'doIt' eg:
procedure doIt(stmt in varchar2, param1 in varchar2, param2 in varchar2)
...
using param1, param2
Also the other caveat is passing different data types into param. Again you could override the procedure with the correct data type - be warned this could get messy with multiple data types or number of arguments.

Resources