Modify object attribute/property — without creating custom function - oracle

I have an Oracle 18c SDO_GEOMETRY object that has attributes (aka properties):
with cte as (
select
sdo_util.from_wktgeometry('MULTILINESTRING ((0 5 0, 10 10 10, 30 0 33.54),(50 10 33.54, 60 10 -10000))') shape
from dual)
select
a.shape.sdo_gtype as old_gtype,
a.shape.sdo_gtype + 300 as new_gtype,
a.shape
from
cte a
OLD_GTYPE NEW_GTYPE SHAPE
--------- --------- -----
3006 3306 SDO_GEOMETRY(3006, NULL, NULL, SDO_ELEM_INFO_ARRAY(1, 2, 1, 10, 2, 1), SDO_ORDINATE_ARRAY(0, 5, 0, 10, 10, 10, 30, 0, 33.54, 50, 10, 33.54, 60, 10, -10000))
I want to modify the GTYPE attribute of the SDO_GEOMETRY object:
Old GTYPE: 3006
New GTYPE: 3306
It's possible to modify the GTYPE attribute using a custom function (or an inline function):
See #AlbertGodfrind's answer in Convert M-enabled SDE.ST_GEOMETRY to SDO_GEOMETRY using SQL
However, as an experiment, I want to modify the GTYPE attribute right in the SELECT clause in a query -- without using a custom function.
For example, I wonder if there might be OOTB functionality like this:
modify_object_property(object, property_name, new_val) returns sdo_geometry
Is there a way to modify an SDO_GEOMETRY GTYPE attribute/property — without creating a custom function?
Related: Replace value in SDO_ELEM_INFO_ARRAY varray

You can use the sdo_geometry constructor as follows
with cte as (
select
sdo_util.from_wktgeometry('MULTILINESTRING ((0 5 0, 10 10 10, 30 0 33.54),(50 10 33.54, 60 10 -10000))') shape
from dual)
select sdo_geometry(a.shape.sdo_gtype + 300,
a.shape.sdo_srid,
a.shape.sdo_point,
a.shape.sdo_elem_info,
a.shape.sdo_ordinates) as shape
from cte a;

You can modify the contents of an object in SQL. You just need to make sure you use an alias on the table. For example:
update my_table t
set t.geom.sdo_gtype = t.geom.sdo_gtype + 300
where ... ;
But that is not what you are looking for. Modifying an object that way in the select list is not possible. Hence the approach via a custom function.

Related

Oracle - how to imitate bit columns and boolean AND/OR?

