oracle dynamic sql evaluate expression in where clause - oracle

I am trying to pass an expression into the WHERE clause of my query using dynamic SQL. The expression can contain multiple filters/columns.
Similar to other posts on SO, the following (example 1) works:
DECLARE
where_expression VARCHAR2(40) := q'[filter_column = 'some_value')]';
plsql_block VARCHAR2(500);
BEGIN
plsql_block := 'SELECT column FROM mytable';
EXECUTE IMMEDIATE plsql_block || ' WHERE ' || where_expression;
END;
/
And this approach (example 2) using placeholders does not work:
DECLARE
where_expression VARCHAR2(40) := q'[filter_column = 'some_value')]';
plsql_block VARCHAR2(500);
BEGIN
plsql_block := 'SELECT column FROM mytable WHERE :a';
EXECUTE IMMEDIATE plsql_block USING where_expression;
END;
/
Oracle returns an error: ORA-00920: invalid relational operator at line 8 (EXEC statement).
What am I doing wrong in example 2 and what's the correct way with placeholders?

What am I doing wrong in example 2 and what's the correct way with placeholders?
The placeholder syntax is for passing values to be checked when the statement is executed. The expected usage is something like this:
DECLARE
v_out_1 varchar2(32);
v_out_2 varchar2(32);
plsql_block VARCHAR2(500);
BEGIN
plsql_block := 'SELECT column FROM mytable WHERE filter_column = :a';
EXECUTE IMMEDIATE plsql_block INTO v_out_1 USING 'some value';
EXECUTE IMMEDIATE plsql_block INTO v_out_2 USING 'another value';
END;
/

Whitelist the possible filter columns in a CASE expression:
DECLARE
v_out VARCHAR2(32);
column_name VARCHAR2(30) := 'COLUMN1';
column_value VARCHAR2(30) := 'value1';
sql VARCHAR2(500);
BEGIN
sql := 'SELECT column
FROM mytable
WHERE CASE :name
WHEN ''COLUMN1'' THEN column1
WHEN ''COLUMN2'' THEN column2
WHEN ''COLUMN3'' THEN column3
END = :value';
EXECUTE IMMEDIATE sql INTO v_out USING column_name, column_value;
END;
/

Related

How to set a variable and use it in a select query in Oracle PL/SQL?

