ST_Boundary for Oracle Spatial - oracle

Is it possible to do the equivalent of ST_Boundary with Oracle Spatial? As in get a LINESTRING/MULTILINESTRING from a POLYGON, or a MULTIPOINT from a LINESTRING?
I know the other vendors support it:
https://postgis.net/docs/ST_Boundary.html
https://learn.microsoft.com/en-us/sql/t-sql/spatial-geometry/stboundary-geometry-data-type?view=sql-server-ver16

To get boundary, please see SDO_UTIL.POLYGONTOLINE
There is not a built in fn for linestring to multipoint. But here is a simple fn to do it (assuming 2D):
CREATE OR REPLACE FUNCTION multipoint_from_line (line_geom SDO_GEOMETRY)
RETURN SDO_GEOMETRY DETERMINISTIC PARALLEL_ENABLE IS
multipoint SDO_GEOMETRY := NULL;
BEGIN
IF line_geom IS NOT NULL
THEN
multipoint := line_geom;
IF multipoint.sdo_ordinates.count = 0
THEN
multipoint := NULL;
ELSE
multipoint.sdo_gtype := 2005;
multipoint.sdo_elem_info :=
sdo_elem_info_array(1,1,multipoint.sdo_ordinates.count/2);
END IF;
END IF;
return multipoint;
END;
/
-- Calling from SQL
SELECT multipoint_from_line
(sdo_geometry(2002,4326,null,sdo_elem_info_array(1,2,1),sdo_ordinate_array(0,0,1,1,3,3)))
FROM dual;
-- Calling from PL/SQL
DECLARE
multipoint SDO_GEOMETRY;
BEGIN
multipoint := multipoint_from_line
(sdo_geometry(2002,4326,null,sdo_elem_info_array(1,2,1),sdo_ordinate_array(0,0,1,1,3,3)));
END;
/

Related

Comparing data of any type without overloading

let's start with an example:
type Collection_t is table of varchar2(3);
Collection_v Collection_t := Collection_t('qwe', 'asd', 'yxc', 'rtz', 'fgh', 'vbn');
single_var varchar2(3) := 'yxc';
procedure get_position(Single_Value varchar2, Value_Set Collection_t) is
i integer := 1;
begin
while (i is not null) loop
if Single_Value = Value_Set(i)
then
dbms_output.put_line(i);
end if;
i := Value_Set.Next(i);
end loop;
end;
begin
get_position(single_var, Collection_v);
end;
now, question is: can I declare this procedure with 'anydata', and check if table (2ed argument) consists of the same type as first argument.
I'd assume declaration of a procedure would look like that:
procedure get_position(Single_Value anydata, Value_Set anydata) is ...
later I'd compare types, and honestly I couldn't figure it out, how to solve this problem.
From my limited understanding, you still have to encode/decode anydata into basic (or user-defined) PL/SQL types to do anything meaningful like comparing values, so you might not gain the benefits of dynamic languages like Python.
Here is an example, passing anydata as a parameter. You'll need to update anydata_to_varchar to handle data types you need.
Personally, overloading seems like a more straightforward approach, unless there is a better way to work with anydata values.
declare
type tab_anydata is table of anydata;
-- test as varchar2
/*
lookup_array tab_anydata := tab_anydata(
anydata.convertVarchar2('blah'),
anydata.convertVarchar2('meh'),
anydata.convertVarchar2('foo'),
anydata.convertVarchar2('bar')
);
lookup_value anydata := anydata.convertVarchar2('foo');
*/
-- test as date
lookup_array tab_anydata := tab_anydata(
anydata.convertDate(trunc(sysdate - 2)),
anydata.convertDate(trunc(sysdate - 0)),
anydata.convertDate(trunc(sysdate + 1)),
anydata.convertDate(trunc(sysdate + 2))
);
lookup_value anydata := anydata.convertDate(trunc(sysdate));
function anydata_to_varchar(p_what anydata)
return varchar2
is
value_type varchar2(30) := anydata.GetTypeName(p_what);
result varchar2(32767);
begin
select
case value_type
when 'SYS.VARCHAR2' then anydata.AccessVarchar2(p_what)
when 'SYS.DATE' then to_char(anydata.AccessDate(p_what), 'YYYY-MM-DD')
when 'SYS.NUMBER' then to_char(anydata.AccessNumber(p_what))
else null
end into result
from dual;
return result;
end;
function get_position(p_what anydata, p_where tab_anydata)
return number
is
pos number := -1;
what_value varchar2(30) := anydata_to_varchar(p_what);
curr_value varchar2(30);
begin
for i in 1..p_where.count
loop
curr_value := anydata_to_varchar(p_where(i));
if what_value = curr_value then
pos := i;
exit;
end if;
end loop;
return pos;
end;
begin
dbms_output.put_line('lookup type : '||anydata.GetTypeName(lookup_value));
dbms_output.put_line('lookup value : '||anydata_to_varchar(lookup_value));
dbms_output.put_line('found position: '||get_position(lookup_value, lookup_array));
end;
/
dbms_output:
lookup type : SYS.DATE
lookup value : 2022-07-20
found position: 2
db<>fiddle here

