Plsql Uninitialized collections - oracle

I have the following types defined into a spec of a package
type column_info is record (col_name varchar2(20), col_value varchar2(1000));
type c_info is varray(10) of column_info;
type table_info is record (table_name varchar2(20), col_info c_info);
In declaration part package body I have
t_info table_info;
Inside of a procedure in the body of package I have
t_info:=null;
t_info.table_name:='gl_temp_report1';
t_info.col_info(1).col_name:='table_idx';
t_info.col_info.extend;
t_info.col_info(2).col_name:='table_row';
t_info.col_info.extend;
t_info.col_info(3).col_name:='table_row_detail';
Even package compile succesfully , at runtime I get the exception ORA-06531: Reference to uninitialized collection .
How I initialize col_info collection ?
I tried to initialize t_info.col_info() but I get "There is no function" like this one . TIA, Aurel

You should initialize all collections (including nested) properly before accessing them.
They are atomically nulls before initialization.
t_info := table_info('gl_temp_report1', c_info());
You also must call extend before assigning a value for each varray element (or extend once with extend(3)).
Or do it all in one statement:
t_info := table_info('gl_temp_report1', c_info('table_idx','table_row','table_row_detail'));

To perform initialization you'll need to add an initialization block to the package body, in a manner similar to the following:
CREATE OR REPLACE PACKAGE BODY your_package IS
t_info table_info;
-- Whatever other procedure definitions, etc, are needed
BEGIN -- package initialization
t_info.table_name:='gl_temp_report1';
t_info.col_info := c_info();
t_info.col_info.extend;
t_info.col_info(1).col_name:='table_idx';
t_info.col_info.extend;
t_info.col_info(2).col_name:='table_row';
t_info.col_info.extend;
t_info.col_info(3).col_name:='table_row_detail';
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Exception!'); -- Add whatever error handling is needed
END your_package;
Share and enjoy.

You can create t_info as shown:
declare
type column_info is record (col_name varchar2(20), col_value varchar2(1000));
type c_info is varray(10) of column_info;
type table_info is record (table_name varchar2(20), col_info c_info);
t_info table_info;
begin
t_info.table_name := null;
t_info.col_info := c_info();
for i in 1..10 loop
t_info.col_info.extend;
t_info.col_info(i).col_name := null;
t_info.col_info(i).col_value := null;
end loop;
end;
/
Cheers!

Related

ORA-06530: Reference to uninitialized composite - Similar questions reviewed but still unclear

I'm getting this error:
Error while executing the procedure : ORA-06530: Reference to uninitialized composite
This is probably a common error for Oracle beginners like me. I looked at similar posts but can't figure out how to apply the answers to my code, which follows.
I have a table of:
CREATE OR REPLACE TYPE "FDS_APPS"."TY_AUDIT_COL_TBL" IS
TABLE OF fds_apps.ty_audit_col_obj;
of this TYPE
CREATE OR REPLACE TYPE "FDS_APPS"."TY_AUDIT_COL_OBJ" AS OBJECT (
application VARCHAR2(30),
module VARCHAR2(30),
created_by VARCHAR2(50),
creation_date DATE
);
I want to create a a test procedure to call the following procedure while passing in the input values with focus, of course on the p_audit_col parameter of type ty_audit_col_tbl.
PROCEDURE check_mv_status (
o_outcome_type OUT VARCHAR2,
p_audit_col IN fds_apps.ty_audit_col_tbl,
p_refresh_ind IN CHAR DEFAULT 'N',
p_mv_result OUT NOCOPY fds_apps.ty_result_tbl
) AS...
Here is my calling procedure:
CREATE OR REPLACE PROCEDURE FDS_APPS.JUNKPROC2
AS
O_OUTCOME_TYPE VARCHAR2(32767);
P_AUDIT_COL TY_AUDIT_COL_TBL;
P_REFRESH_IND CHAR;
P_MV_RESULT TY_RESULT_TBL;
BEGIN
P_AUDIT_COL := fds_apps.TY_AUDIT_COL_TBL();
P_AUDIT_COL.extend(1);
--Error occurs on the following line
P_AUDIT_COL(1).application := 'App';
P_AUDIT_COL(1).module := 'Module';
P_AUDIT_COL(1).created_by := 'MyID';
P_REFRESH_IND := 'N';
FIRM_RTBI_PKG.CHECK_MV_STATUS(O_OUTCOME_TYPE, P_AUDIT_COL, P_REFRESH_IND, P_MV_RESULT);
dbms_output.put_line('O_OUTCOME_TYPE=' || O_OUTCOME_TYPE);
END;
/
I get the error where indicated as a comment, when I try to assign a value to an element in the 1 record collection.
How can I overcome this error?
The syntax you are showing is for populating RECORDS, which is different than populating an OBJECT type.
Replace this:
P_AUDIT_COL(1).application := 'App';
P_AUDIT_COL(1).module := 'Module';
P_AUDIT_COL(1).created_by := 'MyID';
with this:
P_AUDIT_COL(1) := TY_AUDIT_COL_OBJ('App','Module','MyID',sysdate);
An object type variable needs to be initialized with a constructor