How can I do something like this in oracle SQL developer?
DECLARE
p_name products.product_name%TYPE;
BEGIN
p_name := 'Strawberry';
SELECT * FROM products WHERE product_name = p_name;
END;
Use a cursor:
DECLARE
p_name VARCHAR2(100);
p_cur SYS_REFCURSOR;
BEGIN
p_name := 'Strawberry';
OPEN p_cur FOR
SELECT * FROM products where product_name=p_name;
-- do something with the cursor.
END;
/
Or use a SQL/Plus-style bind variable declaration:
VARIABLE p_name VARCHAR2;
BEGIN
:p_name := 'Strawberry';
END;
/
SELECT * FROM products where product_name=:p_name;
If only one row will ever be returned from your query (i.e. product_name is UNIQUE) then you can use SELECT ... INTO ...:
DECLARE
p_name VARCHAR2(100);
p_value1 products.value1%TYPE;
p_value2 products.value2%TYPE;
p_value3 products.value3%TYPE;
BEGIN
p_name := 'Strawberry';
SELECT value1, value2, value3
INTO p_value1, p_value2, p_value3
FROM products
WHERE product_name=p_name;
-- do something with the values.
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
-- Handle the exception
WHEN TOO_MANY_ROWS THEN
NULL;
-- Handle the exception
END;
/
You need to give the SELECT statement somewhere to put the results of the query:
DECLARE
p_name VARCHAR2(100);
aProducts_row PRODUCTS%ROWTYPE;
BEGIN
p_name := 'Strawberry';
SELECT *
INTO aProducts_row
FROM products
where product_name=p_name;
-- Add code to manipulate data in aProducts_row here, as in...
DBMS_OUTPUT.PUT_LINE('PRODUCT_NAME = ''' ||
aProducts_row.PRODUCT_NAME || '''');
END;
Or if you're expecting more than one row to be returned you can use a cursor:
DECLARE
p_name VARCHAR2(100);
BEGIN
p_name := 'Strawberry';
FOR aProducts_row IN (SELECT *
FROM products
where product_name=p_name)
LOOP
-- Add code to manipulate data in aProducts_row here, as in...
DBMS_OUTPUT.PUT_LINE('PRODUCT_NAME = ''' ||
aProducts_row.PRODUCT_NAME || '''');
END LOOP;
END;
If the question is not about setting variables but is actually about output from a PL/SQL anonymous block then it is already answered here.
declare
rc sys_refcursor;
begin
open rc for select 'Hello' as test from dual;
dbms_sql.return_result(rc);
end;
You can set any variable you want using the normal PL/SQL assignment syntax. If there is some part of the documentation that is not clear then please provide an example of what you are trying to do.

Using variable in FROM clause pl/sql

SET SERVEROUTPUT ON
DECLARE
table_name varchar2(80) := 'dual';
BEGIN
SELECT * FROM table_name WHERE dummy = 'X';
END;
The above code throws error. I want to use a variable in the from clause.
For a dynamic query you can use EXECUTE IMMEDIATE.
DECLARE
table_name VARCHAR2 (80) := 'dual';
v_query VARCHAR2 (200);
BEGIN
v_query := 'SELECT *
FROM ' || table_name || '
WHERE dummy = ''X''';
EXECUTE IMMEDIATE v_query;
END;

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

PLSQL dynamic query

I have a table A which has column A which holds table names as values.
All these tables have a common column C. I need maximum value of this column for each table.
I tried this using dynamic SQL but I'm getting errors. Please suggest.
DECLARE
query1 VARCHAR2(100);
c_table VARCHAR2(40);
c_obj VARCHAR2(20);
Cursor cursor_a IS
SELECT a FROM A;
BEGIN
Open cursor_a;
LOOP
Fetch cursor_a INTO c_table2;
EXIT WHEN cursor_a%notfound;
query1 := 'SELECT max(object_ref) AS "c_obj" FROM c_table' ;
EXECUTE IMMEDIATE query1;
dbms_output.put_line('Maximum value: '|| c_table || c_obj);
END LOOP;
Close cursor_a;
END;
Dynamic SQL can't see your PL/SQL variable: you need to pass it a string which can be executed in the scope of the SQL engine. So you need to concatenate the table name with the statement's boilerplate text:
query1 := 'SELECT max(c) FROM ' || variable_name;
You also need to return the result of the query into a variable.
Here is how it works (I've stripped out some of the unnecessary code from your example):
DECLARE
c_table VARCHAR2(40);
c_obj VARCHAR2(20);
BEGIN
for lrec in ( select a as tab_name from A )
LOOP
EXECUTE IMMEDIATE 'SELECT max(object_ref) FROM ' || lrec.tab_name
into c_obj ;
dbms_output.put_line('Maximum value: '|| lrec.tab_name
|| '='|| c_obj);
END LOOP;
END;
There is some miss match in veriables that you had used i.e.
declared as "c_table" but accessing as "c_table2"
Each table common column name is "C" but accessing as "object_ref"
In dynamic query use INTO keyword to store the value to your varibale
Suggestions
Use concat() function to prepare the query dynamically i.e. something like:
SET #SQL := CONCAT('SELECT max(c) INTO ', c_obj, ' FROM ',c_table);
Steps of implementing dynamic query is:
SET #SQL = <your dynamic query>
PREPARE stmt FROM #SQL;
EXECUTE stmt;
Sample code:
DECLARE
query1 VARCHAR2(100);
c_table VARCHAR2(40);
c_obj VARCHAR2(20);
CURSOR cursor_a IS
SELECT a FROM A;
BEGIN
OPEN cursor_a;
LOOP
FETCH cursor_a INTO c_table;
EXIT WHEN cursor_a%notfound;
SET #SQL := CONCAT('SELECT max(object_ref) AS c_obj INTO ', c_obj, ' FROM ',c_table);
PREPARE stmt FROM #SQL;
EXECUTE stmt;
dbms_output.put_line('Maximum value: '|| c_table || c_obj);
END LOOP;
CLOSE cursor_a;
END;

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;

Resources