PL SQL variable reference in a column - oracle

I have a requirement where i have a table column containing an expression that should get appended as a where clause to a select stmt in a procedure.
The expression has reference to variables available in the procedure.
Declare
Query varchar2(4000);
Expression varchar2(4000);
Value varchar2(10):= 'abc';
Valuex varchar2 := 'def';
... n number of variables declared
Cursor c1 is select expression from tableabc where some condition;
Begin
Expression := output of c1 cursor
Query := ‘select 1 from dual where 1 = 1 ';
Query := query || expression;
-- now i will use a ref cursor to evaluate query
End;
Table abctable has a expression column thta stores :
And 'abc'=value
The value being referred in this expression is the one declaree in pl sql block.
How can i achieve this as this is not working.
I want the value in expression column to be replaced with the one available in pl sql block. I dont want to use replace function as expression column can refer valuex variable or any of the n variable declared in pl sql block. Can the expression be stored in the column in a way that once retrieved in pl sql block it gets replaced with the variable value.
Thanks

Declare
Query varchar2(4000);
Value varchar2(10):= 'abc';
c1 sys_refcursor;
Begin
select 'select 1 from dual where 1 = 1 ' || expression
into query
from tableabc where some condition;
-- now i will use a ref cursor to evaluate query
-- suppose the expression looks like "and col1 = :v and col2 = :v"
open rc for query using value;
loop
-- fetch
-- exit when rc%notfound;
-- process
end loop;
close rc;
End;

Related

Pass an array to bind variables

I have a table that contains queries, for example:
select text from queries;
TEXT
1 select item from items where item_no between :low_item_no and :high_item_no and description <> :irellevant
The queries already contains the place holders for the bind variables.
The values themselves exists in variables table:
select * from vars;
ID NAME VALUE
1 1 low_item_no 100
2 2 high_item_no 300
3 3 irellevant_desc old
I have a package that takes the query and execute it with
execute immediate statement
but how do I bind those variables?
I don't know how much variables I have in such query, it's not static.
I wish to have a way to do something like that:
Execute immedaite my_query_str using v_array_of_vars;
Until now I don't know of a way to do something like that, only with list of variables for example:
Execute immedaite my_query_str using v_1, v_2, v_3;
Thanks!
I don't think you can do this with execute immediate as too much is unknown at compile time, so you'll have to use the dbms_sql package instead.
Here's a quick demo that gets the query and variables based on a common query ID. This assumes that the values in vars.name actually match the bind variable names in queries.text, and I haven't included any checks or error handling for that or other potential issues, or dealt with multiple select-list items or data types - just the basics:
declare
my_query_str queries.text%type;
my_cursor pls_integer;
my_result pls_integer;
my_col_descs dbms_sql.desc_tab2;
my_num_cols pls_integer;
my_item items.item%type;
begin
select text into my_query_str from queries where query_id = 42;
dbms_output.put_line(my_query_str);
-- open cursor
my_cursor := dbms_sql.open_cursor;
-- parse this query
dbms_sql.parse(my_cursor, my_query_str, dbms_sql.native);
-- bind all variables by name; assumes bind variables match vars.name
for r in (select name, value from vars where query_id = 42) loop
dbms_output.put_line('Binding ' || r.name || ' || with <' || r.value ||'>');
dbms_sql.bind_variable(my_cursor, r.name, r.value);
end loop;
my_result := dbms_sql.execute(my_cursor);
dbms_output.put_line('execute got: ' || my_result);
dbms_sql.describe_columns2(my_cursor, my_num_cols, my_col_descs);
dbms_sql.define_column(my_cursor, 1, my_item, 30); -- whatever size matches 'item'
-- fetch and do something with the results
while true loop
my_result := dbms_sql.fetch_rows(my_cursor);
if my_result <= 0 then
exit;
end if;
dbms_sql.column_value(my_cursor, 1, my_item);
dbms_output.put_line('Got item: ' || my_item);
end loop;
dbms_sql.close_cursor(my_cursor);
end;
/
You don't seem to really need an array; but if you wanted to you could create and populate an associative array as name/value pairs and then use that fir the binds.
This is just a starting point; you may have to deal with an unknown number and/or types of columns being returned, though if that's the case processing them meaningfully will be a challenge. Perhaps you need to return the result of the query as a ref cursor, which is even simpler; demo using the SQL*Plus variable and print commands:
var rc refcursor;
declare
my_query_str queries.text%type;
my_cursor pls_integer;
my_result pls_integer;
begin
select text into my_query_str from queries where query_id = 42;
dbms_output.put_line(my_query_str);
-- open cursor
my_cursor := dbms_sql.open_cursor;
-- parse this query
dbms_sql.parse(my_cursor, my_query_str, dbms_sql.native);
-- bind all variables by name; assumes bind variables match vars.name
for r in (select name, value from vars where query_id = 42) loop
dbms_output.put_line('Binding ' || r.name || ' || with <' || r.value ||'>');
dbms_sql.bind_variable(my_cursor, r.name, r.value);
end loop;
my_result := dbms_sql.execute(my_cursor);
dbms_output.put_line('execute got: ' || my_result);
:rc := dbms_sql.to_refcursor(my_cursor);
end;
/
print rc
Notice you don't close the cursor inside the PL/SQL block in this scenario.
You could also convert to a ref cursor and then fetch from that within your procedure - there's a bulk-collect example in the docs - but again you'd need to know the number and types of the select-list items to do that.

