How to get name of caller object in oracle - oracle

In Oracle, it's complicated to properly get name of caller object when it is wrapped in a package. Here is my attempt to achieve this:
CREATE OR REPLACE function GET_MY_NAME return varchar2 is
v_owner varchar2(50);
v_name varchar2(50);
v_lineno number ;
v_caller_t varchar2(50);
begin
-- let's determine the object
owa_util.who_called_me(
v_owner
, v_name
, v_lineno
, v_caller_t
);
dbms_output.put_line(v_caller_t || ' ' || v_owner || '.' || v_name || ' at ' || v_lineno);
with s as (
select s.*
, max(line - 1) over (partition by name order by line rows between 1 following and 1 following) to_line
, regexp_substr(s.text, '\w+', 1, 2) func_name
from all_source s
where s.type = v_caller_t
and s.owner = v_owner
and s.name = v_name
and regexp_like(s.text, 'procedure|function')
)
select max(nvl(s.func_name, v_name ))
, max(nvl(s.name , v_owner))
into v_name, v_owner
from s
where v_lineno between s.line and nvl(s.to_line, 9999999);
return v_owner || '.' || v_name;
end;
The idea is to determine lines within the source of each inner object is located and take corresponding object. This works fine for packages, but for functions and procedures it cannot determine if it was called from the main block. Here is a little demo:
create or replace function check_my_name return varchar2 is
function sub_check_my_name return varchar2 is
begin
return get_my_name;
end;
begin
dbms_output.put_line(sub_check_my_name);
return get_my_name;
end;
/
You can see that sub_check_my_name works, but direct get_my_name call also returns sub_check_my_name. How can I determine if the call occures inside main function block?

I did something as simple as this,
Declared a variable as below,
call_stack VARCHAR2(3000) := '';
Now use the below to get the levels of the calls,
FOR j IN REVERSE 1..utl_call_stack.dynamic_depth() LOOP
call_stack := CASE
WHEN call_stack IS NOT NULL THEN
call_stack || '->'
END
|| nvl(utl_call_stack.concatenate_subprogram(utl_call_stack.subprogram(j)), '');
END LOOP;
dbms_output.put_line(call_stack);
This gives the call hierarchy in the call_stack separated by '->'

Related

how to pass formatted string to column and same fetch it in package in PL/SQL

