How to transfer cursor to the procedure dynamically and to set rowtype variable dynamically? - oracle

I have written the procedure with dynamically set cursor and %rowtype variable:
create or replace procedure process(source_table IN varchar2, my_cursor IN sys_refcursor)
is
c sys_refCURSOR;
rec my_cursor%rowtype;
begin
Dbms_Output.put_line('process starts');
open c for 'select * from '||source_table;
loop
fetch c into rec;
exit when c%notfound;
end loop;
close c;
Dbms_Output.put_line('process is over');
end process;
I am going to transfer cursor to the procedure with the function as follows:
CREATE OR REPLACE FUNCTION ddp_get_allitems (source_table IN Varchar2)
RETURN SYS_REFCURSOR
AS
my_cursor SYS_REFCURSOR;
BEGIN
OPEN my_cursor FOR 'SELECT * FROM '|| source_table;
RETURN my_cursor;
END ddp_get_allitems;
While compiling the procedure "process" I have the error:
PLS-00320 the declaration of the type of the expression is incomplete or malformed.
The compiler has hilighted the row with "rec my_cursor%rowtype;" as the error source. The varibale "source_table" and "my_cursor" are based upon the same table (select * from my_table).
So Why the error has arisen and how to remove it?

Since PL/SQL is statically typed the compiler needs to know the types of all the variables at compile time.
So there is no room for advanced metaprogramming. I'm afraid you can't do that.
There are, however, generic types found at SYS.STANDARD and a few internal functions accepting them.
-- The following data types are generics, used specially within package
-- STANDARD and some other Oracle packages. They are protected against
-- other use; sorry. True generic types are not yet part of the language.
type "<ADT_1>" as object (dummy char(1));
type "<RECORD_1>" is record (dummy char(1));
type "<TUPLE_1>" as object (dummy char(1));
type "<VARRAY_1>" is varray (1) of char(1);
type "<V2_TABLE_1>" is table of char(1) index by binary_integer;
type "<TABLE_1>" is table of char(1);
type "<COLLECTION_1>" is table of char(1);
type "<REF_CURSOR_1>" is ref cursor;
Take "<ADT_1>" for example. There is XMLTYPE constructor or DBMS_AQ ENQUEUE and DEQUEUE functions. You can pass any kind of object there.
For now you cannot use this datatype in custom functions since they are "not yet part of the language", but maybe some day there will be some support for this.

Just a thought to modify soe params which can basically same output
you want to achieve. Basically here for Function i have replaced
RETURN type as TABLE TYPE which can be easilt called in Procedure abd
rest manipulations can be done.Let me know if this helps
--SQL Object creation
CREATE TYPE source_table_obj IS OBJECT
(<TABLE_ATTRIBITES DECLARATION>);
--SQL TABLE type creation
CREATE TYPE source_table_tab IS TABLE OF source_table_obj;
--Function creation with nested table type as RETURN type
CREATE OR REPLACE FUNCTION ddp_get_allitems(
source_table IN VARCHAR2)
RETURN source_table_tab
AS
src_tab source_table_tab;
BEGIN
SELECT * BULK COLLECT INTO src_tab FROM source_table;
RETURN src_tab;
END ddp_get_allitems;
-- Using Function's OUT param as an IN Param for Procedure an do all the requird processing
CREATE OR REPLACE PROCEDURE process(
source_table IN VARCHAR2,
src_tab_in IN source_table_tab)
IS
BEGIN
FOR i IN src_tab_in.FIRST..src_tab_in.LAST
LOOP
dbms_output.put_line('job processing');
END LOOP;
END process;

Related

ORACLE stored procedure - Store query result

