Is it possible to dereference a string as a variable in PL/SQL?
I'm looking for something like this:
declare
my_var CONSTANT varchar2(50) := 'test';
my_var_ref CONSTANT varchar2(50) := 'my_var';
begin
-- This of course doesn't work, it's just to illustrate what I'm looking for:
DBMS_OUTPUT.PUT_LINE(&my_var_ref) -- Prints test
end;
Pass the name as a procedure parameter:
declare
my_var CONSTANT varchar2(50) := 'test';
my_var_ref CONSTANT varchar2(50) := 'my_var';
begin
my_func(&my_var_ref) -- Pass my_var instead of my_var_ref
end;
[TL;DR] No, you cannot dereference a PL/SQL variable. However, an alternative option might be to use an associative array.
If you try to dereference it using EXECUTE IMMEDIATE then the PL/SQL block that is being dynamically evaluated has no knowledge of the context from the calling block so cannot get the value of the dereferenced variable.
For example:
DECLARE
my_var CONSTANT varchar2(50) := 'test';
my_var_ref CONSTANT varchar2(50) := 'my_var';
value VARCHAR2(50);
plsql_block VARCHAR2(100) := 'BEGIN :value := ' || my_var_ref || '; END;';
BEGIN
DBMS_OUTPUT.PUT_LINE( plsql_block );
EXECUTE IMMEDIATE plsql_block USING IN OUT value;
DBMS_OUTPUT.PUT_LINE( value );
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE( SQLERRM );
END;
/
Outputs:
BEGIN :value := my_var; END;
ORA-06550: line 1, column 17:
PLS-00201: identifier 'MY_VAR' must be declared
It has got the correct variable name but the variable is not defined in the context within which the dynamic evaluation of the PL/SQL block is occurring so it doesn't work.
However, if you try to pass in the variable's value (rather than trying to dereference the variable) then this demonstrates that, apart from dereferencing, the rest of the code would work:
DECLARE
my_var CONSTANT varchar2(50) := 'test';
my_var_ref CONSTANT varchar2(50) := 'my_var';
value VARCHAR2(50);
plsql_block VARCHAR2(100) := 'BEGIN :value := ''' || my_var || '''; END;';
BEGIN
DBMS_OUTPUT.PUT_LINE( plsql_block );
EXECUTE IMMEDIATE plsql_block USING IN OUT value;
DBMS_OUTPUT.PUT_LINE( value );
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE( SQLERRM );
END;
/
Outputs:
BEGIN :value := 'test'; END;
test
However, you could use a PL/SQL associative array instead:
DECLARE
TYPE MAP IS TABLE OF VARCHAR2(50) INDEX BY VARCHAR2(50);
my_var_ref CONSTANT VARCHAR2(50) := 'my_var';
v_map MAP;
BEGIN
v_map('my_var') := 'test';
DBMS_OUTPUT.PUT_LINE( v_map( my_var_ref) );
END;
/
Which outputs:
test
db<>fiddle here
You can using packages.
create or replace package test_pkg as
my_var1 varchar2(50);
my_var2 varchar2(50);
my_var3 varchar2(50);
procedure set_val(p_var in varchar2,
p_val in varchar2);
function get_val(p_var in varchar2)
return varchar2;
end test_pkg;
/
create or replace package body test_pkg as
procedure set_val(p_var in varchar2,
p_val in varchar2) is
begin
execute immediate 'begin
test_pkg.' || p_var || ' := :x;
end;'
using p_val;
end set_val;
function get_val(p_var in varchar2)
return varchar2 is
v_value varchar2(50);
begin
execute immediate 'begin
:x := test_pkg.' || p_var || ';
end;'
using out v_value;
return v_value;
end get_val;
end test_pkg;
/
Then
set serveroutput on
begin
for i in 1 .. 3 loop
test_pkg.set_val('my_var' || i, 'Test ' || i);
end loop;
for i in 1 .. 3 loop
dbms_output.put_line('my_var' || i || ' = ' || test_pkg.get_val('my_var' || i));
end loop;
end;
Output:
my_var1 = Test 1
my_var2 = Test 2
my_var3 = Test 3
Related
Hi I have the following procedure:
create or replace procedure
SP_DELETE_FROM_TABLE(pTableName in VARCHAR2, pFieldName in VARCHAR2,
pFieldValue in VARCHAR2,pFieldType in VARCHAR2) is
querystring VARCHAR2(500);
begin
queryString := 'DELETE FROM ' ||pTableName||
' WHERE '||pFieldName ||' = DECODE(:pFieldType,integer,:pFieldValue)' ;
EXECUTE IMMEDIATE queryString USING pFieldType,pFieldValue;
end SP_DELETE_FROM_TABLE;
all my Parameters are of Type VARCHAR2, What I am trying to do is: When I call the procedure with the following values ('users_table','users_id','11','integer')
so by using DECODE I would like to check if pFieldValue is of type pFieldTypeand if yes return pFieldValue
so if pFieldValue is:11 and pfieldType is:integer it should delete users_id 11 if fieldType is string do nothing..
I'd create a function that checks parameter has correct type
and then use it in main procedure
--main procedure
create or replace procedure SP_DELETE_FROM_TABLE(pTableName in VARCHAR2, pFieldName in VARCHAR2,pFieldValue in VARCHAR2,pFieldType in VARCHAR2) is
querystring VARCHAR2(500);
begin
if 'Y' = is_type_correct(pFieldValue, pFieldType ) then
queryString := 'DELETE FROM ' ||pTableName|| ' WHERE '
||pFieldName ||' = :pFieldValue';
EXECUTE IMMEDIATE queryString USING pFieldValue;
end
else
null; --incorrect type and parameter, do nothing
end;
end SP_DELETE_FROM_TABLE;
--check function
CREATE OR REPLACE FUNCTION is_type_correct( p_val IN VARCHAR2, p_type varchar2 )
RETURN VARCHAR2 DETERMINISTIC PARALLEL_ENABLE
IS
l_num NUMBER;
l_date date;
BEGIN
if 'integer' = p_type
then
l_num := to_number( p_val );
elsif 'date' = p_type then
l_date := to_date(p_val, 'YYYY.MM.DD');
elsif 'varchar2' then
null;//do nothing
else
return 'N'; //uknown type
end if;
RETURN 'Y';
EXCEPTION
WHEN value_error THEN
RETURN 'N';
END is_number;
Just try to convert a string to a number, and when an exception occurs, do nothing:
querystring VARCHAR2(500);
some_number number;
begin
some_number := to_number( pFieldValue ); /* trying to convert to a number */
queryString := 'DELETE FROM ' ||pTableName||
' WHERE '||pFieldName ||' = :x' ;
EXECUTE IMMEDIATE queryString USING some_number;
EXCEPTION
WHEN VALUE_ERROR THEN null; /* do nothing when pFieldValue is not a number*/
end SP_DELETE_FROM_TABLE;
I have problem Dynamic Call Store Procedure
v_sql := 'begin '|| p_procname || '(''test1'','' test2 '',:v_output2); end;';
execute immediate v_sql
using out v_output2 ;
dbms_output.put_line(v_output2 || ' ' );
In here ı can call procedure with execute immediate .
But my problem is dynamic bind variable . This values comes from log table then i parse for execute_immediate procedure
v_sql := 'begin '|| p_procname || '(''test1'','' test2'',:v_output2); end;';
v_sql1:= ||using|| 'out v_output2 ' ;
execute immediate v_sql
v_sql1;
It doesnt work like that . How can i make dynamic variables bind , because i call a lot of procedure and thats procedure has different in and out parameters.
I hope you can understand what problem i have .How can i pass this problems thx
here is simple procedure
create procedure test_proc(p_user varchar2, p_code varchar2, p_error varchar2) is
begin
p_error := p_user || p_code;
end;
calling code for same ..
Declare
v_test_proc varchar2(50) := 'test_proc';
p_user varchar2(50) := 'test_name';
p_code varchar2(50) := 'test_code';
p_error varchar2(100);
v_sql varchar2(2000);
begin
v_sql := 'begin ' || v_test_proc || '( :1 ,:2, :3 ); end;';
execute immediate v_sql
using p_user, p_code, out p_error;
dbms_output.put_line(p_error);
end;
I have this thing in unix for example below
filename1_temp=3
var1=filename1
var2=${var1}_temp
var3=\$${var2}
so basically var3 contains the value stored in variable filename1_temp which is 3. I need this implementation in oracle PL/SQL. For example
filename1_temp:=3
var1:="filename1";
var2:=var1||"_temp";
var3:=???
How will I do it in PL/SQL??
Please suggest.
You'd like to evaluate a string as a PL/SQL code. Essentially the following code should do what you're asking but unfortunately it doesn't work:
declare
filename1_temp constant varchar2(32767) := 'magic';
var1 constant varchar2(32767) := 'filename1';
var2 constant varchar2(32767) := var1 || '_temp';
var3 varchar2(32767);
function eval(p_var in varchar2) return varchar2 is
v_ret varchar2(32767);
begin
execute immediate 'begin :ret := ' || p_var || '; end;'
using out v_ret;
return v_ret;
end;
begin
dbms_output.put_line('var3 = ' || var3);
var3 := eval(var2); -- expecting magic
dbms_output.put_line('var3 = ' || var3);
end;
/
It fails with:
ORA-06550: line 1, column 15:
PLS-00201: identifier 'FILENAME1_TEMP' must be declared
because execute immediate runs in SQL context and it doesn't know the details of the anonymous PL/SQL block. So you have to make the variable accessible from SQL too. Here is an example where I have used public package variable:
create or replace package so49 is
filename1_temp varchar2(32767);
end;
/
show errors
declare
var1 constant varchar2(32767) := 'so49.filename1';
var2 constant varchar2(32767) := var1 || '_temp';
var3 varchar2(32767);
function eval(p_var in varchar2) return varchar2 is
v_ret varchar2(32767);
begin
execute immediate 'begin :ret := ' || p_var || '; end;'
using out v_ret;
return v_ret;
end;
begin
so49.filename1_temp := 'magic';
dbms_output.put_line('var3 = ' || var3);
var3 := eval(var2); -- expecting magic
dbms_output.put_line('var3 = ' || var3);
end;
/
The magic happens:
SQL> #so49
var3 =
var3 = magic
PL/SQL procedure successfully completed.
SQL>
Hope this helps.
I am not sure what you are looking for but do you want this kind of stuff..
declare
filename1_temp number(4);
var1 number(4);
var2 varchar2(100);
var3 varchar2(100);
begin
filename1_temp := 3 ;
var1 := filename1_temp;
var2 := to_char(var1) || '_temp';
var3 := var2;
dbms_output.put_line(var3);
end;
I'm trying to execute a stored procedure, but I'm not sure if my syntax is wrong or not.
set dbms_output.put_line on;
declare
v_premnum NUMBER(10);
v_util_type CHAR(10);
v_result VARCHAR2(200);
Begin
execute myprocedure( 'E', v_util_type, 73105 , v_premnum);
dbms_output.put_line = v_result
end;
/
There are two ways. Either your procedure has an output parameter:
declare
v_premnum NUMBER(10) := 1234;
v_util_type CHAR(10) := 'the type';
v_result VARCHAR2(200);
begin
myprocedure('E', v_util_type, 73105 , v_premnum, v_result);
dbms_output.put_line(v_result);
end;
Or it should be a function returning the desired value:
declare
v_premnum NUMBER(10) := 1234;
v_util_type CHAR(10) := 'the type';
v_result VARCHAR2(200);
begin
v_result := myfunction('E', v_util_type, 73105 , v_premnum);
dbms_output.put_line(v_result);
end;
For some reason, I have certain fields in a table, that are collections of chars. Chars of different length. Example:
create or replace type t_charray_1 as varray(5) of char(1);
create or replace type t_charray_2 as varray(5) of char(2);
create or replace type t_charray_3 as varray(5) of char(3);
create or replace type t_charray_4 as varray(5) of char(4);
create table mytable (
field1 number,
field2 t_charray_1,
field3 t_charray_3,
Also, I have a function that returns a (fixed length) string representation of a mytable record. This function calls other functions that are returning the string representation of a given collection-typed field. Examples:
function to_chr(
p_array in t_charray_1,
pad_length in number,
p_list_length in number
) return char as
v_res varchar2(255) := '';
begin
for i in 1 .. p_list_length loop
if p_array is not null and p_array.exists(i) and p_array(i) is not null then
v_res := v_res || rpad(p_array(i), pad_length, ' ');
else
v_res := v_res || rpad(' ', pad_length, ' ');
end if;
end loop;
return v_res;
end to_chr;
------------------------------------------------------------------------------
function to_chr(
p_array in t_charray_2,
pad_length in number,
p_list_length in number
) return char as
v_res varchar2(255) := '';
begin
for i in 1 .. p_list_length loop
if p_array is not null and p_array.exists(i) and p_array(i) is not null then
v_res := v_res || rpad(p_array(i), pad_length, ' ');
else
v_res := v_res || rpad(' ', pad_length, ' ');
end if;
end loop;
return v_res;
end to_chr;
Note that these functions are overloaded versions of each other. The only difference in their signature is the type of the p_array argument.
Please also note that the bodies of these functions are identical.
Motivation
I want to eliminate duplicate code. What are my choices?
EDIT I have heard of sys.anydata but never used it. Can it be a solution?
You could write a procedure that takes the largest type, and explicitly CAST the smaller types to the larger type before passing them. Note that CAST can only be used in SQL
DECLARE
x t_charray_1 := t_charray_1();
y t_charray_2 := t_charray_2();
PROCEDURE foo( p_foo t_charray_2 )
AS
BEGIN
FOR i IN p_foo.FIRST..p_foo.LAST loop
dbms_output.put_line( p_foo(i) );
END LOOP;
END;
BEGIN
x.EXTEND;
x.EXTEND;
x(1) := 'A';
x(2) := 'B';
y.EXTEND;
y.EXTEND;
y(1) := 'AA';
y(2) := 'BB';
foo(y);
SELECT CAST(x AS t_charray_2) INTO y FROM dual;
foo(y);
END;
/
Try:
create or replace function to_chr(p_array in anydata,
pad_length in number,
p_list_length in number) return char as
v_res varchar2(255) := '';
x number;
v_array t_charray_4;
v_array1 t_charray_1;
v_array2 t_charray_2;
v_array3 t_charray_3;
begin
dbms_output.put_line(p_array.GetTypeName);
case p_array.GetTypeName
when '<schema>.T_CHARRAY_1' then
x := p_array.GetCollection(v_array1);
select cast(v_array1 as t_charray_4) into v_array from dual;
when '<schema>.T_CHARRAY_2' then
x := p_array.GetCollection(v_array2);
select cast(v_array2 as t_charray_4) into v_array from dual;
when '<schema>.T_CHARRAY_3' then
x := p_array.GetCollection(v_array3);
select cast(v_array3 as t_charray_4) into v_array from dual;
when '<schema>.T_CHARRAY_4' then
x := p_array.GetCollection(v_array);
end case;
for i in 1 .. p_list_length loop
if v_array is not null and v_array.exists(i) and v_array(i) is not null then
v_res := v_res || rpad(v_array(i), pad_length, ' ');
else
v_res := v_res || rpad(' ', pad_length, ' ');
end if;
end loop;
return v_res;
end to_chr;
you can run it like this:
declare
p_array anydata;
v_array t_charray_3 := new t_charray_3('aaa', 'bbb');
v_res varchar2(255);
begin
p_array := anydata.convertcollection(v_array);
v_res := to_chr(p_array => p_array, pad_length => 2, p_list_length => 3);
end;