ORA-00904: "PREV_VAL_R": invalid identifier - oracle

I am trying to bulk collect values into a collection. I'm getting ORA-00904: "PREV_VAL_R" invalid identifier. The code is simplified.
PROCEDURE MY_HELPER() IS
----------------------------------------------------------------
TYPE PREV_VAL_R IS RECORD(
DESC1 VARCHAR2(250),
MONTH_VAL VARCHAR2(250),
MONTH_ALL VARCHAR2(250));
TYPE PREV_VAL_T IS TABLE OF PREV_VAL_R;
BEGIN
SELECT PREV_VAL_R(DESC1, MONTH_VAL, MONTH_ALL)
BULK COLLECT INTO PREV_VAL_T
FROM MY_TABLE;
END MY_HELPER;
What am I doing wrong?
Thanks!

You are calling PREV_VAL_R() as an object constructor, but that is a PL/SQL record type, not something known at SQL level.
You can bulk collect the columns straight into your table of records; though it needs to be an instance of that collection type, not the type itself, e.g.:
PROCEDURE MY_HELPER() IS
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
TYPE PREV_VAL_R IS RECORD(
DESC1 VARCHAR2(250),
MONTH_VAL VARCHAR2(250),
MONTH_ALL VARCHAR(250));
TYPE PREV_VAL_T IS TABLE OF PREV_VAL_R;
L_PREV_VAL_T PREV_VAL_T;
BEGIN
SELECT DESC1, MONTH_VAL, MONTH_ALL
BULK COLLECT INTO L_PREV_VAL_T
FROM MY_TABLE;
END MY_HELPER;
I've added L_PREV_VAL_T PREV_VAL_T; to declare the instance of the collection, and the the INTO PREV_VAL_T to INTO L_PREV_VAL_T to target that instance. (You can/should use a more meaningful name, of course...)
db<>fiddle

Type prev_val_r is a plain old PL/SQL record type, not an object type with a constructor. You can just list the items, without any constructor.
Also, prev_val_t is a type, so you still need a variable of that type. I've named mine t in the example below:
create or replace procedure my_helper
as
type prev_val_r is record(
desc1 varchar2(250)
,month_val varchar2(250)
,month_all varchar2(250));
type prev_val_t is table of prev_val_r;
t prev_val_t;
begin
select 'x', 'y', 'z' bulk collect into t from dual;
end my_helper;
21c's Qualified Expressions for PL/SQL record types are similar to object constructors, so you could write for example
r prev_val_r := prev_val_r('x', 'y', 'z');
but they are for PL/SQL only and not SQL. Presumably the PL/SQL compiler builds the code for you internally so it's just syntactic sugar, and that's why Oracle are careful to avoid calling it a constructor.

Related

oracle - how to create an object type for an already existing table

We have a requirement where we have to pass the table name to a pl/SQL object at the runtime.
Below is the example
create or replace FUNCTION ABC
(P_TABLE VARCHAR2) RETURN NUMBER IS
C_REFERENCE SYS_REFCURSOR;
V_TABLE VARCHAR2(50):=P_TABLE;
V_C_REF v_table%rowtype;
BEGIN
OPEN C_REFERENCE FOR 'SELECT * FROM '||V_TABLE||;
LOOP
FETCH C_REFERENCE INTO V_C_REF;
EXIT WHEN C_REFERENCE%NOTFOUND;
/*some processing*/
END LOOP;
return(1);
END;
The above code will give me an error. Is there any workaround for it? Table name can vary and different tables will have different structures.
The rowtype declaration, has to be static (if it's not, the compiler can't tell if the fields referenced from it are valid).
Possible solutions are:
Put a comma separated list of variables in the INTO clause:
FETCH C_REFERENCE INTO var1, var2, var3;
Create your own record
TYPE V_C_REF IS RECORD ( col1 VARCHAR2(20), col2 VARCHAR2(25) );
Use a table with the expected estructure
vc_ref table1%ROWTYPE;
write a PL/SQL function to query dba_tab_columns and get the column size and type definition.

Invalid Datatype when passing collection into function