I have the following stored procedure:
CREATE OR REPLACE PROCEDURE SP
(
query IN VARCHAR2(200),
CURSOR_ OUT SYS_REFCURSOR
)
AS
row_ PROCESSED_DATA_OBJECT;
processed PROCESSED_DATA_TABLE;
BEGIN
.....
END;
with
CREATE TYPE processed_data_obj AS OBJECT(
id INTEGER,
value FLOAT
);
/
CREATE OR REPLACE TYPE processed_data_table AS TABLE OF processed_data_obj;
/
I call the stored procedure passing the query to be executed as input parameter.
The query is something like that:
SELECT A,B FROM TABLE WHERE
where A,B and TABLE are not fixed (defined at runtime during java program execution), so I don't know their values in advance.
How could I fetch/store each returned row in my structure?
processed PROCESSED_DATA_TABLE;
Thanks
This is one way you can process a dynamically generated query into a user defined type. Note that, in order for this to work, the structure of your query (columns) must match the data type structure of your type (attributes) otherwise you're in for trouble.
CREATE TYPE processed_data_obj AS OBJECT(
ID INTEGER,
VALUE FLOAT,
constructor FUNCTION processed_data_obj RETURN self AS result
);
/
CREATE OR REPLACE TYPE BODY processed_data_obj IS
constructor FUNCTION processed_data_obj RETURN self AS result IS
BEGIN
RETURN;
END;
END;
/
CREATE OR REPLACE TYPE processed_data_table AS TABLE OF processed_data_obj;
/
CREATE OR REPLACE PROCEDURE sp (
p_query IN VARCHAR2
) AS
cursor_ sys_refcursor;
processed processed_data_table := processed_data_table();
BEGIN
OPEN cursor_ FOR p_query;
loop
processed.EXTEND;
processed(processed.count) := processed_data_obj();
fetch cursor_ INTO processed(processed.count).ID, processed(processed.count).VALUE;
exit WHEN cursor_%notfound;
dbms_output.put_line(processed(processed.count).ID||' '||processed(processed.count).VALUE);-- at this point do as you please with your data.
END loop;
CLOSE cursor_; -- always close cursor ;)
processed.TRIM; -- or processed.DELETE(processed.count);
END sp;
I noticed that, originally, you did put CURSOR_ as an output parameter in your stored procedure, if that is still your goal, you can create your procedure as:
CREATE OR REPLACE PROCEDURE sp (
p_query IN VARCHAR2,
cursor_ out sys_refcursor
) AS
processed processed_data_table := processed_data_table();
BEGIN
OPEN cursor_ FOR p_query;
loop
processed.EXTEND;
processed(processed.count) := processed_data_obj();
fetch cursor_ INTO processed(processed.count).ID, processed(processed.count).VALUE;
exit WHEN cursor_%notfound;
dbms_output.put_line(processed(processed.count).ID||' '||processed(processed.count).VALUE);-- at this point do as you please with your data.
END loop;
-- cursor remains open
processed.TRIM; -- or processed.DELETE(processed.count);
END sp;
In this case just be conscious about handling your cursor properly and always close it when you're done with it.

Can we use a table type parameter as a default null parameter in PLSQL?

I have a record type as follows,
TYPE x_Rec IS RECORD(
master_company x_tab.master_company%TYPE,
report_trans_type x_tab.report_trans_type%TYPE,
balance_version_id x_tab.balance_version_id%TYPE,
reporting_entity x_tab.reporting_entity%TYPE,
year_period_from x_tab.year_period%TYPE,
year_period_to x_tab.year_period%TYPE,
journal_id x_tab.journal_id%TYPE,
row_id x_tab.row_id%TYPE);
and I have created a table type using this record:
TYPE x_rec_tab IS TABLE OF x_Rec INDEX BY PLS_INTEGER;
I want to use this table type in a procedure as a default null parameter.
PROCEDURE x_Balance___(x_param IN NUMBER,
x_rec_ IN x_rec_tab default null)
IS
BEGIN
...My code
END;
It gives the following error message
PLS-00382: expression is of the wrong type
I resolved this by using CAST(null as /*your_type*/) in the Procedure's signature.
For instance, in your case, it will be something like this:
PROCEDURE x_Balance (x_param IN NUMBER,
x_rec_ IN x_rec_tab default cast(null as x_rec_tab))
Then, within the procedure, you just need to check if x_rec_ has elements by using the count method.
This way works for me.
You can't do that with an associative array, as that can never be null. You would get the same error if you tried to assign null to a variable of type x_rec_tab. They also don't have constructors, so you can't use an empty collection instead.
You can do this will a varray or more usefully for your situation a nested table:
create or replace package p42 as
TYPE x_Rec IS RECORD(
master_company x_tab.master_company%TYPE,
report_trans_type x_tab.report_trans_type%TYPE,
balance_version_id x_tab.balance_version_id%TYPE,
reporting_entity x_tab.reporting_entity%TYPE,
year_period_from x_tab.year_period%TYPE,
year_period_to x_tab.year_period%TYPE,
journal_id x_tab.journal_id%TYPE,
row_id x_tab.row_id%TYPE);
-- no index-by clause, so nested table not associative array
TYPE x_rec_tab IS TABLE OF x_Rec;
end p42;
/
Package P42 compiled
show errors
No errors.
create or replace package body p42 as
PROCEDURE x_Balance___(x_param IN NUMBER,
x_rec_ IN x_rec_tab default null)
IS
BEGIN
--...My code
null;
END;
PROCEDURE dummy IS
l_rec_tab x_rec_tab;
BEGIN
l_rec_tab := null;
END;
end p42;
/
Package Body P42 compiled
show errors;
No errors.
You could also default to an empty collection instead:
PROCEDURE x_Balance___(x_param IN NUMBER,
x_rec_ IN x_rec_tab default x_rec_tab())
IS
...
That doesn't really help you much if you have other code that relies on the type being an associative array of course.
Old question but still might be helpful.
You can create a function:
function empty_tab
return x_rec_tab
as
l_tab x_rec_tab;
begin
return l_tab;
end empty_tab;
This way you can (notice that empty_tab is used as default parameter):
PROCEDURE x_Balance___(x_param IN NUMBER,
x_rec_ IN x_rec_tab default empty_tab)
IS
BEGIN
...My code
END;
This is a repeat of #ManuelPerez answer, but I just feel that it could have been explained better.
Create this procedure, casting your optional variable to your datatype like this:
CREATE OR REPLACE PROCEDURE Test_Procedure (
txt_ IN VARCHAR2,
col_formats_ IN dbms_sql.varchar2a DEFAULT cast(null as dbms_sql.varchar2a) )
IS BEGIN
Dbms_Output.Put_Line (txt_);
FOR i_ IN 1 .. 10 LOOP
IF col_formats_.EXISTS(i_) THEN
Dbms_Output.Put_Line (i_ || ' Exists');
ELSE
Dbms_Output.Put_Line (i_ || ' DOES NOT Exist');
END IF;
END LOOP;
END Test_Procedure;
The reason this beats the accepted answer is that it doesn't require you to change the datatype of the incoming variable. Depending on your circumstance, you may not have the flexibility to do that.
Now call your procedure like this if you have a variable to feed the procedure:
DECLARE
txt_ VARCHAR2(100) := 'dummy';
arr_ dbms_sql.varchar2a;
BEGIN
arr_(4) := 'another dummy';
Test_Procedure (txt_, arr_);
END;
Or like this if you don't:
DECLARE
txt_ VARCHAR2(100) := 'dummy';
BEGIN
Test_Procedure (txt_);
END;
Your output will look something like this:
dummy
1 DOES NOT Exist
2 DOES NOT Exist
3 DOES NOT Exist
4 Exists
5 DOES NOT Exist
6 DOES NOT Exist
7 DOES NOT Exist
8 DOES NOT Exist
9 DOES NOT Exist
10 DOES NOT Exist

