Oracle dynamic parameters - oracle

I'm struggling to create a dynamic sql parametrized query. It involves using 'IS NULL' or 'IS NOT NULL'
Here's a simple pl/sql query:
CREATE OR REPLACE PROCEDURE GET_ALL_INFORMATION
(
"PARAM_START_DATE" IN DATE,
"PARAM_END_DATE" IN DATE,
"PARAM_IS_SUBMITTED" IN NUMBER,
"EXTRACT_SUBMITTED_CONTACTS" OUT sys_refcursor
) IS
sql_stmt VARCHAR2(3000);
PARAM_CONDITION VARCHAR2(20);
BEGIN
IF PARAM_IS_SUBMITTED = 1 THEN
PARAM_CONDITION := 'NOT NULL';
ELSE
PARAM_CONDITION := 'NULL';
END IF;
sql_stmt := ' SELECT
REGISTRATION_NUMBER,
NAME PROVIDER_TYPE,
ORGANIZATION
FROM TABLE_A
WHERE
P.DATE_FINALIZED IS :A;
OPEN EXTRACT_SUBMITTED_CONTACTS FOR sql_stmt USING PARAM_CONDITION;
Whereas the parameter (:A) in (USING PARAM_CONDITION) should have 'NULL' or 'NOT NULL'. It does not seem to work the way I envisioned.
Am I missing something?

As explained by GriffeyDog in a comment above, bind parameters could only be used as place holder for values. Not to replace keywords or identifiers.
However, this is not really an issue here, as you are using dynamic SQL. The key idea ifs that you build your query as a string -- and it will be parsed at run-time by the PL/SQL engine when you invoke EXECUTE or OPEN .. FOR.
Simply said, you need a concatenation -- not a bound parameter:
...
sql_stmt := ' SELECT
REGISTRATION_NUMBER,
NAME PROVIDER_TYPE,
ORGANIZATION
FROM TABLE_A
WHERE
P.DATE_FINALIZED IS ' || PARAM_CONDITION;
-- ^^
OPEN EXTRACT_SUBMITTED_CONTACTS FOR sql_stmt;

Related

Oracle pl sql dynamic using clause

I have a question about "dynamic using clause" in execute immediate statement. I need to set dynamically the "execute immediate statement" and the using clause as well. I don't know the table structure, but I know only the name of the table, and I need to do an operation update on it.
So I wrote a function (through user_tab_columns and user user_constraints tables) to set a variable with the update statement and the bind_variable but now I need to set the using clause with the list of variable.
Example:
CREATE TABLE table1
(
rec1 VARCHAR2(10 BYTE) NULL,
rec2 DATE NULL,
rec3 number(9) not null
);
declare
TYPE cur_type IS REF CURSOR;
cur cur_type;
table_list table1%ROWTYPE;
sqlstring varchar2(400);
begin
OPEN cur FOR sqlstring;
LOOP
FETCH cur INTO table_list;
EXIT WHEN cur%NOTFOUND;
sqlstring:=function1('table1');
-- that returns sqlstring:='update table1 set rec1=:1 , rec2=:2 , rec3=:3 where rec_id=:c4';
execute immediate sqlstring using table_list.rec1, table_list.rec2, table_list.rec3, table_list.rec_id;
END LOOP;
close cur;
end;
I need to implement dynamically the list of variables of the cursor table_list.
"execute immediate sqlstring using table_list.rec1, table_list.rec2, table_list.rec3, table_list.rec_id"
Does anybody know how to solve this problem?
Thanks a lot for your replies.
The problem is that I'm assuming I don't know the table's structure and so the list of variables of the cursor table_list table1%ROWTYPE.
So I can't explicit table_list.rec1, table_list.rec2 ... in the using clause.
If I use only table_list as variable
begin
OPEN cur FOR sqlstring;
LOOP
FETCH cur INTO table_list;
EXIT WHEN cur%NOTFOUND;
sqlstring:=function1('table1');
execute immediate sqlstring using table_list;
END LOOP;
close cur;
I got the error:" 00457 Expressions have to be of SQL types"
http://psoug.org/oraerror/PLS-00457.htm
Error Cause:
An expression of wrong type is in USING or dynamic RETURNING clause. In USING or dynamic RETURNING clause, an expression cannot be of non-SQL types such as BOOLEAN, INDEX TABLE, and record.
I need a way to retrive not only the values but also the list of variables of the cursor table_list first.
But maybe it's impossible and I have to find a work around.
If I will find something interesting I will post.
Thankyou.
Try to replace your execute immediate to full use of dbms_sql.
http://docs.oracle.com/cd/B28359_01/appdev.111/b28419/d_sql.htm#i996891
And usefull for you will be bind_array function from this package.
Use dynamic PL/SQL, unless you can re-factor the original statement and just plug the values into it.
declare
v_string constant varchar2(32767) := 'update test1 set a = :1, b = :2';
v_using_string varchar2(32767);
begin
--Create dynamic using string.
--For example, let's say you want to pass in the values "1" for each NUMBER column.
select listagg(1, ',') within group (order by null)
into v_using_string
from user_tab_columns
where table_name = 'TEST1'
and data_type = 'NUMBER';
--Execute the original dynamic SQL, adding the USING string.
execute immediate '
begin
execute immediate '''||v_string||''' using '||v_using_string||';
end;
';
end;
/
You can either use DBMS_SQL package:
open a cursor using dbms_sql.open_cursor
parse the statement using dbms_sql.parse
bind variables in a loop using dbms_sql.bind_variable
execute the statement using dbms_sql.execute
and finally close the cursor using dbms_sql.close_cursor
Or EXECUTE IMMEDIATE of anonymous PL/SQL block, which performs a dynamically created EXECUTE IMMEDIATE (this approach is not suitable for returning data). See Answer of #JonHeller.