Oracle PL/SQL speed of NVL/LENGTH/TRIM calls versus IS NOT NULL AND != ' '

I try to find the best way to check if a CHAR/VARCHAR2 variable contains characters (NULL or spaces should be considered the same, as "no-value"):
I know there are several solutions, but it appears that (NVL(LENGTH(TRIM(v)),0) > 0) is faster than (v IS NOT NULL AND v != ' ')
Any idea why? Or did I do something wrong in my test code?
Tested with Oracle 18c on Linux, UTF-8 db charset ...
I get the following results:
time:+000000000 00:00:03.582731000
time:+000000000 00:00:02.494980000
set serveroutput on;
create or replace procedure test1
is
ts timestamp(3);
x integer;
y integer;
v char(500);
--v varchar2(500);
begin
ts := systimestamp;
--v := null;
v := 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
for x in 1..50000000
loop
if v is not null and v != ' ' then
y := x;
end if;
end loop;
dbms_output.put_line('time:' || (systimestamp - ts) ) ;
end;
/
create or replace procedure test2
is
ts timestamp(3);
x integer;
y integer;
v char(500);
--v varchar2(500);
begin
ts := systimestamp;
--v := null;
v := 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
for x in 1..50000000
loop
if nvl(length(trim(v)),0) > 0 then
y := x;
end if;
end loop;
dbms_output.put_line('time:' || (systimestamp - ts) ) ;
end;
/
begin
test1();
test2();
end;
/
drop procedure test1;
drop procedure test2;
quit;
The best practice is to ignore the speed difference between small functions and use whatever is easiest.
In realistic database programming, the time to run functions like NVL or IS NOT NULL is completely irrelevant compared to the time needed to read data from disk or the time needed to join data. If one function saves 1 seconds per 50 million rows, nobody will notice. Whereas if a SQL statement reads 50 million rows with a full table scan instead of using an index, or vice-versa, that could completely break an application.
It's unusual to care about these kinds of problems in a database. (But not impossible - if you have a specific use case, then please add it to the question.) If you really need optimal procedural code you may want to look into writing an external procedure in Java or C.

Wrong number or TYPES of arguments, error in PL/SQL