Oracle PL/SQL Developer: Return %RowType from Package Procedure

i'm kind of new to Oracle Pl\SQL. I was just trying to create a simple Package with a procedure that returns a set of object id's; the code is as follows:
--Package Spec
CREATE OR REPLACE PACKAGE TEST IS
--GET OBJECT ID'S FROM CONTROL TABLE
PROCEDURE get_object_id_control(p_obj_id OUT abc_table%ROWTYPE);
END;
--Package Body
PROCEDURE get_object_id_control(p_obj_id OUT abc_table%ROWTYPE) AS
BEGIN
SELECT object_id
INTO p_obj_id
FROM abc_table
WHERE fec_proc IS NULL;
END;
I get Error: PL/SQL: ORA-00913: too many values. Is this the correct way for returning multiple values of same data type, or is there a better approach. Thanks in advance.
You can create a custom table type and set the out parameter of the procedure to that type.
CREATE TABLE ABC_TABLE(ID varchar2(100));
create or replace type abc_tab is table of varchar2(100);
/
CREATE OR REPLACE PACKAGE TEST IS
PROCEDURE get_object_id_control(p_obj_id OUT abc_tab);
END;
/
CREATE OR REPLACE PACKAGE BODY TEST IS
PROCEDURE get_object_id_control(p_obj_id OUT abc_tab) AS
BEGIN
SELECT id
bulk collect INTO p_obj_id
FROM abc_table;
END;
END;
/
Then you can call it like so:
declare
v abc_tab;
begin
TEST.get_object_id_control(p_obj_id => v);
for i in v.first..v.last loop
dbms_output.put_line(v(i));
end loop;
end;
/
Similar to GurV's answer (since he beat me by like 30 seconds...), you can use a PL/SQL object type as well. You do not need the CREATE TYPE statement if you don't need to reference the type in SQL.
--Package Spec
CREATE OR REPLACE PACKAGE TEST AS
TYPE id_table_type IS TABLE OF NUMBER;
--GET OBJECT ID'S FROM CONTROL TABLE
PROCEDURE get_object_id_control(p_obj_id_list OUT id_table_type);
END;
--Package Body
CREATE OR REPLACE PACKAGE BODY TEST AS
PROCEDURE get_object_id_control(p_obj_id_list OUT id_table_type) AS
BEGIN
SELECT object_id
BULK COLLECT INTO p_obj_id_list
FROM abc_table
WHERE fec_proc IS NULL;
END;
END;
To use it:
DECLARE
l_id_list test.id_table_type;
BEGIN
test.get_object_id_control (p_obj_id_list => l_id_list);
FOR i IN l_id_list.FIRST .. l_id_list.LAST LOOP
DBMS_OUTPUT.put_line (l_id_list (i));
END LOOP;
END;

