I have a column in an Oracle 18c table that is an object type (user-defined type): Esri's ST_GEOMETRY spatial type.
I want to create a function-based index on that column:
create or replace function my_owner.test_func(shape in sde.st_geometry) return sde.st_geometry
deterministic is
begin
return sde.st_geometry_operators.st_startpoint_f(sde.st_geometry_operators.st_geometryn_f(shape,1));
end;
create index my_owner.test_idx on my_owner.active_transportation (my_owner.test_func(shape));
I can create the function without issue. But when I try to create the index, it fails:
Error starting at line : 10 in command -
create index my_owner.test_idx on my_owner.active_transportation (my_owner.test_func(shape))
Error report -
ORA-02327: cannot create index on expression with datatype ADT
02327. 00000 - "cannot create index on expression with datatype %s"
*Cause: An attempt was made to create an index on a non-indexable
expression.
*Action: Change the column datatype or do not create the index on an
expression whose datatype is one of VARRAY, nested table, object,
LOB, or REF.
That error seems pretty definitive: Oracle can't create a function-based index on objects.
With that said, are there any workarounds for this scenario?
For example, could I cast the object as something else in the function, create the index on the casting, and then un-cast it in a query?
For what it's worth, there is a function that can convert the object to a blob. Unfortunately though, blobs aren't supported in function-based indexes either. I get the same error.
Related
I am trying to lookup values from an associative array to update a table, but am getting error. Here is a bare minimum code to demonstrate the issue (my actual query is much more complex).
Am I missing something, or if this is an Oracle limitation is there a way around it?
CREATE TABLE t_employee AS SELECT cast('john' as varchar2(30)) emp_name, CAST (NULL AS NUMBER (5)) emp_id FROM DUAL;
DECLARE
TYPE emp_typ IS TABLE OF NUMBER INDEX BY VARCHAR2(30);
emp_lookup emp_typ;
BEGIN
emp_lookup ('john') := 1234;
UPDATE t_employee e
SET emp_id = (SELECT emp_lookup (e.emp_name) FROM DUAL);
END;
/
--DROP TABLE t_employee;
> PLS-00201: identifier 'E.EMP_NAME' must be declared
and when I change the update to a simpler construct:
UPDATE t_employee SET emp_id = emp_lookup (t_employee.emp_name);
I get this weirdness: PLS-00382: expression is of wrong type
Same with SELECT. It seems to me that we cannot use associative arrays directly in SQL statements.
An associative array collection is a PL/SQL only data type; it cannot be used in SQL statements.
From the Oracle documentation:
Table 5-1 PL/SQL: Collection Types
Collection Type
Number of Elements
Index Type
Dense or Sparse
Uninitialized Status
Where Defined
Can Be ADT Attribute Data Type
Associative array (or index-by table)
Unspecified
String or PLS_INTEGER
Either
Empty
In PL/SQL block or package
No
VARRAY (variable-size array)
Specified
Integer
Always dense
Null
In PL/SQL block or package or at schema level
Only if defined at schema level
Nested table
Unspecified
Integer
Starts dense, can become sparse
Null
In PL/SQL block or package or at schema level
Only if defined at schema level
...
An associative array (formerly called PL/SQL table or index-by table) is a set of key-value pairs.
...
Unlike a database table, an associative array:
Does not need disk space or network operations
Cannot be manipulated with DML statements
VARRAY and nested table collections can be used in SQL statements.
In Oracle PL/SQL records we can use anchor datatypes (including %TYPE and %ROWTYPE) to define the fields.
When I populate a record from a query, in my select clause I want type conversion. Is that possible using an Oracle built-in function or some other approach?
In this example scenario I am using a simple decode function to perform a conversion:
DECLARE
TYPE TEST_RECORD IS RECORD(
FIRST_NAME EMPLOYEE_MT.FIRST_NAME%TYPE,
LAST_NAME EMPLOYEE_MT.LAST_NAME%TYPE,
MARITIAL_STATUS EMPLOYEE_MT.MARITAL_STATUS%TYPE);
EMPLOYEE_NAME TEST_RECORD;
BEGIN
SELECT EMP.FIRST_NAME,
EMP.LAST_NAME,
DECODE(EMP.MARITAL_STATUS, 1, 'MARRIED', 0, 'UN-MARRIED')
INTO EMPLOYEE_NAME
FROM EMPLOYEE_MT EMP
WHERE EMP.EMPLOYEE_ID = 1;
DBMS_OUTPUT.put_line(EMPLOYEE_NAME.MARITIAL_STATUS);
END;
which gets error:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 9
You have defined your record type with the maritial_status (shouldn't that be marital_status?) field using the same data type as the table column. From your decode that appears to be a number data type. You're then trying to set the record's field value to a string, either 'MARRIED' or 'UN-MARRIED', when that field is expecting a number. Clearly neither of those strings can be converted to a number, hence the error you're getting.
If you want the record to store the string value, you'll have to define it like that - explicitly as a string, rather than using %TYPE:
DECLARE
TYPE TEST_RECORD IS RECORD(
FIRST_NAME EMPLOYEE_MT.FIRST_NAME%TYPE,
LAST_NAME EMPLOYEE_MT.LAST_NAME%TYPE,
MARITAL_STATUS VARCHAR2(10));
...
DBMS_OUTPUT.put_line(EMPLOYEE_NAME.MARITAL_STATUS);
...
You can't do that automatically using %TYPE as the data type just doesn't match. You've explicitly told Oracle that you want the field's data type to be a number, so Oracle isn't going to let you put a string in that field instead. It isn't about there not being a built-in function, it just doesn't make sense.
This also means you can't use %ROWTYPE if you're changing the data type either (unless your modified value can be implicitly converted back to the column data type).
As the title said : I want to create a type in oracle based on an existing Table.
I did as follow :
create or replace type MY_NEW_TYPE as object( one_row EXISTING_TABLE%rowtype);
The Aim is to be able to use this into a function which will return a table containing sample row of the table EXISTING_TABLE :
create or replace function OUTPUT_FCT() return MY_NEW_TYPE AS
...
If you only need to create a function that returns a row from your table, you could try something like the following, without creating types.
setup:
create table EXISTING_TABLE( a number, b varchar2(100));
insert into EXISTING_TABLE values (1, 'one');
function:
create or replace function OUTPUT_FCT return EXISTING_TABLE%rowtype AS
retVal EXISTING_TABLE%rowType;
begin
select *
into retVal
from EXISTING_TABLE
where rownum = 1;
--
return retVal;
end;
function call
SQL> begin
2 dbms_output.put_line(OUTPUT_FCT().a);
3 dbms_output.put_line(OUTPUT_FCT().b);
4 end;
5 /
1
one
However, I would not recommend such an approach, because things like select * can be really dangerous; I would much prefer defining a type with the fields I need, and then explicitly query my table for the needed columns.
No, you can't do that, you'll get a compilation error:
create or replace type my_new_type as object(one_row t42%rowtype);
/
Type MY_NEW_TYPE compiled
Errors: check compiler log
show errors
Errors for TYPE STACKOVERFLOW.MY_NEW_TYPE:
LINE/COL ERROR
-------- -----------------------------------------------------------------------
0/0 PL/SQL: Compilation unit analysis terminated
1/36 PLS-00329: schema-level type has illegal reference to MYSCHEMA.T42
You will need to specify each field in the object type, and you will have to specify the data types manually too - you can't use table.column%type either.
You could create the type dynamically based on column and data type information from the data dictionary, but as this will (hopefully) be a one-off task and not something you'd do at runtime, that doesn't really seem worth it.
You can create a PL/SQL table type based on your table's rowtype, but you would only be able to call a function returning that from PL/SQL, not from plain SQL - so you couldn't use it in a table collection expression for example. If you were only returning a single sample row you could return a record rather than a table, but the same applies. You can also have a function that returns a ref cursor which could match the table's structure, but you wouldn't be able to treat that as a table either.
Read more about object type creation in the documentation. Specifically the attribute and datatype sections.
I'm trying to create some output based on select items from APEX.
The case is as follows:
In APEX you select certain items.
APEX makes a nested table of those primary keys and call the function to create the output.
The function generates the output and returns it.
I already found out that I can't use locally defined nested tables in the where clause, but I am now stuck on the following:
My stored type:
CREATE OR REPLACE EDITIONABLE TYPE "T_NUMBERS" as table of NUMBER(10,0)
The part of the function that gives the ORA-06550 error:
DECLARE
c_asked T_NUMBERS;
BEGIN
c_asked := T_NUMBERS(21);
This is just a simple example of what is going wrong.
The error itself:
ORA-06550: line 9, column 23:
PL/SQL: ORA-00932: inconsistent datatypes: expected NUMBER got <schema_name>.T_NUMBERS
I've created a sample oracle function to returns the number of records in a table. Here is it
create or replace FUNCTION TEST_COUNT
RETURN NUMBER AS recCount NUMBER;
BEGIN
SELECT COUNT(*) INTO recCount FROM **tableName**;
return recCount;
END TEST_COUNT;
Its' being compiled successfully, but when I called this function in Oracle SQL-Developr using command
SELECT * FROM TABLE (TEST_COUNT());
it threw me the following error.
ORA-22905: cannot access rows from a non-nested table item
22905. 00000 - "cannot access rows from a non-nested table item"
*Cause: attempt to access rows of an item whose type is not known at
parse time or that is not of a nested table type
*Action: use CAST to cast the item to a nested table type
Error at Line: 1 Column: 22
I've followed Oracle error ORA-22905: cannot access rows from a non-nested table item but can't reach the solution. Please suggest what should I do?
Well, you're just calling it wrong. The TABLE() table collection expression is used when the function returns a collection (e.g. from create type x as table of number) that you want to treat as a table so you can join against it, which isn't the case here; you're returning a simple NUMBER.
So just do:
SELECT TEST_COUNT FROM DUAL;