I have to create a list of RECORD and I need to send it to a procedure.
There is my header.
CREATE OR REPLACE PACKAGE tema4 IS
TYPE obj IS RECORD(id INTEGER := 0,percent INTEGER := 0);
TYPE listObj IS TABLE OF obj INDEX BY PLS_INTEGER;
PROCEDURE ex1 (p_listObj IN listObj);
END tema4;
My body.
create or replace PACKAGE BODY tema4 IS
PROCEDURE ex1 (p_listObj IN listObj) IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Cant reach');
END ex1;
END tema4;
And my code that calls procedure ex1.
DECLARE
TYPE obj IS RECORD(id INTEGER := 0,percent INTEGER := 0);
TYPE listObj IS TABLE OF obj INDEX BY PLS_INTEGER;
v_obj obj;
v_listObj listObj;
BEGIN
FOR v_i IN (SELECT ID,BURSA FROM STUDENTI ORDER BY ID) LOOP
v_obj.id := v_i.id;
v_obj.percent := 50;
v_listObj(v_i.id) := v_obj;
END LOOP;
FOR v_i IN v_listObj.FIRST..v_listObj.LAST LOOP
DBMS_OUTPUT.PUT_LINE(v_listObj(v_i).id || ' - ' ||
v_listObj(v_i).percent);
END LOOP;
tema4.ex1(v_listObj); --this line is with problems
END;
PLS-00306: wrong number or types of arguments in call to 'EX1'
Can someone explain me what is wrong in my code? I also tried to create my type as global, but it won't let me because of 'RECORD' keyword.
Don't declare the types again (new types), use the types already declared in the package spec:
DECLARE
v_obj tema4.obj;
v_listObj tema4.listObj;
BEGIN
FOR v_i IN (SELECT ID,BURSA FROM STUDENTI ORDER BY ID) LOOP
v_obj.id := v_i.id;
v_obj.percent := 50;
v_listObj(v_i.id) := v_obj;
END LOOP;
FOR v_i IN v_listObj.FIRST..v_listObj.LAST LOOP
DBMS_OUTPUT.PUT_LINE(v_listObj(v_i).id || ' - ' ||
v_listObj(v_i).percent);
END LOOP;
tema4.ex1(v_listObj); --this line is with problems
END;

PLSQL function if not available do else