Type validation within stored procedures - oracle

How does oracle stored procedures handle two different types with the same exact type declaration only that they are stored in two different packages?
Example:
package1 has type,
Type myType Is Table Of VARCHAR2(40) Index By Binary_Integer;
package2 has type,
Type myType Is Table Of VARCHAR2(40) Index By Binary_Integer;
Now if I use a stored procedure (parameter => myType) stored in package2 and make the call in package1 (input => myType), this throws me an error
PLS-00306: wrong number or types of arguments in call to
Does oracle handle these as different with no internal check for whether both types are 'table of varchar(40)'?
Also what's the best way I can make package1 myType be referencing package2 myType. Subtypes? or is there a way to do it directly?
Thanks
It does not matter if the type signatures are identical, package1.mytype will never be the same type as package2.mytype as they are declared in two different places so they are not identical.
If you want to pass types between packages then only declare the type in one place and use it in all the different packages.
For example:
CREATE PACKAGE package1
AS
Type myType Is Table Of VARCHAR2(40) Index By Binary_Integer;
PROCEDURE proc(
value IN package1.mytype
);
END;
/
CREATE PACKAGE BODY package1
AS
PROCEDURE proc(
value IN package1.mytype
)
IS
i PLS_INTEGER;
BEGIN
DBMS_OUTPUT.PUT_LINE('Package 1:');
i := value.FIRST;
WHILE i IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE( i || ': ' || value(i) );
i := value.NEXT(i);
END LOOP;
END;
END;
/
Do not declare the type in package2, use the type from package1:
CREATE PACKAGE package2
AS
PROCEDURE proc(
value IN package1.mytype
);
END;
/
CREATE PACKAGE BODY package2
AS
PROCEDURE proc(
value IN package1.mytype
)
IS
i PLS_INTEGER;
BEGIN
DBMS_OUTPUT.PUT_LINE('Package 2:');
i := value.FIRST;
WHILE i IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE( i || ': ' || value(i) );
i := value.NEXT(i);
END LOOP;
END;
END;
/
Then:
DECLARE
value PACKAGE1.MYTYPE;
BEGIN
value(2) := 'ABC';
value(4) := 'DEF';
value(5) := 'GHI';
value(9) := 'JKL';
PACKAGE1.PROC(value);
PACKAGE2.PROC(value);
END;
/
Outputs:
Package 1:
2: ABC
4: DEF
5: GHI
9: JKL
Package 2:
2: ABC
4: DEF
5: GHI
9: JKL
db<>fiddle here
Does oracle handle these as different with no internal check for whether both types are 'table of varchar(40)'?
Yes, package1 != package2 therefore package1.mytype != package2.mytype regardless of whether the signature of package1.mytype is identical to the signature of package2.mytype.
what's the best way I can make package1.myType be referencing package2.myType. Subtypes? or is there a way to do it directly?
It will not work. Just create a single type and use that throughout all the packages.

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

How to pass an array type in plsql package specification?