CREATE OR REPLACE PACKAGE test_package
IS
TYPE IDTable IS TABLE OF test_table.id%TYPE;
TYPE RowIDTable IS TABLE OF VARCHAR2(500);
FUNCTION delete_rows (row_ids IN RowIDTable) RETURN IDTable;
END test_package;
/
CREATE OR REPLACE PACKAGE BODY test_package
IS
FUNCTION delete_rows (row_ids IN RowIDTable) RETURN IDTable
IS
ids IDTable;
BEGIN
DELETE FROM test_table
WHERE ROWIDTONCHAR(rowid) IN (SELECT * FROM TABLE(row_ids))
RETURNING id BULK COLLECT INTO ids;
COMMIT;
RETURN ids;
END;
END test_package;
/
I keep getting ORA-00902: invalid datatype when using the above function. What am I doing wrong?
The error is pointing to the DELETE statement.
It looks like the problem is the type of collection you’re using. in that it’s a package-level PL/SQL collection type.
The ruby-plsql documentation says it supports PL/SQL records, but the table and varray types don’t refer to PL/SQL - suggesting they have to be SQL (i.e. schema-level) user-defined types.
Even without Ruby in the picture, the invalid-datatype error is thrown at runtime, when the same package-defined types are used consistently. Prior to 12c you couldn’t use a PL/SQL collection in a SQL statement at all, and it still doesn’t seem to work in this scenario.
Your code works if you change from the the PL/SQL type to a schema-level type, in this example a built-in varray type:
CREATE OR REPLACE PACKAGE test_package
IS
TYPE IDTable IS TABLE OF test_table.id%TYPE;
--TYPE RowIDTable IS TABLE OF VARCHAR2(500); —- not used now
FUNCTION delete_rows (row_ids IN sys.odcivarchar2list) RETURN IDTable;
END test_package;
/
CREATE OR REPLACE PACKAGE BODY test_package
IS
FUNCTION delete_rows (row_ids IN sys.odcivarchar2list) RETURN IDTable
IS
ids IDTable;
BEGIN
DELETE FROM test_table
WHERE ROWIDTONCHAR(rowid) IN (SELECT * FROM TABLE(row_ids))
RETURNING id BULK COLLECT INTO ids;
COMMIT;
RETURN ids;
END;
END test_package;
/
which you can test with anonymous block:
declare
ids test_package.idtable;
begin
ids := test_package.delete_rows(sys.odcivarchar2list('ROWID1', 'ROWID2'));
end;
/
It would be more efficient to not convert every rowid in the table, so you can do this instead:
WHERE rowid IN (SELECT CHARTOROWID(column_value) FROM TABLE(row_ids))
but bear in mind that rowids are not always immutable - hopefully the references you are passing in are recent enough to always still be valid. Otherwise, work with primary keys rather than rowids.
I don’t know Ruby so not entirely sure how that translates; again from the docs it looks like:
plsql.test_package.delete_rows(['ROWID1','ROWID2'])
... but not sure how it’ll handle the returned (PL/SQL) table type. That may need to be a schema-level table type too. (And you can probably create types as either varrays or nested tables if you want to make your own, which is probably a good idea.)
Why don't you use TABLE OF ROWID instead of TABLE OF VARCHAR2(500) on RowIDTable?
Like so:
TYPE RowIDTable IS TABLE OF ROWID;
Then you wouldn't have to cast ROWID:
DELETE FROM test_table tbl
WHERE tbl.rowid IN (SELECT column_value FROM TABLE(row_ids))
RETURNING id BULK COLLECT INTO ids;
Hope this helps.

How do I select from Bulk Collected Table of Records Type