In my oracle 11 environment I have declared following function (successfully, no errors).
create or replace function s2_encrypt(paramToEncrypt in VARCHAR2, encrypt8BYTEKey in RAW)
return RAW is encryptedReturnValue RAW (2000);
encryptionMode number := DBMS_CRYPTO.ENCRYPT_AES128 + DBMS_CRYPTO.CHAIN_CBC + DBMS_CRYPTO.PAD_PKCS5;
begin
encryptedReturnValue := dbms_crypto.encrypt(UTL_I18N.STRING_TO_RAW(paramToEncrypt, 'AL32UTF8'), encryptionMode, encrypt8BYTEKey);
return encryptedReturnValue;
end;
/
found here: http://anujktyagi.blogspot.ch/2012/12/oracle-using-dbmscrypto-package-for.html
I am deploying this on several servers, some of which only run Oracle version 8 / 9 -> in which case I want to just copy the value (instead of decrypting it).
How would I extend the above function so that it can be deployed both in Oracle 10+ and lower (in later case just copy paste the value and not encrypt)?
Something like
create or replace function s2_encrypt(paramToEncrypt in VARCHAR2, encrypt8BYTEKey in RAW)
return RAW is encryptedReturnValue RAW (2000);
encryptionMode number := DBMS_CRYPTO.ENCRYPT_AES128 + DBMS_CRYPTO.CHAIN_CBC + DBMS_CRYPTO.PAD_PKCS5;
begin
...
if !dbms_crypto.encrypt exit ???
...
encryptedReturnValue := dbms_crypto.encrypt(UTL_I18N.STRING_TO_RAW(paramToEncrypt, 'AL32UTF8'), encryptionMode, encrypt8BYTEKey);
return encryptedReturnValue;
end;
/
You can use conditional compilation
using package DBMS_DB_VERSION or some of your constants
$IF DBMS_DB_VERSION.ver_le_9_1 $THEN
return paramToEncrypt ;
$ELSE
encryptedReturnValue := dbms_crypto.encrypt(UTL_I18N.STRING_TO_RAW(paramToEncrypt, 'AL32UTF8'), encryptionMode, encrypt8BYTEKey);
return encryptedReturnValue;
$END
The most sensible approach would be to use two separate versions of the function - one for version 8/9 and one for 10.
That being said, the following approach should work (untested, I don't have access to any instances running Oracle 10 or lower):
check the database version (either by checking the existence of DBMS_DB_VERSION or by parsing the output of PRODUCT_COMPONENT_VERSION or V$VERSION which - to the best of my knowledge - already existed in version 8).
use dynamic SQL to either call dbms_crypto or return the string unchanged (since your package won't compile on 8/9 if you reference dbms_crypto directly)
Example (untested):
create or replace function s2_encrypt(paramToEncrypt in VARCHAR2,
encrypt8BYTEKey in RAW) return RAW is
encryptedReturnValue RAW(2000);
objectCount pls_integer;
begin
select count(*)
into objectCount
from all_objects
where object_name = 'DBMS_CRYPTO';
-- Oracle 8/9: return string unchanged
if objectCount = 0 then
encryptedReturnValue := paramToEncrypt;
else
execute immediate '
declare
encryptionMode number := DBMS_CRYPTO.ENCRYPT_AES128 + DBMS_CRYPTO.CHAIN_CBC + DBMS_CRYPTO.PAD_PKCS5;
begin
:encryptedReturnValue := dbms_crypto.encrypt(UTL_I18N.STRING_TO_RAW(:paramToEncrypt, ''AL32UTF8''), encryptionMode, :encrypt8BYTEKey);
end;'
using out encryptedReturnValue, in paramToEncrypt, in encrypt8BYTEKey;
end if;
return encryptedReturnValue;
end;
Usage (11g - 8i apparently did not have UTL_I18N, see comments)
select s2_encrypt(
'hello world',
UTL_I18N.STRING_TO_RAW ('8232E3F8BDE7703C', 'AL32UTF8'))
from dual;
You can use Conditional Compilation available from Oracle 10g Release 1.
See https://oracle-base.com/articles/10g/conditional-compilation-10gr2
In older versions you can just use a normal IF-ELSE statement
DECLARE
v1 VARCHAR2 (10);
v2 VARCHAR2 (10);
BEGIN
DBMS_UTILITY.db_version (v1, v2);
IF SUBSTR (v1, 1, INSTR (v1, '.', 1)-1) < 10 THEN
returnvalue paramToEncrypt;
ELSE
returnValue := dbms_crypto.encrypt(UTL_I18N.STRING_TO_RAW(paramToEncrypt, 'AL32UTF8'), encryptionMode, encrypt8BYTEKey);
END;
return returnValue;
END;

PL/SQL: Procedure not working correctly in a package

I'm working on a package with some procedures in them and I'm running into a bit of trouble. When I try to test the procedure to convert gallons to liters or the other procedures, it just prints out what was declared in the unnamed block instead of converting the numbers. Any ideas?
CREATE OR REPLACE PACKAGE eng_metric
IS
PROCEDURE convert(degree_fahrenheit IN OUT NUMBER,degree_celsius IN OUT NUMBER,measure IN VARCHAR2);
PROCEDURE convert(liters IN OUT NUMBER,gallons IN OUT NUMBER);
END eng_metric;
/
CREATE OR REPLACE PACKAGE BODY eng_metric
AS
PROCEDURE Convert
(degree_fahrenheit IN OUT NUMBER,
degree_celsius IN OUT NUMBER,
measure IN VARCHAR2)
IS
df NUMBER;
dc NUMBER;
convertf NUMBER;
measurecf VARCHAR2(4);
BEGIN
measurecf := measure;
df := degree_fahrenheit;
dc := degree_celsius;
IF measure = 'TEMP' THEN
IF dc = NULL THEN
convertf := ((df - 32) * .56);
degree_fahrenheit := convertf;
dbms_output.Put_line('The temperature in fahrenheit is '
||To_char(degree_fahrenheit));
ELSIF df = NULL THEN
convertf := (dc + 17.98) * 1.8;
degree_celsius := convertf;
END IF;
ELSE
dbms_output.Put_line('Invalid measure');
END IF;
END convert;
PROCEDURE Convert
(liters IN OUT NUMBER,
gallons IN OUT NUMBER)
IS
lit NUMBER;
gal NUMBER;
convertlg NUMBER;
BEGIN
lit := liters;
gal := gallons;
IF gal = NULL THEN
convertlg := (lit / 3.785);
liters := convertlg;
ELSIF lit = NULL THEN
convertlg := (gal * 3.785);
gallons := convertlg;
END IF;
END convert;
END eng_metric;
/
DECLARE
liters NUMBER := 25;
gallons NUMBER := 41;
nully NUMBER := NULL;
BEGIN
eng_metric.Convert(nully,gallons);
dbms_output.Put_line(To_char(gallons));
END;
/
Instead of
IF gal = NULL THEN
you need
IF gal IS NULL
What you have to remember is that NULL means "no value". It NEVER equals or fails to equal anything, including NULL. So you need to use IS NULL or IS NOT NULL, or use the NVL function to change the null to something that has a value.

Resources