Object not exists error while using TABLE expression

Here is my code: Quite Straight forward..
create or replace package types
as
type rec is record
(
employee_id NUMBER,
fname varchar2(20)
);
type tab_rec is table of rec;
type tab_numbers is table of number;
type tab_chars is table of varchar2(10);
end types;
/
create or replace
function get_employees_rec
(
O_error_msg IN OUT varchar2,
L_access_tab OUT types.tab_chars
)
return boolean
as
--o_access_tab types.tab_chars;
cursor c_rec is
select first_name from employees;
begin
open c_rec;
fetch c_rec bulk collect into L_access_tab;
close c_rec;
return true;
exception
when others then
O_error_msg:=substr(sqlerrm,1,100);
return false;
end;
/
declare
O_error_msg varchar2(100);
L_access types.tab_chars;
begin
if get_employees_rec(O_error_msg,L_access)=FALSE then
dbms_output.put_line('Got you');
end if;
for rec in(select * from employees e,TABLE(L_access) f where value(f)=e.first_name)
loop
dbms_output.put_line(rec.first_name);
end loop;
end;
/
However I am getting the error :
ORA-21700: object does not exist or is marked for delete
ORA-06512: at line 9
21700. 00000 - "object does not exist or is marked for delete"
*Cause: User attempted to perform an inappropriate operation to
an object that is non-existent or marked for delete.
Operations such as pinning, deleting and updating cannot be
applied to an object that is non-existent or marked for delete.
*Action: User needs to re-initialize the reference to reference an
existent object or the user needs to unmark the object.
What is the reason behind this error?
You can't access those types from an external package like that, rather create them as database objects:
create or replace type rec is object (
employee_id NUMBER,
fname varchar2(20));
create or replace type tab_rec is table of rec;
etc.

Oracle SQL Developer PL/SQL return an array

Devs,
I've searched everywhere I can, but I could not find solution to this simple problem.
Situation:
I need to write a procedure where it takes a column name as the input and return all the distinct values present in that column as output. And then I have to use this procedure in some c# code.
In MS server, it is very easy as it will directly give the set of results unlike in PL/SQL.
Script I could write (which is not giving me the result I need):
CREATE OR REPLACE
PROCEDURE GetCol(PARAM IN STRING, recordset OUT sys_refcursor)
AS
BEGIN
OPEN recordset FOR
SELECT DISTINCT(PARAM)
FROM my_table;
END
;
When I try to check the data in the recordset using this code:
DECLARE
l_cursor SYS_REFCURSOR;
l_sname VARCHAR2(50);
BEGIN
GetCol('col_name',l_cursor);
LOOP
FETCH l_cursor INTO l_sname;
EXIT WHEN l_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(l_sname);
END LOOP;
CLOSE
Can someone help me with this code please.
You can also open a ref_cursor for a string value. Please take a look at this:
CREATE OR REPLACE PROCEDURE GetCol(PARAM IN VARCHAR2, recordset OUT sys_refcursor)
AS
QRY varchar2(100);
BEGIN
QRY := 'SELECT DISTINCT '||PARAM||' FROM my_table';
OPEN recordset FOR QRY;
END;
Then:
DECLARE
l_cursor SYS_REFCURSOR;
l_sname VARCHAR2(50);
BEGIN
GetCol('col_name',l_cursor);
LOOP
FETCH l_cursor INTO l_sname;
EXIT WHEN l_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(l_sname);
END LOOP;
END;
Your problem is caused by ambiguity about what PARAM is in the procedure's SELECT statement:
CREATE OR REPLACE
PROCEDURE GetCol(PARAM IN STRING, recordset OUT sys_refcursor)
AS
BEGIN
OPEN recordset FOR
SELECT DISTINCT(PARAM) -- Ambiguity here
FROM my_table;
END;
Does PARAM refer to the table column or to the first parameter of the procedure? Oracle has assumed the parameter. You can explicitly say which like this:
SELECT DISTINCT(my_table.PARAM)
FROM my_table;
You could if appropriate (it probably isn't here) specify the procedure parameter instead:
SELECT DISTINCT(GetCol.PARAM)
FROM my_table;
Generally this is best avoided by:
always using table aliases in column references select statements, and
having a standard for parameter names that makes them less likely to clash e.g. P_PARAM.

Resources