Best practice for formatting/building queries to execute in a pl/sql stored procedure

Which method is preferred/faster/best practice? Does the second example execute slower? I assume it may, because the query has not been "compiled" yet since it is passed as a concatenated string before it is executed.
CREATE OR REPLACE PROCEDURE SP_FAST(
VI_OBID IN NUMBER,
VO_NAME OUT VARCHAR)
AS
BEGIN
SELECT NAME
INTO VO_NAME
FROM BILLING.CUSTOMER
WHERE OBJECTID = VI_OBID;
-- dbms_output.put_line('VI_OBID: '||VI_OBID||']');
-- dbms_output.put_line('VO_NAME: ['||VO_NAME||']');
END SP_FAST;
Or this:
CREATE OR REPLACE PROCEDURE SP_NOTASFAST(
VI_OBID IN NUMBER,
VO_NAME OUT VARCHAR)
AS
qstring VARCHAR2(500);
BEGIN
qstring:= 'SELECT NAME ' ||
'FROM BILLING.CUSTOMER ' ||
'WHERE OBJECTID = :1';
execute immediate qstring into VO_NAME using VI_OBID;
-- dbms_output.put_line('VI_OBID: '||VI_OBID||']');
-- dbms_output.put_line('VO_NAME: ['||VO_NAME||']');
END SP_NOTASFAST;
Each method has advantages and disadvantages.
The following link will be very useful for you. This chapter address how coding dynamic SQL in Oracle database.
http://docs.oracle.com/cd/B19306_01/appdev.102/b14251/adfns_dynamic_sql.htm

Use execute immediate in procedure for DML and passing character value in parameter

I have a delete procedure which is taking table name and some values to delete record from that table, hence I have created a procedure with execute immediate which is forming the delete query by taking the parameter and delete.
But when ever I am passing the char value in the parameter it is getting error :
invalid identifier
as query formed with out single quote for the character value. Please let me know how can I pass char value in the procedure to form a string correctly.
Below is the procedure:
CREATE OR replace PROCEDURE Prd_delete(p_tbl_name IN VARCHAR2,
p_sys VARCHAR2,
p_b_id VARCHAR2,
p_c_date NUMBER)
IS
dlt_query VARCHAR2(200);
BEGIN
dlt_query := 'delete from '
||p_tbl_name
||' where system='
||p_sys
|| ' And batch_id='
||p_b_id
|| ' And cobdate='
||p_c_date;
--dbms_output.put_line(dlt_query);
EXECUTE IMMEDIATE dlt_query;
END;
/
Below is the running command :
exec prd_delete ('TBL_HIST_DATA','M','N1',20141205);
Below is the error :
ORA-00904:"N1" invalid identifier.
How to pass this value correctly ? please suggest.
At first place, why do you need PL/SQL for the DELETE. You could do it in plain SQL.
Why is P_C_DATE a NUMBER, What data type is cobdate COLUMN. A date should always be a DATE. If the column data type is DATE, then you will run into more errors. Always pay attention to declaring correct data types.
With dynamic SQL, before directly executing, it is always a good practice to see whether the query is formed correctly using DBMS_OUTPUT. I would also suggest to use quoting string literal technique to make it even easier.
DBMS_OUTPUT.PUT_LINE(dlt_query);
The issue with the query is that you are missing the single-quotation marks around the VARCHAR2 type.
Modify the query to -
dlt_query := 'delete from '||P_TBL_NAME||' where system='||P_SYS||
' And batch_id='||''''||P_B_ID|| '''' ||
' And cobdate='||P_C_DATE;
you are losing the quotes around N1 during concatination
you can fix by adding quotes before and after , eg.
dlt_query := 'delete from '||P_TBL_NAME||' where system='||P_SYS||
' And batch_id='||''''||P_B_ID|| '''' ||
' And cobdate='||P_C_DATE;
If you have to use the EXECUTE IMMEDIATE statement, you should use bind variables:
CREATE OR REPLACE PROCEDURE prd_delete (P_TBL_NAME IN VARCHAR2,
P_SYS VARCHAR2,
P_B_ID VARCHAR2,
P_C_DATE NUMBER) IS
dlt_query VARCHAR2 (200);
BEGIN
dlt_query := 'delete from ' || P_TBL_NAME || ' where system=:1 and batch_id=:2 and cobdate=:3';
BEGIN
EXECUTE IMMEDIATE dlt_query USING P_SYS, P_B_ID, P_C_DATE;
EXCEPTION
WHEN OTHERS THEN
-- catch exception !!
END;
END;
/