Oracle PL/SQL dynamic if statement global vars

I'm having trouble with dynamic sql, Issue is (I think) reading and setting global variable. Here's what I have and any help at all is greatly appreciated. Please let me know if you need table data too although I have included the data in comments.
CREATE OR REPLACE PACKAGE data_load
IS
curr_rec NUMBER;
curr_rule VARCHAR2(200);
curr_sql VARCHAR2(4000);
curr_sql_two VARCHAR2(4000);
curr_data_element VARCHAR2 (200);
curr_rule_text VARCHAR2(200);
curr_error_code VARCHAR2(10);
curr_error_flag VARCHAR2(10);
curr_flag_val NUMBER;
v_check NUMBER;
v_ID NUMBER;
cur_hdl INT ;
rows_processed NUMBER;
PROCEDURE check_rules;
END data_load;
The package body:
create or replace PACKAGE BODY data_load IS
PROCEDURE check_rules IS
CURSOR c1
IS
SELECT * FROM STAGING_TABLE where rownum < 3;
CURSOR c2
IS
SELECT * FROM ERROR_CODES WHERE rule_text IS NOT NULL AND status =1;
BEGIN
FOR rec1 IN c1
LOOP
FOR rec2 IN c2
LOOP
curr_data_element := 'rec1.'||rec2.data_element; --- this results in value "rec1.SHIP_FROM_ACCOUNT_ORG_CODE" without quotes
curr_rule_text := rec2.rule_text; --- this value is "is not null" without quotes
curr_error_flag := rec2.error_flag; --this value is "FLAG_03" without quotes
curr_flag_val := to_number(rec2.error_code); --- this value is 31
curr_sql :='begin if :curr_data_element '||curr_rule_text||' then update table_with_column_FLAG_03 set '||curr_error_flag ||' = 0; else update table_with_column_FLAG_03 set '||curr_error_flag ||' = '||curr_flag_val||'; end if; end;';
dbms_output.put_line(curr_sql); -- results in "begin if :curr_data_element is null then update table_with_column_FLAG_03 set FLAG_03 = 0; else update table_with_column_FLAG_03 set FLAG_03 = 31; end if; end;"
EXECUTE IMMEDIATE curr_sql USING curr_data_element ; -- this always updates the column with 31 even when curr_data_element/ rec1.SHIP_FROM_ACCOUNT_ORG_CODE is null and that's the problem
COMMIT;
END LOOP;
curr_rec := curr_rec+1;
END LOOP;
dbms_output.put_line(curr_rec);
END check_rules;
END data_load;
You've already highlighted the problem really:
curr_data_element := 'rec1.'||rec2.data_element; --- this results in value "rec1.SHIP_FROM_ACCOUNT_ORG_CODE" without quotes
You can't refer to cursor columns dynamically. You are creating a string with value 'rec1.SHIP_FROM_ACCOUNT_ORG_CODE'; there is no mechanism to evaluate what that represents. You can't, for instance, try to dynamically select that from dual because the rec1 is not in scope for a SQL call, even dynamically.
When you bind that string value it is never going to be null. You are using that string, not the value in the outer cursor that it represents, and essentially you cannot do that.
The simplest way to deal with this, if you have a reasonably small number of columns in your staging table that might appear as the rec2.data_element value, is to use a case expression to assign the appropriate actual rec1 column value to the curr_data_element variable, based on the rec2.data_element value:
...
BEGIN
FOR rec1 IN c1
LOOP
FOR rec2 IN c2
LOOP
curr_data_element :=
case rec2.data_element
when 'SHIP_FROM_ACCOUNT_ORG_CODE' then rec1.SHIP_FROM_ACCOUNT_ORG_CODE
when 'ANOTHER_COLUMN' then rec1.ANOTHER_COLUMN
-- when ... -- repeat for all possible columns
end;
curr_rule_text := rec2.rule_text;
...
If you have a lot of columns you could potentially do that via a collection but it may not be worth the extra effort.
The curr_sql string stays the same, all that's changing is that you're binding the actual value from the relevant rec1 column, rather than never-null string you were forming.