I've got a procedure where I need to cache some data, for performance reasons, for downstream operations.
The TYPE definitions work
The BULK COLLECT INTO works
The SELECT does not work
PROCEDURE MYPROC((PARAMS))AS
TYPE REC_TYPE IS RECORD (
COLUMN_1 (TABLEA.COLUMN_A)%TYPE,
COLUMN_2 (TABLEA.COLUMN_B)%TYPE
);
TYPE TAB_TYPE IS TABLE OF REC_TYPE;
TABLE_1 TAB_TYPE;
BEGIN
SELECT COLUMN_A, COLUMN_B
BULK COLLECT INTO TABLE_1
FROM TABLE_A;
SELECT * FROM TABLE_1;
END MYPROC;
Yields:
Error(#,#): PL/SQL: ORA-00942: table or view does not exist
I've also tried wrapping it in a table function like I do with my single-column types elsewhere, but that did not work either
SELECT * FROM TABLE(TABLE_1);
Error(#,#): PL/SQL: ORA-22905: cannot access rows from a non-nested
table item
Your problem is actually a PLS-00642 error, rather than ORA-22905. Essentially you can't use local collection types in SQL statements. The solution therefore, is to define your types at the schema level. When defining types in this way, we cannot use the %TYPE syntax, and instead must explicitly define the column (Getting PLS-00201 error while creating a type in oracle) i.e.
create or replace type rec_type as object (
COLUMN_1 integer,
COLUMN_2 varchar2(128)
);
create or replace type tab_type as table of rec_type;
You then need to explicitly convert the values into the relevant type in order to perform the bulk collect as mentioned here: ORA-00947 Not enough values while declaring type globally.
Your procedure would therefore look something like this:
PROCEDURE MYPROC((PARAMS))AS
TABLE_1 TAB_TYPE;
lCount integer;
BEGIN
SELECT REC_TYPE(COLUMN_A, COLUMN_B)
BULK COLLECT INTO TABLE_1
FROM TABLE_A;
SELECT COUNT(*) INTO lCount FROM TABLE(TABLE_1);
END MYPROC;

Returning Nested table from a stored procedure

I would like to know how can I return a nested table from a stored procedure in oracle to consume in my .net client.
Basically I used the following format for creating nested table :
TYPE changes IS RECORD(
col1 VARCHAR2(20),
col2 VARCHAR2(20),
col3 VARCHAR2(20)
);
TYPE collection is table of changes;
I'm populating this with values in stored procedure logic.
Now I want to return these values for my .net client.
Can we try to dump nested tables values to cursor and return back. If yes then how ?
First off, if using ODP.net then know that you can directly pass collection types between a procedure and your UI. It is finicky, but it works.
If you want to simply dump a collection into a cursor to be returned, then look at the TABLE() function. In your example you could pass back a ref_cursor for the UI to traverse using something like (forgive any minor syntax glitches - I'm away from my database at the moment):
FUNCTION collection_to_cursor
return sys_refcursor
IS
p_cursor sys_refcursor;
p_change changes;
BEGIN
Open p_cursor for (
SELECT col1, col2, col3
FROM TABLE(p_change));
RETURN p_cursor;
end;
You could use a ref cursor, something along the lines of
DECLARE
lt_collection collection := collection();
lrc_collection SYS_REFCURSOR;
BEGIN
lt_collection := f_populate_collection_somehow;
OPEN lrc_collection
SELECT col1
,col2
,col3
FROM TABLE(CAST(lt_collection AS collection));
END;
.NET can then retrieve the data from the ref cursor, I'm not sure of the details of how this is done.
There is some good further information on using ref cursors or associative arrays
here
Please note, I am not conversant with .NET, so I am writing this literally to your question "how can I return a nested table from a stored procedure in oracle".
I am not sure about the "to consume in my .net client" portion, as I am not sure if a returned record type/table type can directly be used in .net code.
I did some research and what I learnt is that there is no direct support of record type in any oracle client interface. What people generally do is to create a wrapper procedure around the returned table type from function to convert it into a ref cursor and use the ref cursor in custom code.
CREATE OR REPLACE TYPE changes AS OBJECT(
col1 VARCHAR2(20),
col2 VARCHAR2(20),
col3 VARCHAR2(20)
);
CREATE OR REPLACE TYPE collection is table of changes;
CREATE OR REPLACE PACKAGE test_pkg AS
FUNCTION test_fn RETURN collection;
END test_pkg;
CREATE OR REPLACE PACKAGE BODY test_pkg
AS
FUNCTION test_fn RETURN collection AS
l_collection collection;
BEGIN
l_collection := collection();
l_collection.EXTEND;
l_collection(l_collection.LAST) := changes('Subhasis','Mukherjee','Male');
RETURN l_collection;
END test_fn;
END test_pkg;
SELECT * FROM table(test_pkg.test_fn)
col1 col2 col3
----------- ------------- --------------
Subhasis Mukherjee Male

Using an Oracle Table Type in IN-clause - compile fails

Simply trying to get a cursor back for the ids that I specify.
CREATE OR REPLACE PACKAGE some_package AS
TYPE t_cursor IS REF CURSOR;
TYPE t_id_table IS TABLE OF NVARCHAR(38) INDEX BY PLS_INTEGER;
PROCEDURE someentity_select(
p_ids IN t_id_table,
p_results OUT t_cursor);
END;
CREATE OR REPLACE PACKAGE BODY some_package AS
PROCEDURE someentity_select(
p_ids IN t_guid_table,
p_results OUT t_cursor)
IS
BEGIN
OPEN p_results FOR
SELECT *
FROM someschema.someentity
WHERE id IN (SELECT column_value FROM TABLE(p_ids)); - fails here
END;
END;
Note: someschema.someentity.id is a NVARCHAR2(38)
PL/SQL: ORA-00382: expression is of wrong type
PL/SQL: ORA-22905: cannot access rows from a non-nested table item
Where am I going wrong?
In Oracle versions prior to 12.2 you can only SELECT from a collection type that is defined in the database via a CREATE TYPE statement, not an associative array:
CREATE TYPE t_id_table IS TABLE OF NVARCHAR(38);
CREATE OR REPLACE PACKAGE some_package AS
PROCEDURE someentity_select(
p_ids IN t_guid_table,
p_results OUT SYS_REFCURSOR);
END;
CREATE OR REPLACE PACKAGE BODY some_package AS
PROCEDURE someentity_select(
p_ids IN t_guid_table,
p_results OUT SYS_REFCURSOR)
IS
BEGIN
OPEN p_results FOR
SELECT *
FROM someschema.someentity
WHERE id IN (SELECT column_value FROM TABLE(p_ids));
END;
END;
This is an index-by table, which is a PL/SQL type.
You can only use SQL types in the SQL engine of Oracle. Or PL/SQL types, that Oracle can hack around to look like SQL types.
You can have a simple array-like collection and use it as a result. (no index by)
type TGuidList is table of NVarchar(38);
But the best compatibility and stability, you get by declaring it as a global SQL type and use that inside your package:
create type TGuidList is table of NVarchar(38);
Edit: You will not need an NVarChar for a GUID, will you? A good ol' VarChar should do the trick just fine.

Resources