Create an ODCI list of SDO_GEOMETRY objects - oracle

I recently learned about ODCI lists (in an answer from #MT0).
For example, an OdciVarchar2List:
select
sys.odcivarchar2list('a', 'b', 'c') as my_list
from
dual
MY_LIST
-------------------------------
ODCIVARCHAR2LIST('a', 'b', 'c')
Out of curiosity, is there a way to create a ODCI list of MDSYS.SDO_GEOMETRY objects?
I tried using ODCIObjectList:
select
sys.ODCIObjectList(
sdo_geometry('point(10 20)'),
sdo_geometry('point(30 40)'),
sdo_geometry('point(50 60)')
) as my_list
from
dual
But I got an error, suggesting that ODCIObjectList isn't meant for sdo_geometry objects:
ORA-00932: inconsistent datatypes: expected SYS.ODCIOBJECT got MDSYS.SDO_GEOMETRY
00932. 00000 - "inconsistent datatypes: expected %s got %s"
*Cause:
*Action:
Error at Line: 3 Column: 9

is there a way to create a ODCI list of MDSYS.SDO_GEOMETRY objects?
No, the ODCI types are built-in types belonging to the SYS schema and there is no ODCI list that can hold SDO geometry objects.
You either create your own collection type; see Justin Cave's answer.
Or you need to look for a different (non-ODCI) built-in collection type.
To find the existing collection types, you can use:
SELECT *
FROM ALL_TYPES
WHERE TYPECODE = 'COLLECTION'
To find a list of all the built-in collections.
The SDO objects are defined in the MDSYS schema and you can filter to just those using:
SELECT *
FROM ALL_TYPES
WHERE OWNER = 'MDSYS'
AND TYPECODE = 'COLLECTION'
Which contains 92 collection types; one of which is the SDO_GEOMETRY_ARRAY type which happens to do exactly what you want.
Therefore, if you do not want to create your own type, you can use:
SELECT MDSYS.SDO_GEOMETRY_ARRAY(
sdo_geometry('point(10 20)'),
sdo_geometry('point(30 40)'),
sdo_geometry('point(50 60)')
)
FROM DUAL;
or, more simply:
SELECT SDO_GEOMETRY_ARRAY(
sdo_geometry('point(10 20)'),
sdo_geometry('point(30 40)'),
sdo_geometry('point(50 60)')
)
FROM DUAL;

Declaring your own collection type is pretty easy and definitely the way to go. Being able to use the ODCI collection types can be handy if you happen to be in a situation where you really need to use a collection but you're not allowed to declare your own collection type for some reason (i.e. you're using a read-only database). In general, though, it makes vastly more sense to declare your own collection types (which can be given more informative names and which you can then manage just like all the other objects in the schema.
create or replace type sdo_geometry_nt is table of sdo_geometry;
/
declare
l_points sdo_geometry_nt := sdo_geometry_nt( sdo_geometry( 'point(10 20)' ),
sdo_geometry( 'point(30 40)' ),
sdo_geometry( 'point(50 60)' ));
begin
for i in 1..l_points.count
loop
-- do something
null;
end loop;
end;
/

Related

Synthetic TABLE types show up in ALL_COLL_TYPES, but OBJECT types don't show in ALL_TYPES in Oracle 18c

When creating the following:
create view v (a, b) as select 1, 2 from dual;
create or replace package p as
type t is table of v%rowtype;
function f return t pipelined;
end p;
/
I can see some synthetic types show up in the dictionary:
select o.object_name, s.line, s.text
from all_objects o
join all_source s on o.owner = s.owner and o.object_name = s.name
where o.owner = 'TEST'
and o.object_name like 'SYS_PLSQL_%'
order by 1;
Resulting in:
|OBJECT_NAME |LINE|TEXT |
|--------------------------|----|--------------------------------------------------------------------------------|
|SYS_PLSQL_3473F824_9_1 |1 |type SYS_PLSQL_3473F824_9_1 as table of "TEST"."SYS_PLSQL_56AACD46_15_1";|
|SYS_PLSQL_3473F824_DUMMY_1|1 |type SYS_PLSQL_3473F824_DUMMY_1 as table of number; |
|SYS_PLSQL_56AACD46_15_1 |1 |type SYS_PLSQL_56AACD46_15_1 as object ("A" NUMBER, |
|SYS_PLSQL_56AACD46_15_1 |2 |"B" NUMBER); |
|SYS_PLSQL_56AACD46_DUMMY_1|1 |type SYS_PLSQL_56AACD46_DUMMY_1 as table of number; |
These all appear in ALL_OBJECTS, but only the collection types also appear in ALL_COLL_TYPES. The OBJECT type is not to be found in ALL_TYPES:
select type_name, elem_type_name
from all_coll_types
where owner = 'TEST' and type_name like 'SYS_PLSQL_%'
union all
select type_name, null as elem_type_name
from all_types
where owner = 'TEST' and type_name like 'SYS_PLSQL_%';
Resulting in
|TYPE_NAME |ELEM_TYPE_NAME |
|--------------------------|-----------------------|
|SYS_PLSQL_3473F824_9_1 |SYS_PLSQL_56AACD46_15_1|
|SYS_PLSQL_3473F824_DUMMY_1|NUMBER |
|SYS_PLSQL_56AACD46_DUMMY_1|NUMBER |
Is this a bug in the definition of ALL_TYPES, or is there a good reason for the SYS_PLSQL_56AACD46_15_1 not to be listed? After all, it appears elsewhere, specifically referenced from ALL_COLL_TYPES.ELEM_TYPE_NAME for SYS_PLSQL_3473F824_9_1
I'm using Oracle Database 18c Express Edition Release 18.0.0.0.0
Possibly just because it doesn't need to be. The collection type can be referenced, indirectly, from SQL:
select * from table(p.f);
But the object type can't.
The types don't appear in all_objects, and the collection type doesn't appear in all_coll_types, if you only declare it or only use it in a procedure:
create or replace package p as
type t is table of v%rowtype;
end p;
/
or
create or replace package p as
type t is table of v%rowtype;
procedure p (v_t in out t);
end p;
/
With both of those package specs, your queries return zero rows. Once you add a public reference that might be called from a SQL context, i.e. a function returning that type, the data dictionary needs to know about it.
But you can't refer to the element type of the collection directly - if you have a function returning v%rowtype then that doesn't need to be a synthetic type of its own, and variable of that type is compatible with the synthetic element type.
So SQL needs to be able to handle the collection type, and needs to see it in all_coll_types to do that (presumably - who knows what's happening under the hood); and the link between the collection type and the record type is there via elem_type_name. The record type can't be used, so doesn't need to be listed in all_types - which, as the documentation says, is only for object types, so record types wouldn't really fit there anyway.
Similarly, the collection type appears in all_plsql_types as soon as it is declared, even if it isn't used; but the element (record) type never appears there, since it can't be referred to.
db<>fiddle

Oracle create operator

I've recently run into a case where a fuzzy-match was useful when categorizing historical unstructured-string data. UTL_MATCH is great and has worked well when wrapping into a truthy fuzzy-match function. But I wanted to be joining ad-hoc, and took the route of building out a new function-based operator.
OPERATOR creation: (function-based, FUZZY_MATCH returns 0 for unmatched, 1 for match. A dummy version is included here)
CREATE OR REPLACE FUNCTION FUZZY_MATCH(
LEFT_ITEM VARCHAR2,
RIGHT_ITEM VARCHAR2 )
RETURN NUMBER AS BEGIN
RETURN 1;
END;
CREATE OR REPLACE OPERATOR RESEMBLES
BINDING (VARCHAR2, VARCHAR2)
RETURN NUMBER USING FUZZY_MATCH
;
Creating a new operator works fine, but I've been a little dissatisfied with the resultant sql syntax (example below).
CREATE TABLE LEFT_TABLE(ARBITRARY_DATA VARCHAR2(200) NOT NULL);
CREATE TABLE RIGHT_TABLE(ARBITRARY_DATA VARCHAR2(200) NOT NULL);
INSERT INTO LEFT_TABLE VALUES ('In a hole in the ground there lived a hobbit.');
INSERT INTO RIGHT_TABLE VALUES ('In the ground there lived a hobbit.');
SELECT
LEFT_TABLE.ARBITRARY_DATA LEFT_DATA,
RIGHT_TABLE.ARBITRARY_DATA RIGHT_DATA
FROM
LEFT_TABLE
INNER JOIN
RIGHT_TABLE
ON 1 = RESEMBLES ( LEFT_TABLE.ARBITRARY_DATA, RIGHT_TABLE.ARBITRARY_DATA )
;
Is there an alternative OPERATOR definition to make the truthiness of the operator implicit, allowing for more natural syntax like the following? I'm on 11gR2
Thanks
SELECT
LEFT_TABLE.ARBITRARY_DATA LEFT_DATA,
RIGHT_TABLE.ARBITRARY_DATA RIGHT_DATA
FROM
LEFT_TABLE
INNER JOIN
RIGHT_TABLE
ON LEFT_TABLE.ARBITRARY_DATA RESEMBLES RIGHT_TABLE.ARBITRARY_DATA;

Parameter for IN query oracle [duplicate]

This question already has an answer here:
Oracle: Dynamic query with IN clause using cursor
(1 answer)
Closed 8 years ago.
SELECT * FROM EMPLOYEE
WHERE EMP_NAME IN (:EMP_NAME);
This is my query and now the EMP_NAME parameter I would like to send it as a list of strings.
When I run this query in SQL developer it is asked to send the EMP_NAME as a parameter, Now I want to send 'Kiran','Joshi' (Basically, I want to fetch the details of the employee with employee name either Kiran or Joshi. How should I pass the value during the execution of the query?
It works when I use the value Kiran alone, but when I concatenate with any other string it won't work. Any pointers in this?
I tried the one below
'Kiran','Joshi'
The above way doesn't work as understood this is a single parameter it tries the employee with the name as 'Kiran',Joshi' which won't come. Understandable, but in order to achieve this thing, how can I go ahead?
Any help would be really appreciated.
Thanks to the people who helped me in solving this problem.
I could get the solution using the way proposed, below is the approach
SELECT * FROM EMPLOYEE WHERE EMP_NAME IN (&EMP_NAME)
I have tried in this way and following are the scenarios which I have tested and they are working fine.
Scenario 1:
To fetch details of only "Kiran", then in this case the value of EMP_NAME when sql developer prompts is given as Kiran. It worked.
Scenario 2:
To fetch details of either "Kiran" or "Joshi", then the value of EMP_NAME is sent as
Kiran','Joshi
It worked in this case also.
Thanks Kedarnath for helping me in achieving the solution :)
IN clause would be implicitly converted into multiple OR conditions.. and the limit is 1000.. Also query with bind variable means, the execution plan will be reused.. Supporting bind variables for IN clause will hence affect the bind variable's basic usage, and hence oracle limits it at syntax level itself.
Only way is like name in (:1,:2) and bind the other values..
for this, you might dynamic SQL constructing the in clause bind variables in a loop.
Other way is, calling a procedure or function(pl/sql)
DECLARE
v_mystring VARCHAR(50);
v_my_ref_cursor sys_refcursor;
in_string varchar2='''Kiran'',''Joshi''';
id2 varchar2(10):='123'; --- if some other value you have to compare
myrecord tablename%rowtype;
BEGIN
v_mystring := 'SELECT a.*... from tablename a where name= :id2 and
id in('||in_string||')';
OPEN v_my_ref_cursor FOR v_mystring USING id2;
LOOP
FETCH v_my_ref_cursor INTO myrecord;
EXIT WHEN v_my_ref_cursor%NOTFOUND;
..
-- your processing
END LOOP;
CLOSE v_my_ref_cursor;
END;
IN clause supports maximum of 1000 items. You can always use a table to join instead. That table might be a Global Temporary Table(GTT) whose data is visible to thats particular session.
Still you can use a nested table also for it(like PL/SQL table)
TABLE() will convert a PL/Sql table as a SQL understandable table object(an object actually)
A simple example of it below.
CREATE TYPE pr AS OBJECT
(pr NUMBER);
/
CREATE TYPE prList AS TABLE OF pr;
/
declare
myPrList prList := prList ();
cursor lc is
select *
from (select a.*
from yourtable a
TABLE(CAST(myPrList as prList)) my_list
where
a.pr = my_list.pr
order by a.pr desc) ;
rec lc%ROWTYPE;
BEGIN
/*Populate the Nested Table, with whatever collection you have */
myPrList := prList ( pr(91),
pr(80));
/*
Sample code: for populating from your TABLE OF NUMBER type
FOR I IN 1..your_input_array.COUNT
LOOP
myPrList.EXTEND;
myPrList(I) := pr(your_input_array(I));
END LOOP;
*/
open lc;
loop
FETCH lc into rec;
exit when lc%NOTFOUND; -- Your Exit WHEN condition should be checked afte FETCH iyself!
dbms_output.put_line(rec.pr);
end loop;
close lc;
END;
/

Ordered iteration in an user-defined aggregate function?

I just implemented the ODCIAggregate Interface to create a custom aggregation function. It works quite well and fast, but I would like it to do a little something more. I have a statement going like this:
SELECT SomeId, myAggregationFunction(Item) FROM
(
SELECT
Foo.SomeId,
SomeType(Foo.SomeValue, Foo.SomeOtherValue) AS Item
FROM
Foo
ORDER BY Foo.SomeOrderingValue
)
GROUP BY SomeId;
My problem is that items aren't passed to the ODCIAggregateIterate function of my implementation in the same order that my inner (ordered) SELECT returns them.
I've Googled around and didn't find any Oracle-provided way to do so. Has any of you experimented a similar problem based on that requirement?
Thanks!
Have you considered using COLLECT instead of data cartridge?
At least for string aggregation, the COLLECT method is simpler and much faster. It does make your SQL a little weirder though.
Below is an example using just simple string concatenation.
--Create a type
create or replace type sometype as object
(
someValue varchar2(100),
someOtherValue varchar2(100)
);
--Create a nested table of the type.
--This is where the performance improvement comes from - Oracle can aggregate
--the types in SQL using COLLECT, and then can process all the values at once.
--This significantly reduces the context switches between SQL and PL/SQL, which
--are usually more expensive than the actual work.
create or replace type sometypes as table of sometype;
--Process all the data (it's already been sorted before it gets here)
create or replace function myAggregationFunction(p_sometypes in sometypes)
return varchar2 is
v_result varchar2(4000);
begin
--Loop through the nested table, just concatenate everything for testing.
--Assumes a dense nested table
for i in 1 .. p_sometypes.count loop
v_result := v_result || ',' ||
p_sometypes(i).someValue || '+' || p_sometypes(i).someOtherValue;
end loop;
--Remove the first delimeter, return value
return substr(v_result, 2);
end;
/
--SQL
select someId
,myAggregationFunction
(
cast
(
--Here's where the aggregation and ordering happen
collect(sometype(SomeValue, SomeOtherValue)
order by SomeOrderingValue)
as someTypes
)
) result
from
(
--Test data: note the unordered SoemOrderingValue.
select 1 someId, 3 SomeOrderingValue, '3' SomeValue, '3' SomeOtherValue
from dual union all
select 1 someId, 1 SomeOrderingValue, '1' SomeValue, '1' SomeOtherValue
from dual union all
select 1 someId, 2 SomeOrderingValue, '2' SomeValue, '2' SomeOtherValue
from dual
) foo
group by someId;
--Here are the results, aggregated and ordered.
SOMEID RESULT
------ ------
1 1+1,2+2,3+3
Oracle is very likely be rewriting your query and getting rid of the subquery. I've never done anything like what you're doing, but could you add the NO_UNNEST hint on the inner query?
SELECT SomeId, myAggregationFunction(Item) FROM
(
SELECT /*+ NO_UNNEST */
Foo.SomeId, ...
Even then, I'm really not sure what it will do with an ORDER BY inside a subquery.

Functional Where-In Clause - Oracle PL/SQL

I've got an association table that groups accounts together.
I'm trying to select a subset of table 'target'
p_group_id := 7;
select *
target t
where t.account_id in get_account_ids(p_group_id);
Is it possible to write a function that returns a list of account_ids (as some form of collection) that would facilitate the above code?
I've looked at pipelined functions, however I want to stay away from loops and cursors. Also, custom types / table also get into casting that I'd like to avoid.
For reference, here's some pseudocode for what the function 'get_account_ids' would do, hypothetically:
function get_account_ids(p_group_id)
insert into v_ret
select aa.account_id
from assoc_account aa
where aa.groupid = p_group_id;
return v_ret;
You simply need:
select *
from target t
where t.account_id in
( select aa.account_id
from assoc_account aa
where aa.groupid = 7;
)
The above will work, assuming that assoc_account.account_id is never NULL.

Resources