Oracle Variable as select

I have to call oracle SQL statement inside my project.
All connection related stuff is done, but my tool does not capture the output parameter executed by oracle.
Hence I need alter this query to return p_num value in a select statement.
i.e. the table which has 1 column ('p_num') with column name called 'Result' and which has only one row which is p_num value.
Following is the sql statement which currently gives output value with help of dbms_output.put_line
DECLARE
p_num varchar2(4000);
message varchar2(4000) ;
BEGIN
p_num := MyFunction();
dbms_output.put_line('Message : ' || p_num) ;
END;
What I want is p_num value in a SELECT statement so that I can capture specific column inside my bpm tool.
You can directly call the function in the SELECT statement.
1) First way is to do it VIA plain SQL
SELECT MyFunction FROM DUAL;
2) Second way is PLSQL but i will not recommend it unless its unavoidable
set serveroutput on;
declare
lv_var VARCHAR2(100);
lv_out_param VARCHAR2(100);
BEGIN
lv_var:=MyFunction(lv_out_param);
dbms_output.put_line(lv_var||' '||lv_out_param);
END;
/

Oracle cursor with variable columns/tables/criteria

I need to open a cursor while table name, columns and where clause are varying. The table name etc will be passed as parameter. For example
CURSOR batch_cur
IS
SELECT a.col_1, b.col_1
FROM table_1 a inner join table_2 b
ON a.col_2 = b.col_2
WHERE a.col_3 = 123
Here, projected columns, table names, join criteria and where clause will be passed as parameters. Once opened, i need to loop through and process each fetched record.
You need to use dynamic SQL something like this:
procedure dynamic_proc
( p_table_1 varchar2
, p_table_2 varchar2
, p_value number
)
is
batch_cur sys_refcursor;
begin
open batch_cur for
'select a.col_1, b.col_1
from ' || p_table_1 || ' a inner join || ' p_table_2 || ' b
on a.col_2 = b.col_2
where a.col_3 = :bind_value1';
using p_value;
-- Now fetch data from batch_cur...
end;
Note the use of a bind variable for the data value - very important if you will re-use this many times with different values.
From your question i guess you need a dynamic cursor. Oracle provides REFCURSOR for dynamic sql statements. Since your query will be built dynamically you need a refcursor to do that.
create procedure SP_REF_CHECK(v_col1 number,v_col2 date,v_tab1 number,v_var1 char,v_var2 varchar2)
is
Ref_cur is REF CURSOR;
My_cur Ref_cur;
My_type Table_name%rowtype;
stmt varchar2(500);
begin
stmt:='select :1,:2 from :3 where :4=:5';
open My_cur for stmt using v_col1,v_col2,v_tab1,v_var1,v_var2;
loop
fetch My_cur into My_type;
//do some processing //
exit when My_cur%notfound;
end loop;
close My_cur;
end;
Check this link for more http://docs.oracle.com/cd/B10500_01/appdev.920/a96624/11_dynam.htm