I come from MS SQL and My SQL. These DBMS provide a bit data type where the two boolean values are represented by 0 and 1.
I am now in a project with Oracle that is new to me. There is no bit type. It (somewhere) advises to use NUMBER(1) as bit - so values 0 and 1 would go, but it supports also values -9 to -1 and 2 to 9.
And we have to make an OR between two such columns.
I read also this article here. But what is best for OR (and AND) functionality, few boiler plate, readable for programmers (and performant)?
Here some test code to check it:
--drop table test_bool_number;
create table test_bool_number (
test_id NUMBER GENERATED BY DEFAULT AS IDENTITY,
abool NUMBER(1),
bbool NUMBER(1),
cbool NUMBER(1)
PRIMARY KEY(test_id)
);
INSERT INTO test_bool_number (abool, bbool, cbool)
select 0, 0, null from dual
union all select 0, 1, null from dual
union all select 1, 0, null from dual
union all select 1, 1, null from dual;
SELECT * FROM test_bool_number ORDER BY test_id; -- to query the results
A team member proposed to use:
-- option A + for OR, * for AND
UPDATE test_bool_number
SET
cbool = abool + bbool;
I found this version works most probably, if cbool is deserialized in Java class boolean property, but it won't work always:
abool and bbool have to be only either 0 or 1
cbool as result does not fulfill above condition and is true if cbool > 0
and if 10 bool columns like that are summed, it results in 10 and with a number(1) leads to ORA-01438 error
So I found then this:
-- option B greatest for OR, least for AND
UPDATE test_bool_number
SET
cbool = greatest(abool, bbool);
Above works great, because the result is always either 0 or 1, but only:
abool and bbool have to be only either 0 or 1
And this works also for 10 and more columns OR'ed.
For AND the method least instead of greatest could be taken.
But if someone calculates intermediate boolean results in the first manner or different - the value in abool and bbool could be -9 ... -1 or 2 ... 9 too!
Assuming all values not being 0 are representation of true (like in e. g. C programming language)
What is the simplest code to get a boolean OR?
-- bool input values are not always only 1 in case of true!
UPDATE test_bool_number
SET abool = abool * -5, bbool = bbool * +5;
-- option C standard SQL always correct
UPDATE test_bool_number
SET cbool = case when ((abool <> 0) or (bbool <> 0)) then 1 else 0 end;
Above statement is fully correct, but a lot of boiler plate.
So I found another option:
-- option D ABS and SIGN
UPDATE test_bool_number
SET
cbool = SIGN(ABS(abool) + ABS(bbool));
Is shorter, always correct. It even works with abool = 9 and bbool = 7 and the intermediate result being SIGN(16) where 16 is outside of range of NUMBER(1), but performant?
Is there a simpler one, perhaps even performant one to deal with boolean values?
Or is it better to represent boolean values in an other fashion on Oracle table columns than NUMBER(1) with values 0 = false and 1 (or other) = true?
-- option E ABS + > 0
UPDATE test_bool_number
SET
cbool = case when ((ABS(abool) + ABS(bbool)) > 0) then 1 else 0 end;
So what about using binary or bitwise OR?
-- option F BITOR for OR
UPDATE test_bool_number
SET
cbool = BITOR(abool, bbool) <> 0 then 1 else 0 end;
Above only works for OR, BITAND cannot be used for AND functionality.
Only some magic idea, not at all easy to understand by the next code reader:
-- option G via text
UPDATE test_bool_number
SET
cbool = ABS(SIGN(TO_NUMBER(abool || bbool)));
The most inner could be read as boolean or like it is in a lot of programming languages (C#, Java, ...) but in Oracle it is a string concatenation operator!
So we end up with '00', '10' etc.
Finally it returns only 0 or 1, but only if (abool and) bbool are positive values or 0!
And I doubt the performance (number to text and text to number conversion).
So how are you working with boolean values in Oracle, and what is best suitable to be able to build an understandable OR (and AND) code for 2 and more other boolean columns, that is quite performant as well?
Do you use another column type to represent boolean values? char? '0' and '1' or 'y' and 'n'? How do you make OR (and AND) with these for 2... n columns?
Use the functions BITAND (from at least Oracle 11) and BITOR (from Oracle 21, although undocumented) and put a CHECK constraint on your columns:
SELECT abool,
bbool,
BITAND(abool, bbool),
BITOR(abool, bbool)
FROM test_bool_number
ORDER BY test_id;
Which, for the sample data:
create table test_bool_number (
test_id NUMBER GENERATED BY DEFAULT AS IDENTITY,
abool NUMBER(1) CHECK (abool IN (0, 1)),
bbool NUMBER(1) CHECK (bbool IN (0, 1))
PRIMARY KEY(test_id)
);
INSERT INTO test_bool_number (abool, bbool)
select 0, 0 from dual
union all select 0, 1 from dual
union all select 1, 0 from dual
union all select 1, 1 from dual;
Outputs:
ABOOL
BBOOL
BITAND(ABOOL,BBOOL)
BITOR(ABOOL,BBOOL)
0
0
0
0
0
1
0
1
1
0
0
1
1
1
1
1
Prior to Oracle 21, you can create a user-defined function for BITOR.
db<>fiddle here

Distance between two Point with Latitude & Longitude in oracle DB

I need to calculate Distance between two location through Pl SQL Query. I had tried some Qry but not getting it.
for this we are using Query in SQL is as follows
SELECT ROUND(geography::Point(cast('19.2806118' as float),cast('72.8757395' as float) ,
4326).STDistance(geography::Point(cast('19.4482006' as float),
cast('72.7912511' as float), 4326))/1000,1) DIST
Result : 20.6
To Calculate the same in ORACLE Db I have found a solution over internet which is as follows
select sdo_geom.sdo_distance(sdo_geometry(2001,4326,null,sdo_elem_info_array(1, 1, 1),
sdo_ordinate_array( 19.2806118,72.8757395)),sdo_geometry(2001,4326, null,sdo_elem_info_array(1, 1,
1),sdo_ordinate_array( 19.4482006,72.7912511)),1,'unit=KM') distance_km from dual
Result : 10.9271..
Please suggest the method which will give me result in KM as I am getting MS SQL
Please suggest the method which will give me result in KM as I am getting MS SQL
Swapping parameter order 19.2806118,72.8757395 to 72.8757395,19.2806118
select sdo_geom.sdo_distance(
sdo_geometry(2001,4326,null,sdo_elem_info_array(1, 1, 1),
sdo_ordinate_array(72.8757395,19.2806118)),
sdo_geometry(2001,4326, null,sdo_elem_info_array(1, 1,1),
sdo_ordinate_array(72.7912511,19.4482006))
,1
,'unit=KM') distance_km
from dual;
Yields exactly the same result as SQL Server: 20.5657053685075
db<>fiddle demo Oracle
db<>fiddle demo SQL Server
Or by using sdo_point:
SELECT sdo_geom.sdo_distance(
sdo_geometry(2001, 4326,
sdo_point_type(72.8757395, 19.2806118, NULL), NULL, NULL),
sdo_geometry(2001, 4326,
sdo_point_type(72.7912511, 19.4482006, NULL), NULL, NULL),
1, 'unit=KM') distance_in_m
from DUAL;
SQL Server: geography::Point ( Lat, Long, SRID )

How can I get the complete definition (sql) of system views like user_objects?

I want to get the complete SQL code of the system views like USER_OBJECTS. However, when I execute the query below, it returns an error saying view not found in the SYS schema.
select dbms_metadata.get_ddl('VIEW', 'USER_OBJECTS', 'SYS') from dual;
When I execute the query below, it returns some codes in the text_vc column, but not the complete one. I cannot see the tables and where clause etc.
select * from ALL_VIEWS where VIEW_NAME = 'USER_OBJECTS';
But with this query, I can see that it is in the SYS schema with that name.
So, what is the reason that I cannot see the whole query? And is there a way to see it all?
+1 for looking at the definitions of the system views!
The first problem (DBMS_METADATA empty) is a privilege problem. According to the documentation, normal users will see only their own objects. You'll need the role SELECT_CATALOG_ROLE or EXP_FULL_DATABASE to see other users objects.
The second problem (SQL is not complete) comes from the datatype LONG, which - according to Oracle - should not be used anymore. However, it is still used by Oracle for view definitions, defaults, constraint text etc. Because it is so hard to handle, the view ALL_VIEWS has the original text in the LONG column and a truncated text, mostly the first 4000 characters, in the column text_vc, presumably for "text in varchar".
EDIT:
I believe you use Oracle 12 as you mention the column text_vc, which is not available in Oracle 11. Presumably, you are using a containerized database. If so, then please have a look at Data Dictionary Architecture in a CDB. Apparently, the definition of Oracle supplied things like views and packages are only visible in the root container. Sigh!!
In SQL*Plus, you'd set long (I shortened the output):
SQL> set pagesize 0
SQL> set long 10000
SQL>
SQL> select text from all_views where view_name = 'USER_OBJECTS';
select o.name, o.subname, o.obj#, o.dataobj#,
decode(o.type#, 0, 'NEXT OBJECT', 1, 'INDEX', 2, 'TABLE', 3, 'CLUSTER',
4, 'VIEW', 5, 'SYNONYM', 6, 'SEQUENCE',
7, 'PROCEDURE', 8, 'FUNCTION', 9, 'PACKAGE',
11, 'PACKAGE BODY', 12, 'TRIGGER',
<snip>
from sys."_CURRENT_EDITION_OBJ" o
where o.owner# = userenv('SCHEMAID')
and o.linkname is null
and (o.type# not in (1 /* INDEX - handled below */,
10 /* NON-EXISTENT */)
or
<snip>
union all
select l.name, NULL, to_number(null), to_number(null),
'DATABASE LINK',
l.ctime, to_date(null), NULL, 'VALID', 'N', 'N', 'N', NULL, NULL
from sys.link$ l
where l.owner# = userenv('SCHEMAID')
SQL>
Also, your first query works in my database (11g XE) if I'm connected as SYS:
SQL> show user
USER is "SYS"
SQL> select dbms_metadata.get_ddl('VIEW', 'USER_OBJECTS', 'SYS') from dual;
DBMS_METADATA.GET_DDL('VIEW','USER_OBJECTS','SYS')
--------------------------------------------------------------------------------
CREATE OR REPLACE FORCE VIEW "SYS"."USER_OBJECTS" ("OBJECT_NAME", "SUBOBJECT_N
AME", "OBJECT_ID", "DATA_OBJECT_ID", "OBJECT_TYPE", "CREATED", "LAST_DDL_TIME",
"TIMESTAMP", "STATUS", "TEMPORARY", "GENERATED", "SECONDARY", "NAMESPACE", "EDIT
ION_NAME") AS
select o.name, o.subname, o.obj#, o.dataobj#,
decode(o.type#, 0, 'NEXT OBJECT', 1, 'INDEX', 2, 'TABLE', 3, 'CLUSTER',
4, 'VIEW', 5, 'SYNONYM', 6, 'SEQUENCE',
7, 'PROCEDURE', 8, 'FUNCTION', 9, 'PACKAGE',
11, 'PACKAGE BODY', 12, 'TRIGGER',
<snip>

Weird dba|all_views text column content [duplicate]

I want to get the complete SQL code of the system views like USER_OBJECTS. However, when I execute the query below, it returns an error saying view not found in the SYS schema.
select dbms_metadata.get_ddl('VIEW', 'USER_OBJECTS', 'SYS') from dual;
When I execute the query below, it returns some codes in the text_vc column, but not the complete one. I cannot see the tables and where clause etc.
select * from ALL_VIEWS where VIEW_NAME = 'USER_OBJECTS';
But with this query, I can see that it is in the SYS schema with that name.
So, what is the reason that I cannot see the whole query? And is there a way to see it all?
+1 for looking at the definitions of the system views!
The first problem (DBMS_METADATA empty) is a privilege problem. According to the documentation, normal users will see only their own objects. You'll need the role SELECT_CATALOG_ROLE or EXP_FULL_DATABASE to see other users objects.
The second problem (SQL is not complete) comes from the datatype LONG, which - according to Oracle - should not be used anymore. However, it is still used by Oracle for view definitions, defaults, constraint text etc. Because it is so hard to handle, the view ALL_VIEWS has the original text in the LONG column and a truncated text, mostly the first 4000 characters, in the column text_vc, presumably for "text in varchar".
EDIT:
I believe you use Oracle 12 as you mention the column text_vc, which is not available in Oracle 11. Presumably, you are using a containerized database. If so, then please have a look at Data Dictionary Architecture in a CDB. Apparently, the definition of Oracle supplied things like views and packages are only visible in the root container. Sigh!!
In SQL*Plus, you'd set long (I shortened the output):
SQL> set pagesize 0
SQL> set long 10000
SQL>
SQL> select text from all_views where view_name = 'USER_OBJECTS';
select o.name, o.subname, o.obj#, o.dataobj#,
decode(o.type#, 0, 'NEXT OBJECT', 1, 'INDEX', 2, 'TABLE', 3, 'CLUSTER',
4, 'VIEW', 5, 'SYNONYM', 6, 'SEQUENCE',
7, 'PROCEDURE', 8, 'FUNCTION', 9, 'PACKAGE',
11, 'PACKAGE BODY', 12, 'TRIGGER',
<snip>
from sys."_CURRENT_EDITION_OBJ" o
where o.owner# = userenv('SCHEMAID')
and o.linkname is null
and (o.type# not in (1 /* INDEX - handled below */,
10 /* NON-EXISTENT */)
or
<snip>
union all
select l.name, NULL, to_number(null), to_number(null),
'DATABASE LINK',
l.ctime, to_date(null), NULL, 'VALID', 'N', 'N', 'N', NULL, NULL
from sys.link$ l
where l.owner# = userenv('SCHEMAID')
SQL>
Also, your first query works in my database (11g XE) if I'm connected as SYS:
SQL> show user
USER is "SYS"
SQL> select dbms_metadata.get_ddl('VIEW', 'USER_OBJECTS', 'SYS') from dual;
DBMS_METADATA.GET_DDL('VIEW','USER_OBJECTS','SYS')
--------------------------------------------------------------------------------
CREATE OR REPLACE FORCE VIEW "SYS"."USER_OBJECTS" ("OBJECT_NAME", "SUBOBJECT_N
AME", "OBJECT_ID", "DATA_OBJECT_ID", "OBJECT_TYPE", "CREATED", "LAST_DDL_TIME",
"TIMESTAMP", "STATUS", "TEMPORARY", "GENERATED", "SECONDARY", "NAMESPACE", "EDIT
ION_NAME") AS
select o.name, o.subname, o.obj#, o.dataobj#,
decode(o.type#, 0, 'NEXT OBJECT', 1, 'INDEX', 2, 'TABLE', 3, 'CLUSTER',
4, 'VIEW', 5, 'SYNONYM', 6, 'SEQUENCE',
7, 'PROCEDURE', 8, 'FUNCTION', 9, 'PACKAGE',
11, 'PACKAGE BODY', 12, 'TRIGGER',
<snip>

Is it possible to check whether a value is in the list of items in a single Oracle Decode Function?

I would like to know that if I can compare a value with a list of items in an decode function. Basically I want to know that if is it possible to make a decode statement's 'search' value a list. For example,
decode(task_id, (1,2,3), 3 * task_time)
This piece of code won't compile though. Is this the only option for this case then (without using case-when) or are there alternative ways of doing this?
decode(task_id, 1, 3 * task_time,
2, 3 * task_time,
3, 3 * task_time)
I am using Oracle 10gR2. Any help is much appreciated.
If a single list of values is sufficient, you can turn it into a CASE and IN clause:
case when task_id in (1, 2, 3) then 3 * task_time else null end
I don't think its possible to use a list with decode in this way. Per the docs:
DECODE compares expr to each search value one by one. If expr is equal
to a search, then Oracle Database returns the corresponding result. If
no match is found, then Oracle returns default
So task_id is compared with a search value one by one. If search value was a list, you couldn't compare with a single value.
I found a solution :)
select
decode(
task_id,
(select task_id from dual where task_id in (1,2,3)),
3*task_time)
decode ( (taskid-1)*(taskid-2)*(taskid-3), 0, 3 * tasktime ) could do what you want
Here's a working example:
with a as (
select 1 taskid, 11 tasktime from dual union all
select 2 taskid, 11 tasktime from dual union all
select 3 taskid, 11 tasktime from dual union all
select 4 taskid, 11 tasktime from dual
)
select
taskid,
decode (
(taskid-1) *
(taskid-2) *
(taskid-3) ,
0, 3 * tasktime
) decoded
from a;
you can use union all:
select 3 * task_time from your_table where task_id in (1,2,3)
union all
select task_time from your_table where task_id not in (1,2,3)
but why ?
In if condition :
IF vVal in (3,1,2) THEN
dbms_output.put_line('FOUND');
ELSE
dbms_output.put_line('NOT FOUND');
END IF;
I've seen instr(...) and static strings used as a way to quickly determine whether a value is one of multiple you're looking for as a condition for what value you return. You may need to choose a delimiter, but with limited datasets you can even omit it. It avoids using case-when, subqueries, and PL/SQL. As far as I know, there is no shorter way to do this:
decode(instr('123', taskid), 0, null, taskid * 3)
It's also very convenient when you want to set exceptions (for instance returning taskid without multiplication if it equals 1):
decode(instr('12345', taskid), 0, null, 1, taskid, taskid * 3)

Resources