I have error master table which contain description like 'Error in table abc in xyz column.' I need to format string for column name which is xyz here. Where ever I need to call this table I will pass column name and then I will get expected description.
Ex - Insert into errorTabl values(01,There is error in {0})
Whenever inside package I need to retrieve value of 01 then I will pass column name col1 so then expected value will be as below :
01 There is error in col1
Request you to please help me for insert and select both statements.
Though this doesn't make sense, maybe the code below could help you to start with something or to clarify your problem.
NOTE: the code below is here just to show you the basics - there is no sense in it for any kind of production. You are the one to adjust it to your context.
So, the package to put and get things into or from errors table:
CREATE OR REPLACE PACKAGE ERRS AS
Procedure putError(p_table IN VarChar2, p_column IN VarChar2);
Function getError(p_table VarChar2, p_column VarChar2) RETURN VarChar2;
END ERRS;
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE PACKAGE BODY ERRS AS
Procedure putError(p_table IN VarChar2, p_column IN VarChar2) AS
BEGIN
Declare
mSql VarChar2(512) := '';
sq VarChar2(1) := '''';
Begin
mSql := 'Insert Into ERRORTABLE values( ' || sq || '01' || sq || ', ' || sq ||
'There is error in table ' || p_table || ' in ' || p_column || ' column' || sq || ')';
Execute Immediate mSql;
Commit;
End;
END putError;
-- -------------------------------------------------------------------------------
Function getError(p_table VarChar2, p_column VarChar2) RETURN VarChar2 IS
BEGIN
Declare
Cursor c IS
Select ERR_DESC From ERRORTABLE Where ERR_DESC Like('%table ' || p_table || ' in ' || p_column || '%');
mRet VarChar2(512) := '';
mDesc VarChar2(512) := '';
Begin
Open c;
LOOP
FETCH c into mDesc;
EXIT WHEN c%NOTFOUND;
mRet := '01 ' || mDesc || Chr(10);
END LOOP;
Close c;
RETURN RTRIM(mRet, Chr(10));
End;
END getError;
END ERRS;
Now the calling code to insert 5 records (once more - this is senseless) and to get you one of them...
set serveroutput on
DECLARE
errMsg VarChar2(512);
BEGIN
ERRS.putError('T_ABC', 'C_XYZ');
ERRS.putError('T_ABC', 'C_MNO');
ERRS.putError('T_ABC', 'C_PQR');
ERRS.putError('T_DEF', 'C_MNO');
ERRS.putError('T_DEF', 'C_XYZ');
--
errMsg := ERRS.getError('T_ABC', 'C_XYZ');
dbms_output.put_line(errMsg);
END;
/* R e s u l t :
anonymous block completed
01There is error in table T_ABC in C_XYZ column
*/
Just needed to pass double colon in insert query so then it will take single colon in table.
Ex - Insert into errorTabl values(01,There is error in ''{0}'')
In table it will be look like
**Id** **Description**
01 There is error in'{0}'.

PLS-00302: component 'M' must be declared in oracle 11g [duplicate]

This question already has answers here:
Accessing elements of Oracle PLSQL record type on runtime
(3 answers)
Closed 6 months ago.
I want to convert table type data into single variable with ',' separated
also I dont know what column used in table type. please let us know how to achieve it without passing column name, output must be string type.
DECLARE
TEST_TAB ADT.TEST_TABB2 := ADT.TEST_TABB2(adt.test_obj(2,
'dsd'),
adt.test_obj(3,
'agg'));
l_name clob := '';
CURSOR c IS
SELECT
ATTR_NAME
FROM
ALL_TYPE_ATTRS
WHERE
OWNER = 'ADT'
AND TYPE_NAME = (
SELECT
REFERENCED_NAME
FROM
DBA_DEPENDENCIES
WHERE
REFERENCED_OWNER = 'ADT'
AND NAME = 'TEST_TABB2'
AND REFERENCED_TYPE = 'TYPE');
t nvarchar2(500) := '';
BEGIN
FOR i IN TEST_TAB.FIRST .. TEST_TAB.LAST
LOOP
l_name := l_name || '(';
FOR cur IN c LOOP
t := cur.ATTR_NAME;
dbms_output.put_line(t);
l_name := l_name || ' ' || t ;
l_name := l_name || ', ' || TEST_TAB(i).t;
END LOOP;
l_name := l_name || ')' ;
END LOOP;
dbms_output.put_line(l_name);
END;
You could use dynamic SQL, concatenating in the attribute name, and passing the object as a bind variable:
...
BEGIN
FOR i IN TEST_TAB.FIRST .. TEST_TAB.last LOOP
FOR cur IN c LOOP
m := '.' || cur.ATTR_NAME;
execute immediate 'select :o' || m || ' from dual'
into t
using test_tab(i);
dbms_output.put_line(t);
END LOOP;
END LOOP;
END;
or
...
BEGIN
FOR i IN TEST_TAB.FIRST .. TEST_TAB.last LOOP
FOR cur IN c LOOP
execute immediate 'select :o.' || cur.ATTR_NAME || ' from dual'
into t
using test_tab(i);
dbms_output.put_line(t);
END LOOP;
END LOOP;
END;
which both produce output;
2
John
3
Devid
db<>fiddle
The first block above doesn't work in the unpatched version of 11gR2 available in db<>fiddle, but I'm not quite sure why, or whether it would work in a later, patched, version. The second block does work though.

Can we use collection variable as a table in execute immediate select statement? [duplicate]

I am building a function on PL/SQL using Oracle 11g.
I am trying to use a table variable within an EXECUTE IMMEDIATE statement, but it is not working, as you can see:
ERROR at line 1:
ORA-00904: "CENTER_OBJECTS": invalid identifier
ORA-06512: at "HIGIIA.KNN_JOIN", line 18
The code I am using is...
First, the type definitions
CREATE TYPE join_t IS OBJECT (
inn char(40),
out char(40)
);
/
CREATE TYPE join_jt IS TABLE OF join_t;
/
CREATE TYPE blob_t IS OBJECT (
id CHAR(40),
fv BLOB
);
/
CREATE TYPE blob_tt IS TABLE OF blob_t;
/
The function is:
create or replace FUNCTION knn_join (tab_inn IN varchar2, tab_out IN varchar2, blob_col1 IN varchar2, blob_col2 IN varchar2, dist_alg in VARCHAR2, kv in NUMBER ) RETURN join_jt
IS
var_fv BLOB;
var_id CHAR(40);
center_objects blob_tt := blob_tt();
retval join_jt := join_jt ();
join_table join_jt := join_jt();
sql_stmt1 varchar2(400);
sql_stmt2 varchar2(400);
BEGIN
sql_stmt1 := 'SELECT blob_t(ROWIDTOCHAR(rowid),' || blob_col1 || ') FROM ' || tab_out;
sql_stmt2 := 'SELECT join_t(ROWIDTOCHAR(r.rowid), center_objects(idx).id) FROM ' || tab_inn || ' r WHERE ' || dist_alg || '_knn(r.' || blob_col2 || ', center_objects(idx).' || blob_col1 || ')<=' || kv;
dbms_output.put_line(sql_stmt2);
EXECUTE IMMEDIATE sql_stmt1 BULK COLLECT INTO center_objects;
for idx in center_objects.first()..center_objects.last()
loop
--SELECT join_t(ROWIDTOCHAR(r.rowid), center_objects(idx).id) BULK COLLECT INTO join_table FROM londonfv r WHERE manhattan_knn(r.fv, center_objects(idx).fv) <=5;
EXECUTE IMMEDIATE sql_stmt2 BULK COLLECT INTO join_table;
for idx2 in join_table.first()..join_table.last()
loop
retval.extend();
retval(retval.count()) := join_table(idx2);
end loop;
end loop;
RETURN retval;
END;
/
To run the function:
select * from TABLE(knn_join('london','cophirfv','fv','fv','manhattan',5));
I am trying to use run the statement 'SELECT join_t(ROWIDTOCHAR(r.rowid), center_objects(idx).id) BULK COLLECT INTO join_table FROM london r WHERE manhattan_knn(r.fv, center_objects(idx).fv) <=5' using the EXECUTE IMMEDIATE, but it does not work because I am using a variable in it.
Can someone give me a hand on it?
Thanks in advance!
You can't refer to a local PL/SQL variable inside a dynamic SQL statement, because it is out of scope within the SQL context used by the dynamic call. You could replace your first call:
SELECT join_t(ROWIDTOCHAR(r.rowid), center_objects(idx).id) FROM ' ...
with a bind variable:
SELECT join_t(ROWIDTOCHAR(r.rowid), :id FROM ' ...
EXECUTE IMMEDIATE ... USING center_objects(idx).id ...
but you can't do what when the object attribute is variable too:
... ', center_objects(idx).' || blob_col1 || ')<='...
although - at least in the example you've shown - the only object attribute name available is fv, regardless of the table column names passed in to the function - so that could be hard-coded; and thus a bind variable could be used:
... ', :fv)<='...
EXECUTE IMMEDIATE ... USING center_objects(idx).id, center_objects(idx).fv ...
and the kv value should also be a bind variable, so you'd end up with:
create or replace FUNCTION knn_join (tab_inn IN varchar2, tab_out IN varchar2,
blob_col1 IN varchar2, blob_col2 IN varchar2, dist_alg in VARCHAR2, kv in NUMBER )
RETURN join_jt
IS
center_objects blob_tt := blob_tt();
retval join_jt := join_jt ();
join_table join_jt := join_jt();
sql_stmt1 varchar2(400);
sql_stmt2 varchar2(400);
BEGIN
sql_stmt1 := 'SELECT blob_t(ROWIDTOCHAR(rowid),' || blob_col1 || ') FROM ' || tab_out;
sql_stmt2 := 'SELECT join_t(ROWIDTOCHAR(r.rowid), :id) FROM ' || tab_inn || ' r WHERE '
|| dist_alg || '_knn(r.' || blob_col2 || ', :fv)<= :kv';
dbms_output.put_line(sql_stmt1);
dbms_output.put_line(sql_stmt2);
EXECUTE IMMEDIATE sql_stmt1 BULK COLLECT INTO center_objects;
for idx in center_objects.first()..center_objects.last()
loop
EXECUTE IMMEDIATE sql_stmt2 BULK COLLECT INTO join_table
USING center_objects(idx).id, center_objects(idx).fv, kv;
for idx2 in join_table.first()..join_table.last()
loop
retval.extend();
retval(retval.count()) := join_table(idx2);
end loop;
end loop;
RETURN retval;
END;
/
As far as I can tell you could still do the join within the dynamic SQL statement, and eliminate the loops and the need for the intermediate center_objects and join_table collections:
create or replace FUNCTION knn_join (tab_inn IN varchar2, tab_out IN varchar2,
blob_col1 IN varchar2, blob_col2 IN varchar2, dist_alg in VARCHAR2, kv in NUMBER )
RETURN join_jt
IS
retval join_jt;
sql_stmt varchar2(400);
BEGIN
sql_stmt := 'SELECT join_t(ROWIDTOCHAR(tinn.rowid), ROWIDTOCHAR(tout.rowid))'
|| ' FROM ' || tab_inn || ' tinn JOIN ' || tab_out || ' tout'
|| ' ON ' || dist_alg || '_knn(tinn.fv, tout.fv) <= :kv';
dbms_output.put_line(sql_stmt);
EXECUTE IMMEDIATE sql_stmt BULK COLLECT INTO retval USING kv;
RETURN retval;
END;
/
When you call it as you've shown:
select * from TABLE(knn_join('london','cophirfv','fv','fv','manhattan',5));
that's the equivalent of the hard-coded:
SELECT join_t(ROWIDTOCHAR(tinn.rowid), ROWIDTOCHAR(tout.rowid))
FROM london tinn
JOIN cophirfv tout
ON manhattan_knn(tinn.fv, tout.fv) <= 5
... so I guess you can verify whether that hard-coded version gives you the results you expect first. (Adding sample data and expected results to the question would have helped, of course).
That join condition may be expensive, depending on what the function is doing, how may rows are in each table (as every row in each table has to be compared with every row in the other), whether you actually have other filters, etc. The loop version would be even worse though. Without more information there isn't much to be done about that anyway.
As an aside, using varchar2 instead of char for the object attributes would be more normal; that's also the data type returned by the rowidtochar() function.

Can I access a cursor from within a procedure by name?

The cursor name would be passed in as a varchar2, and the cursor itself exists in the same package as the procedure.
Given only the name (and not a cursor reference) is it possible to access the cursor and loop through it?
If this requires the use of an "execute immediate" or similar, that's not out of the question... (though it's not quite clear to me how that would work, I was under the impression anything it declares is out of scope once it completes).
Apologies in advance, this seems like it should be obvious to me but I'm coming up blank.
Thinking about this a bit, I think you're going about it the wrong way. I'd UNION ALL the results from each of the "cursors" together and then use the "cursor name" to eliminate all the unwanted rows (which the optimizer should optimize away) so that you only get the rows you want. So something like
CREATE OR REPLACE PROCEDURE DO_SOMETHING(pin_Cursor_name IN VARCHAR2)
IS
CURSOR csrFruits IS
SELECT UPPER(pin_Cursor_name) AS FRUIT_TYPE,
VARIETY_NAME,
COLOR,
SIZE,
TARTNESS_RATING,
NULL AS FUZZ_LENGTH,
ROOTSTOCK,
NULL AS PEEL_THICKNESS
FROM APPLES
WHERE pin_Cursor_name = 'apples'
UNION ALL
SELECT UPPER(pin_Cursor_name) AS FRUIT_TYPE,
VARIETY_NAME,
COLOR,
SIZE,
NULL AS TARTNESS_RATING,
FUZZ_LENGTH,
NULL AS ROOTSTOCK,
NULL AS PEEL_THICKNESS
FROM PEACHES
WHERE pin_Cursor_name = 'peaches'
UNION ALL
SELECT UPPER(pin_Cursor_name) AS FRUIT_TYPE,
VARIETY_NAME,
COLOR,
SIZE,
NULL AS TARTNESS_RATING,
NULL AS FUZZ_LENGTH,
NULL AS ROOTSTOCK,
PEEL_THICKNESS
FROM KUMQUATS
WHERE pin_Cursor_name = 'kumquats'
UNION ALL
SELECT UPPER(pin_Cursor_name) AS FRUIT_TYPE,
VARIETY_NAME,
'GREEN' AS COLOR,
SIZE,
NULL AS TARTNESS_RATING,
FUZZ_LENGTH,
ROOTSTOCK,
NULL AS PEEL_THICKNESS
FROM KIWIS
WHERE pin_Cursor_name = 'kiwis';
BEGIN
FOR aRow IN csrFruits LOOP
DBMS_OUTPUT.PUT_LINE(pin_Cursor_name || ' - ' ||
aRow.VARIETY_NAME || ', ' ||
aRow.COLOR || ', ' ||
aRow.SIZE);
END LOOP;
END DO_SOMETHING;
So here we have a cursor which will read from one of four different tables (APPLES, PEACHES, KUMQUATS, and KIWIS) depending on the input parameter. The idea is to have each of the subqueries return a rowset of the same "shape", adding NULL AS XXX for each column which an individual subquery doesn't supply.
Best of luck.
Your original problem statement is quite a bit vague and it's unclear for me what constraints you have and what that "other system" expects as a return value. You also might have an XY-problem, so the answer by #bobjarvis might have a valid point too.
The key problem here is that in PL/SQL there is no way to convert an explicit cursor to a cursor variable. Thus the following "simple" solution is not possible:
-- pseudo PL/SQL code
cursor cur_a ...
cursor cur_b ...
function get_cursor(p_cur_name varchar2) return sys_refcursor is
v_cur sys_refcursor;
begin
execute immediate 'open v_cur for p_cur_name';
return v_cur;
end;
v_cur := get_cursor('cur_b');
In the example package below I assume that all the cursors will have the same result set structure. I was lazy and used weak cursor variables even I should have used strong ones. The package code should be easy to follow.
There is at least one other variation that might be useful for you - bulk collect the data to a collection and process the collection with other subroutines. Below print(varchar2) just demonstrates how to open-iterate-close a cursor "dynamically" with a dbms_output.put_line.
create or replace package so48 is
cursor cur_a is
select 'A1' as val, 1 as id from dual union all
select 'A2' as val, 2 as id from dual union all
select 'A3' as val, 3 as id from dual
;
cursor cur_b is
select 'B1' as val, 4 as id from dual union all
select 'B2' as val, 5 as id from dual union all
select 'B3' as val, 6 as id from dual
;
function get_cursor(p_cur_name in varchar2) return sys_refcursor;
procedure print(p_cur in sys_refcursor);
procedure print(p_cur_name in varchar2);
end;
/
show errors
create or replace package body so48 is
function get_cursor(p_cur_name in varchar2) return sys_refcursor is
v_cur sys_refcursor;
begin
case
when p_cur_name = 'A' then
open v_cur for
select 'A1' as val, 1 as id from dual union all
select 'A2' as val, 2 as id from dual union all
select 'A3' as val, 3 as id from dual
;
when p_cur_name = 'B' then
open v_cur for
select 'B1' as val, 4 as id from dual union all
select 'B2' as val, 5 as id from dual union all
select 'B3' as val, 6 as id from dual
;
else
null;
end case;
return v_cur;
end;
procedure print(p_cur in sys_refcursor) is
v_val varchar2(32767);
v_id number;
begin
loop
fetch p_cur into v_val, v_id;
exit when p_cur%notfound;
dbms_output.put_line('(val = ' || v_val || ')(id = ' || v_id || ')');
end loop;
end;
procedure print(p_cur_name in varchar2) is
plsql_compilation_error exception;
pragma exception_init(plsql_compilation_error, -6550);
v_cur_name constant varchar2(32767) := 'so48.' || p_cur_name;
v_plsql constant varchar2(32767) :=
q'[declare
v_val varchar2(32767);
v_id number;
begin
open ]' || v_cur_name || q'[;
loop
fetch ]' || v_cur_name || q'[ into v_val, v_id;
exit when ]' || v_cur_name || q'[%notfound;
dbms_output.put_line('(val = ' || v_val || ')(id = ' || v_id || ')');
end loop;
close ]' || v_cur_name || q'[;
end;]';
begin
execute immediate v_plsql;
exception
when plsql_compilation_error then
dbms_output.put_line('PL/SQL compilation error');
end;
end;
/
show errors
Example run
SQL> exec so48.print(so48.get_cursor('A'))
(val = A1)(id = 1)
(val = A2)(id = 2)
(val = A3)(id = 3)
PL/SQL procedure successfully completed.
SQL> exec so48.print('cur_b')
(val = B1)(id = 4)
(val = B2)(id = 5)
(val = B3)(id = 6)
PL/SQL procedure successfully completed.
SQL>

Dynamically assigning variables oracle sql

I have a table attribute_config with below columns:
table_name column_name key
Let us say is has below 2 rows
account accountphone accountnum
customer customernumber customerid
Key can be only accountnum or customerid.
I have to write code which will accept (i_accountnum,i_customerid) and;
fetch the respective values from columns mentioned in column_name in tables mentioned in table_name using the key in where condition.
For ex: select accountphone from account where accountnum = i_accountnum
select customernumber from customer where customerid = i_customerid
the complete query should be formed dynamically, whether to pass i_accountnum or i_customerid in the query also needs to be decided dynamically. if key - accountnum, i_accountnum will be passed to where condition.
I have been trying on these lines so far, this is not working, i know it is wrong.
declare
v_accountnum varchar2(20);
v_customerid varchar2(20);
v_attribute_value varchar2(20);
v_stmt varchar2(255);
begin
Account_Num := 'TestCustomer'; -- input to the function
v_customer_ref := 'TestAccount'; -- input to the function
for i in (Select * from attribute_config) loop
v_stmt := 'select ' || i.column_name || ' from ' || i.table_name ||' where ' || i.key|| ' = v_' || i.key;
execute immediate v_Stmt into v_attribute_value;
end loop;
end;
This will fix your code, but I do not see any advantage of using dynamic query when your code should accept 2 parameters(i_accountnum,i_customerid) - which is already static situation and fetch the relevant values, perhaps only in learning purposes.
declare
procedure fecth_values(i_accountnum account.accountnum%type,
i_customerid customer.customerid%type) return varchar2 is
v_attribute_value varchar2(20);
begin
for i in (select * from attribute_config) loop
execute immediate 'select ' || i.column_name || ' from ' ||
i.table_name || ' where ' || i.key || ' = ' || case when i.key = 'accountnum' then i_accountnum when i.key = 'customerid' then i_customerid end;
into v_attribute_value;
dbms_output.put_line(v_attribute_value);
end loop;
return null;
end;
begin
fecth_values(1, 1);
end;
Your where clause was wrong the i.key should be compared against the inputed values, not the 'v_' || i.key, which is undeclared when you execute your stmt.

Resources