using EXECUTE IMMEDIATE with multiple same bind arguments

When I create the following procedure
create or replace procedure check_exec_imm(
tab IN VARCHAR2,
col IN VARCHAR2,
col_name IN VARCHAR2
)
IS
cv SYS_REFCURSOR;
col_value VARCHAR2(32767);
lv_query VARCHAR2(32767);
BEGIN
lv_query := 'SELECT ' ||col||
' FROM ' ||tab||
' WHERE (:1 = ''EUR'' OR :1 = ''USD'') and rownum <=1';
EXECUTE IMMEDIATE lv_query INTO col_value USING col_name ;
DBMS_OUTPUT.PUT_LINE('COLUMN VALUE : ' || col_value);
END;
When the procedure is executed, I'm getting the following error
ORA-01008: not all variables bound
ORA-06512: at "GRM_IV.CHECK_EXEC_IMM", line 18
ORA-06512: at line 2
When I give the bind argument col_name again as below, the procedure is running fine.
EXECUTE IMMEDIATE lv_query INTO col_value USING col_name, col_name ;
Why oracle is behaving differently in this procedure. Since, it is the same bind variable, one bind argument should be sufficient right..!!? Please explain where I'm getting my logic wrong.
There is "special" behaviour in Oracle: Repeated Placeholder Names in Dynamic SQL Statements
In an Anonymous Block or CALL Statement it is not required to repeat the bind values if the names are equal.
For example this Anonymous Block is working:
DECLARE
a NUMBER := 4;
b NUMBER := 7;
plsql_block VARCHAR2(100);
BEGIN
plsql_block := 'BEGIN calc_stats(:x, :x, :y, :x); END;';
EXECUTE IMMEDIATE plsql_block USING a, b; -- calc_stats(a, a, b, a)
END;
/
But this EXECUTE IMMEDIATE plsql_block USING a, b; does not work inside a Procedure.
The way you have referenced the column name through bind variable is not a preferred method as Nichoas pointed out. What you tried is called as native dynamic SQL using 'cleverer' bind variables.
In this method, you need to bind every parameter X times since you use it X times because they are all treated as separate variables.
Read more on binding to dynamic SQL.
#ethan and #ManiSankar I too had a same problem in my scenario as well. I solved this using some kind of brute force techinque. What i have done is
Before this
EXECUTE IMMEDIATE lv_query INTO col_value USING col_name ;
I have added replace condition in my code by replacing parameter with the required value then called "Execute Immediate" without "using" clause
lv_query := replace(lv_query, ':1',col_name);
EXECUTE IMMEDIATE lv_query INTO col_value;
I don't know this is optimal one but served purpose for what i am expecting..
Please advice if this one recommended or not...

Searching data from table by passing table name as a parameter in PL/SQL

Is this stored procedure in oracle is correct for searching data from table by passing table name as a parameter
CREATE OR REPLACE PROCEDURE bank_search_sp
(
p_tablename IN VARCHAR2,
p_searchname IN VARCHAR2,
p_bankcode OUT VARCHAR2,
p_bankname OUT VARCHAR2,
p_dist_code OUT NUMBER
)
AS
v_tem VARCHAR2(5000);
BEGIN
v_tem := 'SELECT bankcode,bankname,dist_code FROM ' || UPPER (p_tablename) || '
WHERE bankname LIKE '''|| p_searchname||'''';
EXECUTE IMMEDIATE v_tem
INTO p_bankcode,p_bankname,p_dist_code
USING p_searchname ;
END bank_search_sp;
If you need this procedure, then I guess that you have several tables with the columns bankcode, bankname and dist_code. If this is true, then try to normalize your model if possible.
The USING term is the correct approach, but you have to use the parameter in your query.
To avoid SQL injection, you could use dbms_assert.sql_object_name.
This should work for you:
v_tem := 'SELECT bankcode, bankname, dist_code FROM '
|| dbms_assert.sql_object_name(p_tablename)
|| ' WHERE bankname LIKE :1';
Your EXECUTE IMMEDIATE will throw an exception when finding no row or more than one row, so using LIKE might not be a good idea.
Questions that you should ask yourself:
Is the model properly normalized?
Do you really need to use LIKE, or is = what you want?
If you want to use LIKE, how should the program deal with NO_DATA_FOUND / TOO_MANY_ROWS exceptions?

Resources