Fetch variable from cursor error in oracle pl sql

I'm fetching value using cursor:
CURSOR Colcond
IS
SELECT CONDITION
FROM CONDITION_TAB
WHERE PROCEDURE_NAME = 'CALL_VOL';
In first iteration it would fetch "SUM(CASE WHEN CALL_REF=0 THEN 1 ELSE 0 END)".
In my program:
OPEN Colcond;
FETCH Colcond INTO cond_val;
SELECT Appnum, customer_num,'"cond_val"'
INTO iappnum, icustnum,icond_val
FROM CALL_DETAILS WHERE APPNUM = val_appl
AND customer_num = val_cust
Group by APPLICATION_NUM,CUST_SGMT_NUM,DNIS;
INSERT INTO S_CALL_VOLUME VALUES (iappnum, icustnum, SYSDATE, icond_val);
The record thRough the variable "icond_val" inserted is SUM(CASE WHEN CALL_REF=0 THEN 1 ELSE 0 END) instead of the value (10 or 20 or 50).
How to get the value instead of that Sum case statement?
You need to use dynamic SQL to incorporate the value you selected from the condition_tab table into the next query. Here's an example in an anonymous block rather than a procedure:
declare
val_appl number; -- procedure argument in your version?
val_cust number; -- procedure argument in your version?
query_string varchar2(2000);
cond_val condition_tab.condition%type;
iappnum call_details.appnum%type;
icustnum call_details.customer_num%type;
icond_val number;
cursordyn sys_refcursor;
cursor colcond is
select condition
from condition_tab
where procedure_name = 'CALL_VOL';
begin
open colcond;
fetch colcond into cond_val;
close colcond;
query_string:='select appnum, customer_num, ' || cond_val || ' from call_details '
|| 'where appnum = :val_appl and customer_num = :val_cust '
|| 'group by application_num,cust_sgmt_num,dnis';
open cursordyn for query_string using val_appl, val_cust;
fetch cursordyn into iappnum, icustnum, icond_val;
close cursordyn;
insert into s_call_volume values (iappnum, icustnum, sysdate, icond_val);
end;
/
Your column names seem to be a bit inconsistent so it probably needs some tweaking.
For both cursors you're only selecting one row, so (a) they don't really need to be cursors, they can just be select into statements; and (b) the second one is selecting the two columns from the where clause which seems a bit pointless - when you use iappnum in the insert, you could just use val_app, etc. So I think you could simplify this to:
declare
val_appl number; -- procedure argument in your version?
val_cust number; -- procedure argument in your version?
query_string varchar2(2000);
cond_val condition_tab.condition%type;
icond_val number;
begin
select condition
into cond_val
from condition_tab
where procedure_name = 'CALL_VOL';
query_string:='select ' || cond_val || ' from call_details '
|| 'where appnum = :val_appl and customer_num = :val_cust '
|| 'group by application_num,cust_sgmt_num,dnis';
execute immediate query_string into icond_val using val_appl, val_cust;
insert into s_call_volume values (val_appl, val_cust, sysdate, icond_val);
end;
/
This will error if either query doesn't return exactly one row. Your cursor version will error if the condition_tab query finds no data, and will only use one 'condition' if there are multiples; and will only use the first result from the second query if there are multiples. If you're expecting multiples from either (not sure what your actual grouping is supposed to be, it looks inconsistent too) then you need to loop over the cursor, fetching repeatedly.
Hopefully this will get you started though.

Resources