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.
Related
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.
FYI: Oracle 12c
I have created a custom type called Payeezy_Error:
create or replace TYPE PAYEEZY_ERROR
AS
OBJECT (
CODE VARCHAR(30),
DESCRIPTION VARCHAR(200)
);
And then in turn created a Table type of Payeezy_Errors:
create or replace TYPE PAYEEZY_ERRORS AS TABLE OF PAYEEZY_ERROR;
I then have a Procedure that takes Payeezy_Errors as an IN parameter:
create or replace PROCEDURE SAVE_USER_PAYMENT_TRANSACTION
(
in_AccountID IN VARCHAR2,
in_SequenceID IN VARCHAR2,
in_CorrelationID IN VARCHAR2,
in_TransactionID IN VARCHAR2,
in_TransactionTag IN VARCHAR2,
in_Currency IN VARCHAR2,
in_TransactionType IN VARCHAR2,
in_BankResponse IN VARCHAR2,
in_GatewayResponse IN VARCHAR2,
in_ValidationStatus IN VARCHAR2,
in_TransactionStatus IN VARCHAR2,
in_Errors IN PAYEEZY_ERRORS
)
AS
var_uptID NUMBER;
var_ErrorCount NUMBER := 0;
EX_AUTHENTICATION EXCEPTION;
BEGIN
-- Insert the Payeezy Response values tied to the user
INSERT INTO
USER_PAYMENT_TRANSACTION (
ACCOUNT_ID, UP_PAYMENT_SEQ_ID, CORRELATION_ID, TRANSACTION_ID,
TRANSACTION_TAG, CURRENCY, TRANSACTION_TYPE, BANK_RESPONSE,
GATEWAY_RESPONSE, VALIDATION_STATUS, TRANSACTION_STATUS
) VALUES (
in_AccountID, in_SequenceID, in_CorrelationID, in_TransactionID,
in_TransactionTag, in_Currency, in_TransactionType, in_BankResponse,
in_GatewayResponse, in_ValidationStatus, in_TransactionStatus
)
RETURNING
ID
INTO
var_uptID;
-- Insert any errors that may be associated with a failure/unsuccessful transaction
SELECT
COUNT(*)
INTO
var_ErrorCount
FROM
in_Errors;
IF (var_ErrorCount > 0) THEN
INSERT INTO
USER_PAYMENT_TRANSACTION_ERROR (
UPT_ID, CODE, DESCRIPTION
)
SELECT
var_uptID, e.CODE, e.DESCRIPTION
FROM
in_Errors;
END IF;
-- Exception Handling
EXCEPTION
WHEN EX_AUTHENTICATION THEN
raise_application_error(-20001, 'Authentication Failed.');
END SAVE_USER_PAYMENT_TRANSACTION;
When I compile the procedure it yells at me at the SELECT COUNT(*) statement that:
ORA-00942: table or view does not exist.
And red-dashes are under SELECT and in_Errors.
Further down the procedure I get the same error and the second INSERT INTO and in_Errors lines are red-dashes too.
I have exited and reloaded Oracle SQL Developer just to see if it was a caching thing. I have searched around the web but have not found my particular case.
If you want to use the table in a query, you'd need to use the table operator
SELECT
COUNT(*)
INTO
var_ErrorCount
FROM
table( in_Errors );
That works. But it means that you're taking all of the data that you have in the PL/SQL collection, moving it to the SQL VM, doing the aggregation, and then returning the result to PL/SQL. It would likely be more efficient (and less code) in this case to just do
var_ErrorCount := in_Errors.count;
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;
I declared table type and set a value in it with using loop. I am having an error while I was casting this t_table
DECLARE
TYPE t_row IS RECORD
(
id NUMBER,
description VARCHAR2(50)
);
TYPE t_table IS TABLE OF t_row;
l_tab t_table := t_table();
BEGIN
FOR i IN 1 .. 10 LOOP
l_tab.extend();
l_tab(l_tab.last).id := i;
l_tab(l_tab.last).description := 'Description for ' || i;
END LOOP;
SELECT * from TABLE(CAST(l_tab AS t_table));
END
Best regards
Why do you want to do a select onto the the type? You would use the the TABLE() and the CAST rather if you have a collection in a column stored in a table.
You could just loop through the table in your code. Example:
for i in l_tab.first .. l_tab.last
loop
dbms_output.put_line(l_tab(i).id||' '||l_tab(i).description);
end loop;
Since l_tab is of type t_table, there's no need for the cast. But that's not your problem.
Your problem is that you're trying to reference a PL/SQL type in SQL, which you simply can't do. You can either remove the select as #hol suggested or make the type a database object (which will allow SQL to access it):
CREATE OR REPLACE TYPE t_row AS OBJECT
(
id NUMBER,
description VARCHAR2 (50)
);
CREATE OR REPLACE TYPE t_table AS TABLE OF t_row;
DECLARE
l_tab t_table := t_table ();
BEGIN
FOR i IN 1 .. 10 LOOP
l_tab.EXTEND ();
l_tab (l_tab.LAST) := t_row (i, 'Description for ' || i);
END LOOP;
FOR r IN (SELECT * FROM TABLE (l_tab)) LOOP
DBMS_OUTPUT.put_line (r.id);
END LOOP;
END;
There is a second problem with the initial code, in that you are running a select without telling the code what to do with it. Unlike some other procedural SQL extensions, PL/SQL does not allow you to implicitly return a handle to a resultset (prior to 12c). You must either handle it directly or explicitly return a ref_cursor that points to it. The code above has been update to primitively handle the result of the query.
I have a package which declares a collection of type table of some database table's %rowtype. It also declares a function to populate the package-level variable with some data. I can now print the data with dbms_output, seems fine.
But when I use the package-level variable in some sql I get the following error:
ORA-21700: object does not exist or is marked for delete
ORA-06512: at "TESTDB.SESSIONGLOBALS", line 17
ORA-06512: at line 5
Here is my code:
create some dummy data:
drop table "TESTDATA";
/
CREATE TABLE "TESTDATA"
( "ID" NUMBER NOT NULL ENABLE,
"NAME" VARCHAR2(20 BYTE),
"STATUS" VARCHAR2(20 BYTE)
);
/
insert into "TESTDATA" (id, name, status) values (1, 'Hans Wurst', 'J');
insert into "TESTDATA" (id, name, status) values (2, 'Hans-Werner', 'N');
insert into "TESTDATA" (id, name, status) values (3, 'Hildegard v. Bingen', 'J');
/
now create the package:
CREATE OR REPLACE
PACKAGE SESSIONGLOBALS AS
type t_testdata is table of testdata%rowtype;
v_data t_testdata := t_testdata();
function load_testdata return t_testdata;
END SESSIONGLOBALS;
and the package body:
CREATE OR REPLACE
PACKAGE BODY SESSIONGLOBALS AS
function load_testdata return t_testdata AS
v_sql varchar2(500);
BEGIN
if SESSIONGLOBALS.v_data.count = 0
then
v_sql := 'select * from testdata';
execute immediate v_sql
bulk collect into SESSIONGLOBALS.v_data;
dbms_output.put_line('data count:');
dbms_output.put_line(SESSIONGLOBALS.v_data.count);
end if; -- SESSIONGLOBALS.v_data.count = 0
-- ******************************
-- this line throws the error
insert into testdata select * from table(SESSIONGLOBALS.v_data);
-- ******************************
return SESSIONGLOBALS.v_data;
END load_testdata;
END SESSIONGLOBALS;
execute the sample:
DECLARE
v_Return SESSIONGLOBALS.T_TESTDATA;
BEGIN
v_Return := SESSIONGLOBALS.LOAD_TESTDATA();
dbms_output.put_line('data count (direct access):');
dbms_output.put_line(SESSIONGLOBALS.v_data.count);
dbms_output.put_line('data count (return value of function):');
dbms_output.put_line(v_Return.count);
END;
If the line marked above is commented out i get the expected result.
So can anyone tell me why the exception stated above occurs?
BTW: it is absolutely nessecary for me to execute the statement which populates the collection with data as dynamic sql because the tablename is not known at compiletime. (v_sql := 'select * from testdata';)
the solution is to use pipelined functions in the package
see: http://docs.oracle.com/cd/B19306_01/appdev.102/b14289/dcitblfns.htm#CHDJEGHC ( => section Pipelining Between PL/SQL Table Functions does the trick).
my package looks like this now (please take the table script from my question):
create or replace
PACKAGE SESSIONGLOBALS AS
v_force_refresh boolean;
function set_force_refresh return boolean;
type t_testdata is table of testdata%rowtype;
v_data t_testdata;
function load_testdata return t_testdata;
function get_testdata return t_testdata pipelined;
END SESSIONGLOBALS;
/
create or replace
PACKAGE BODY SESSIONGLOBALS AS
function set_force_refresh return boolean as
begin
SESSIONGLOBALS.v_force_refresh := true;
return true;
end set_force_refresh;
function load_testdata return t_testdata AS
v_sql varchar2(500);
v_i number(10);
BEGIN
if SESSIONGLOBALS.v_data is null then
SESSIONGLOBALS.v_data := SESSIONGLOBALS.t_testdata();
end if;
if SESSIONGLOBALS.v_force_refresh = true then
SESSIONGLOBALS.v_data.delete;
end if;
if SESSIONGLOBALS.v_data.count = 0
then
v_sql := 'select * from testdata';
execute immediate v_sql
bulk collect into SESSIONGLOBALS.v_data;
end if; -- SESSIONGLOBALS.v_data.count = 0
return SESSIONGLOBALS.v_data;
END load_testdata;
function get_testdata return t_testdata pipelined AS
v_local_data SESSIONGLOBALS.t_testdata := SESSIONGLOBALS.load_testdata();
begin
if v_local_data.count > 0 then
for i in v_local_data.first .. v_local_data.last
loop
pipe row(v_local_data(i));
end loop;
end if;
end get_testdata;
END SESSIONGLOBALS;
/
now i can do a select in sql like this:
select * from table(SESSIONGLOBALS.get_testdata());
and my data collection is only populated once.
nevertheless it is quite not comparable with a simple
select * from testdata;
from a performace point of view but i'll try out this concept for some more complicated use cases. the goal is to avoid doing some really huge select statements involving lots of tables distributed among several schemas (english plural for schema...?).
The syntax you use does not work:
insert into testdata select * from table(SESSIONGLOBALS.v_data); -- does not work
You have to use something like that:
forall i in 1..v_data.count
INSERT INTO testdata VALUES (SESSIONGLOBALS.v_data(i).id,
SESSIONGLOBALS.v_data(i).name,
SESSIONGLOBALS.v_data(i).status);
(which actually duplicates the rows in the table)
Package-level types cannot be used in SQL. Even if your SQL is called from within a package, it still can't see that package's types.
I'm not sure how you got that error message, when I compiled the package I got this error, which gives a good hint at the problem:
PLS-00642: local collection types not allowed in SQL statements
To fix this problem, create a type and a nested table of that type:
create or replace type t_testdata_rec is object
(
"ID" NUMBER,
"NAME" VARCHAR2(20 BYTE),
"STATUS" VARCHAR2(20 BYTE)
);
create or replace type t_testdata as table of t_testdata_rec;
/
The dynamic SQL to populate the package variable gets more complicated:
execute immediate
'select cast(collect(t_testdata_rec(id, name, status)) as t_testdata)
from testdata ' into SESSIONGLOBALS.v_data;
But now the insert will work as-is.