I am new to plsql. I am trying to put two scripts under the same package. These scripts deal with arrays. How do I pass an array into the procedure? If I am to declare the array, do I do it in the specification or the body? I am trying this right now but it doesn't work.
CREATE PACKAGE cop_cow_script AS
PROCEDURE COP_COW_DATALOAD_V2(arr_claims VARRAY(15000) OF VARCHAR2(10), arr_sql VARRAY(500) OF VARCHAR2(1000));
END cop_cow_script;
As you see I want to pass in those two arrays.
You need to declare types in the package specification and use them as parameter types in the procedure declaration:
CREATE OR REPLACE PACKAGE cop_cow_script AS
TYPE arr_claims_t IS VARRAY(15000) OF VARCHAR2(10);
TYPE arr_sql_t IS VARRAY(500) OF VARCHAR2(1000);
PROCEDURE COP_COW_DATALOAD_V2(arr_claims arr_claims_t, arr_sql arr_sql_t);
END cop_cow_script;
/
EDIT - below is an example of the package body - the procedure loops through elements of it's first parameter and prints them using DBMS_OUTPUT.PUT_LINE
PROCEDURE COP_COW_DATALOAD_V2(arr_claims arr_claims_t, arr_sql arr_sql_t)
IS
BEGIN
FOR i IN arr_claims.FIRST .. arr_claims.LAST
LOOP
DBMS_OUTPUT.PUT_LINE( arr_claims( i ) );
END LOOP;
END;
END cop_cow_script;
/
An then you can initialize them and pass to the procedure invocation for example in this way (this is an anonymous block that declares two variables, initializes them and invokes the procedure passing both parameters to it:
DECLARE
my_array1 cop_cow_script.arr_claims_t := cop_cow_script.arr_claims_t();
my_array2 cop_cow_script.arr_sql_t := cop_cow_script.arr_sql_t();
BEGIN
my_array1.extend;
my_array1( 1 ) := 'string 1';
my_array2.extend;
my_array2( 1 ) := 'string 2';
cop_cow_script.COP_COW_DATALOAD_V2( my_array1, my_array2 );
END;
/
First off, I'm hard-pressed to imagine a situation where you'd actually want to use a PL/SQL varray rather than a nested table or an associative array. There really isn't a benefit to declaring a fixed size collection.
Whatever sort of collection type you want, you'd need to declare the collection type either in the package specification or at the SQL level (depending on the type of collection) separate from the procedure specification
CREATE PACKAGE cop_cow_script AS
TYPE arr_claims IS varray(15000) of varchar(10);
TYPE arr_sql IS varray(500) of varchar(1000);
PROCEDURE COP_COW_DATALOAD_V2(p_claims arr_claims,
p_sql arr_sql);
END cop_cow_script;

Oracle Pl/SQL: How to implement pointers to in-memory record types

I have two packages pkg_company and pkg_employee. pkg_company includes a (record) type definition typ_company. pkg_employee includes a type definition typ_employee.
I have one instance of company type and 1000s of instances of typ_employee. In the employee type, I would like to have a pointer to the company instance.
declare
c pkg_company.typ_company;
e pkg_employee.typ_employee;
begin
c := pkg_company.get(12345);
for e in (select employeeid from employee where companyid=12345)
loop
e := pkg_employee.get(12345, c); -- c passed by reference in out nocopy
pkg_employee.process(e); -- I would like to access company info inside here as e.c.companyname
end loop;
end;
How do I store a pointer to c inside e? I don't want to create 1000s of copies of c. Just want to store the pointer and access the value when needed.
Thanks for all the help!
end;
In Oracle SQL it's possible to use REF and DEREF functions to pass logical pointers to objects stored in database tables.
But there are no such thing as pointer or reference in PL/SQL so you need a workaround.
Below some approaches for possible workarounds in PL/SQL. I apologize in advance for any errors in the code. It's intended rather to demonstrate approaches than for use in production.
Approach 1 - cached access method
Generic idea is to access entities through function which cache results:
create or replace package EntitytAccess as
procedure GetCompany1(
pCompanyId in number,
pCompany out nocopy pkg_company.typ_company
);
function GetCompany2(
pCompanyId in number
) return pkg_company.typ_company;
procedure ClearCompanyCache;
end;
Access package body:
create or replace package body EntitytAccess as
type typ_company_cache is table of pkg_company.typ_company index by number;
var_company_cache typ_company_cache;
procedure GetCompany1(
pCompanyId in number,
pCompany out nocopy pkg_company.typ_company
)
is
begin
if( var_company_cache.exists(pCompanyId) )
pCompany := var_company_cache(pCompanyId);
else
pCompany := pkg_company.get(pCompanyId);
var_company_cache(pCompanyId) := pCompany;
end if;
end;
function GetCompany2(
pCompanyId in number
) return pkg_company.typ_company
is
begin
if(not var_company_cache.exists(pCompanyId) )
var_company_cache(pCompanyId) := pkg_company.get(pCompanyId);
end if;
return var_company_cache(pCompanyId);
end;
procedure ClearCompanyCache
is
begin
var_company_cache.Delete;
end;
end;
Usage:
declare
c pkg_company.typ_company;
e pkg_employee.typ_employee;
begin
EntityAccess.GetCompany2(12345,c);
for e in (select employeeid from employee where companyid=12345)
loop
e := pkg_employee.get(12345, c); -- c passed by reference in out nocopy
-- and c.companyid stored inside e.
pkg_employee.process(e); -- No need to pass company, but inside process()
-- method you need to call either
-- EntityAccess.GetCompany1(e.companyid, var_c)
-- or
-- var_c := EntityAccess.GetCompany2(e.companyid)
end loop;
end;
Approach 2 - environment package
Because package state belongs to one session you can use package variables to hold current processing state and reference it when needed.
create or replace package ProcessEnvironment as
var_current_company pkg_company.typ_company;
-- may be more "global" variables here
end;
/
create or replace package body ProcessEnvironment as
end;
Usage:
declare
e pkg_employee.typ_employee;
begin
ProcessEnvironmant.var_current_company := pkg_company.get(12345);
for e in (select employeeid from employee where companyid=12345)
loop
e := pkg_employee.get(12345, ProcessEnvironmant.var_current_company);
-- c passed by reference in out nocopy
-- and c.companyid stored inside e.
pkg_employee.process(e); -- No need to pass company, but inside process()
-- method you references
-- ProcessEnvironmant.var_current_company
-- with appropriate checks
end loop;
end;
Mixed approach
Seems that in case of Approach 1 instances copied from collection, especially if accessed with function GetCompany2(). Copy constructor may be fast enough but produce some overhead.
For Approach 2 there are some checks must be present in code of business logic function so it's maintenance overhead just from another point of view.
To deal with both problems you can use cache, but hold only one value inside a package:
create or replace package EntitytAccess as
procedure GetCompany(
pCompanyId in number,
pCompany out nocopy pkg_company.typ_company
);
end;
Package body:
create or replace package body EntitytAccess as
var_cached_company pkg_company.typ_company;
procedure GetCompany(
pCompanyId in number,
pCompany out nocopy pkg_company.typ_company
)
is
begin
if( (var_cached_company is null)
or
(var_cached_company.comanyid != pCompanyId)
)then
var_company_cache := pkg_company.get(pCompanyId);
end if;
pCompany := var_cached_company;
end;
end;
Usage:
declare
c pkg_company.typ_company;
e pkg_employee.typ_employee;
begin
EntityAccess.GetCompany(12345,c);
for e in (select employeeid from employee where companyid= c.companyid)
loop
e := pkg_employee.get(c.companyid , c); -- c passed by reference in out nocopy
-- and c.companyid stored inside e.
pkg_employee.process(e); -- No need to pass company, but inside process()
-- method you need to call
-- EntityAccess.GetCompany(e.companyid, var_c)
-- where var_c is company object variable.
end loop;
end;
How do I store a pointer to c inside e? I don't want to create 1000s
of copies of c. Just want to store the pointer and access the value
when needed.
"pkg_employee.get" is a function - remove OUT parameters.
It is bad practice to define OUT parameters for a function.
Why is it out parameter if you work only with the function's result?
If you call the same function over and over again and the function is deterministic as performance boost you can use function's "RESULT_CACHE" clause. Please read about it first to be sure that this is what you need.

Resources