Look up SDO_GEOMETRY validation error code using SQL - sql

I have a query that validates an SDO_GEOMETRY in Oracle 18c:
select
sdo_geom.validate_geometry_with_context(
sdo_geometry ('polygon ((676832.320 4857578.086, 665287.423 4857578.086, 665277.423 4878109.585,
676832.320 4878119.585, 676842.320 4857588.086))', 26917)
, 0.005) as validation
from
dual
VALIDATION
-----------------------------
13348 [Element <1>] [Ring <1>]
(1 row selected.)
db<>fiddle
The query produces an error code in a text column, but it doesn't describe what the code means.
I am able look up the error manually in the docs: 82 ORA-12700 to ORA-19400
ORA-13348: polygon boundary is not closed
Cause: The boundary of a
polygon does not close.
Action: Alter the coordinate values or the
definition of the SDO_GTYPE or SDO_ETYPE attribute of the geometry.
But manually looking up those error codes is inconvenient.
Is there a way to enhance the query so that it returns the full error description? (get the description from the database)

Assuming you can parse the string to pull out the error message, you can pass it to sqlerrm to get the text of the error (note that you're apparently getting a positive value, you'd need to negate that value to pass it to sqlerrm). I would assume that you could just look for everything before the first space to get the error number but I don't have a huge sample set to work with.
declare
l_message varchar2(1000);
begin
l_message := sqlerrm( -13348 );
dbms_output.put_line( l_message );
end;
/
will print
ORA-13348: polygon boundary is not closed

Building on #JustinCave's answer, here's a custom function that gets the error description from the validation text:
with function error_description(validation in varchar2) return varchar2 is
begin
return sqlerrm(substr(validation, 1, instr(validation,' ') - 1) * -1); --Multiply by -1. Oracle error codes seem to be "negative".
end;
select
error_description(validation) as error_description
from
(select
sdo_geom.validate_geometry_with_context(
sdo_geometry ('polygon ((676832.320 4857578.086, 665287.423 4857578.086, 665277.423 4878109.585, 676832.320 4878119.585, 676842.320 4857588.086))', 26917), 0.005) as validation
from dual)
ERROR_DESCRIPTION
-------------------
ORA-13348: polygon boundary is not closed
Edit:
As pointed out by #SolomonYakobson in a related post, the SQLERRM() function can also be used in a SELECT query (without the need for a custom function).
Are certain kinds of Oracle functions only available in PL/SQL, not
SQL?
Many of the functions are defined in Oracle supplied package
SYS.STANDARD.
Example: SELECT SYS.STANDARD.SQLERRM(-1422) FROM DUAL;
So we just need to fully qualify the function: SYS.STANDARD.SQLERRM()
select
sys.standard.sqlerrm(substr(validation, 1, instr(validation,' ') - 1) * -1) error_description
from
(select
sdo_geom.validate_geometry_with_context(
sdo_geometry ('polygon ((676832.320 4857578.086, 665287.423 4857578.086, 665277.423 4878109.585, 676832.320 4878119.585, 676842.320 4857588.086))', 26917), 0.005) as validation
from dual)
ERROR_DESCRIPTION
---------------------
ORA-13348: polygon boundary is not closed

Related

How to Insert data using cursor into new table having single column containing XML type data in Oracle?

I'm able to insert values into table 2 from table 1 and execute the PL/SQL procedure successfully but somehow the output is clunky. I don't know why?
Below is the code :
create table airports_2_xml
(
airport xmltype
);
declare
cursor insert_xml_cr is select * from airports_1_orcl;
begin
for i in insert_xml_cr
loop
insert into airports_2_xml values
(
xmlelement("OneAirport",
xmlelement("Rank", i.Rank) ||
xmlelement("airport",i.airport) ||
xmlelement("Location",i.Location) ||
xmlelement("Country", i.Country) ||
xmlelement("Code_iata",i.code_iata) ||
xmlelement("Code_icao", i.code_icao) ||
xmlelement("Total_Passenger",i.Total_Passenger) ||
xmlelement("Rank_change", i.Rank_change) ||
xmlelement("Percent_Change", i.Percent_change)
));
end loop;
end;
/
select * from airports_2_xml;
Output:
Why it is showing &lt ,&gt in the output ? And why am I unable to see the output fully?
Expected output:
<OneAirport>
<Rank>3</Rank>
<Airport>Dubai International</Airport>
<Location>Garhoud</Location>
<Country>United Arab Emirates</Country>
<Code_IATA>DXB</Code_IATA>
<Code_ICAO>OMDB</Code_ICAO>
<Total_passenger>88242099</Total_passenger>
<Rank_change>0</Rank_change>
<Percent_Change>5.5</Percent_Change>
</OneAirport>
The main issue is how you are constructnig the XML. You have an outer XMLElement for OneAirport, and the content of that element is a single string.
You are generating individual XMLElements from the cursor fields, but then you are concenating those together, which gives you a single string which still has the angle brackets you're expecting. So you're trying to do something like, simplified a bit:
select
xmlelement("OneAirport", '<Rank>1</Rank><airport>Hartsfield-Jackson</airport>')
from dual;
XMLELEMENT("ONEAIRPORT",'<RANK>1</RANK><AIRPORT>HARTSFIELD-JACKSON</AIRPORT>')
--------------------------------------------------------------------------------
<OneAirport><Rank>1</Rank><airport>Hartsfield-Jackson</airp
and by default XMLElement() escapes entities in the passed-in values, so the angle-brackets are being converted to 'safe' equivalents like <. If it didn't do that, or you told it not to with noentityescaping:
select xmlelement(noentityescaping "OneAirport", '<Rank>1</Rank><airport>Hartsfield-Jackson</airport>')
from dual;
XMLELEMENT(NOENTITYESCAPING"ONEAIRPORT",'<RANK>1</RANK><AIRPORT>HARTSFIELD-JACKS
--------------------------------------------------------------------------------
<OneAirport><Rank>1</Rank><airport>Hartsfield-Jackson</airport></OneAirport>
then that would appear to be better, but you still actually have a single element with a single string (with characters that are likely to cause problems down the line), rather than the XML structure you almost certainly intended.
A simple way to get an zctual structure is with XMLForest():
xmlelement("OneAirport",
xmlforest(i.Rank, i.airport, i.Location, i.Country, i.code_iata,
i.code_icao, i.Total_Passenger, i.Rank_change, i.Percent_change)
)
You don't need the cursor loop, or any PL/SQL; you can just do:
insert into airports_2_xml (airport)
select xmlelement("OneAirport",
xmlforest(i.Rank, i.airport, i.Location, i.Country, i.code_iata,
i.code_icao, i.Total_Passenger, i.Rank_change, i.Percent_change)
)
from airports_1_orcl i;
The secondary issue is the display. You'll see more data if you issue some formatting commands, such as:
set lines 120
set long 32767
set longchunk 32767
Those will tell your client to retrieve and show more of the long (XMLType here) data, rather the default 80 characters it's giving you now.
Once you are generating a nested XML structure you can use XMLSerialize() to display that more readable when you query your second table.
Try this below block :
declare
cursor insert_xml_cr is select * from airports_1_orcl;
v_airport_xml SYS.XMLTYPE;
begin
for i in insert_xml_cr
loop
SELECT XMLELEMENT ( "OneAirport",
XMLFOREST(i.Rank as "Rank"
,i.airport as "Airport"
,i.Location as "Location"
,i.Country as "Country"
,i.code_iata as "Code_iata"
,i.code_icao as "code_icao"
,i.Total_Passenger as "Total_Passenger"
, i.Rank_change as "Rank_change"
,i.Percent_change as "Percent_Change"
))
into v_airport_xml
FROM DUAL;
insert into airports_2_xml values (v_airport_xml);
end loop;
end;

Returning multiple values using function causing multiple query runs

We have kiosks for customers to check their purchase volume for two different categories of items. They will input their mobile number, which will send an OTP to their mobile numbers and they will input it back to authenticate, the system has to check the data and display for them. As a developer, the kiosk supplier has provided us with a limited functionality development kit by which we can execute select statement on the database and display the returned values on the kiosk.
I have created an object type as follows:
CREATE OR REPLACE TYPE rebate_values
AS
OBJECT (ASales_total number,
ACurrent_Rebate_Percent number,
ANeeded_Sales number,
ANext_Rebate_Percent number,
BSales_total number,
BCurrent_Rebate_Percent number,
BNeeded_Sales number,
BNext_Rebate_Percent number);
A function to which I will pass customers' mobile to get their sales and rebate information:
CREATE OR REPLACE FUNCTION AA_rebate_function (P_phone IN NUMBER)
RETURN rebate_values
IS
A_P_Sales_total NUMBER;
A_P_Current_Rebate_Percent NUMBER;
A_P_Needed_Sales NUMBER;
A_P_Next_Rebate_Percent NUMBER;
B_P_Sales_total NUMBER;
B_P_Current_Rebate_Percent NUMBER;
B_P_Needed_Sales NUMBER;
B_P_Next_Rebate_Percent NUMBER;
P_CODE VARCHAR (10);
BEGIN
SELECT CC_CODE
INTO P_CODE
FROM CUSTOMERS
WHERE C_MOBILE = P_phone;
FOR OUTDATA
IN (
--My Query to retrieve the data
Select ................
)
LOOP
IF OUTDATA.CLASS = 'X'
THEN
A_P_Sales_total := OUTDATA.SALES_TOTAL;
A_P_Current_Rebate_Percent := OUTDATA.CURRENT_REBATE_PERCENT;
A_P_Needed_Sales := OUTDATA.NEEDED_SALES_FOR_HIGHER_REBATE;
A_P_Next_Rebate_Percent := OUTDATA.NEXT_HIGHER_REBATE_PERCENT;
END IF;
IF OUTDATA.CLASS = 'Y'
THEN
B_P_Sales_total := OUTDATA.SALES_TOTAL;
B_P_Current_Rebate_Percent := OUTDATA.CURRENT_REBATE_PERCENT;
B_P_Needed_Sales := OUTDATA.NEEDED_SALES_FOR_HIGHER_REBATE;
B_P_Next_Rebate_Percent := OUTDATA.NEXT_HIGHER_REBATE_PERCENT;
END IF;
END LOOP;
RETURN rebate_values (A_P_Sales_total,
A_P_Current_Rebate_Percent,
A_P_Needed_Sales,
A_P_Next_Rebate_Percent,
B_P_Sales_total,
B_P_Current_Rebate_Percent,
B_P_Needed_Sales,
B_P_Next_Rebate_Percent);
END;
/
The query takes 27 seconds to retrieve the values for each customer. Each customer will have 2 rows, so that's why I have used LOOP to collect the values.
When I execute the function:
SELECT AA_rebate_function (XXXXXXXXXX) FROM DUAL;
I get data as follows in a single column within 27 seconds:
(XXXX, X, XXXX, X, XXXX, X, XXXX, X)
But when I execute the function to get the values in different columns, it takes 27 x 8 seconds = 216 seconds, i.e., approximately 3.6 minutes which is a big issue as the customer cannot wait for 3.6 minutes on the kiosk to view the data.
SELECT x.c.ASales_total,
x.c.ACurrent_Rebate_Percent,
x.c.ANeeded_Sales,
x.c.ANext_Rebate_Percent,
x.c.BSales_total,
x.c.BCurrent_Rebate_Percent,
x.c.BNeeded_Sales,
x.c.BNext_Rebate_Percent
FROM (SELECT AA_rebate_function (XXXXXXXXXX) c FROM DUAL) x;
I have tried using stored procedure with OUT values but it doesn't fit in my environment as I cannot program to execute stored procedures from the kiosk development toolkit because it only supports select statements, checked with the supplier and they don't have any plan to add that support in near future.
I tried converting the single field into multiple columns using REGEXP_SUBSTR but I get a type conversion error as it is an array.
The query is very complex and has to calculate data for the last 10 years and has millions of rows, 27 seconds is actually the optimum time to get the desired results.
Interesting! I didn't realize that when you query a function that returns an object, it runs the function once for each column you reference the object in. That's awkward.
The easiest solution I could find for this is to switch your function to be PIPELINED. You'll need to create a nested table type to do this.
create type rebate_values_t is table of rebate_values;
/
CREATE OR REPLACE FUNCTION AA_rebate_function (P_phone IN NUMBER)
RETURN rebate_values_t PIPELINED
IS
... your code here ...
PIPE ROW (rebate_values (A_P_Sales_total,
A_P_Current_Rebate_Percent,
A_P_Needed_Sales,
A_P_Next_Rebate_Percent,
B_P_Sales_total,
B_P_Current_Rebate_Percent,
B_P_Needed_Sales,
B_P_Next_Rebate_Percent));
RETURN;
END;
/
SELECT x.ASales_total,
x.ACurrent_Rebate_Percent,
x.ANeeded_Sales,
x.ANext_Rebate_Percent,
x.BSales_total,
x.BCurrent_Rebate_Percent,
x.BNeeded_Sales,
x.BNext_Rebate_Percent
FROM TABLE(AA_rebate_function (XXXXXXXXXX)) x;
For some reason, this should only execute the function once, and take 27 seconds.

How to call Oracle MD5 hash function?

I have below code. I am using Oracle 11g.
SELECT DBMS_OBFUSCATION_TOOLKIT.md5 (input => UTL_RAW.cast_to_raw(
FIRST_NAME
||LAST_NAME
)) md5_key ,
FIRST_NAME ,
LAST_NAME
FROM C_NAME_TAB
WHERE PKEY='1234'
How can i call this code? Can i directly execute this code in sqldeveloper?
In Oracle 12c you can use the function STANDARD_HASH. It does not require any additional privileges.
select standard_hash('foo', 'MD5') from dual;
The dbms_obfuscation_toolkit is deprecated (see Note here). You can use DBMS_CRYPTO directly:
select rawtohex(
DBMS_CRYPTO.Hash (
UTL_I18N.STRING_TO_RAW ('foo', 'AL32UTF8'),
2)
) from dual;
Output:
ACBD18DB4CC2F85CEDEF654FCCC4A4D8
Add a lower function call if needed. More on DBMS_CRYPTO.
I would do:
select DBMS_CRYPTO.HASH(rawtohex('foo') ,2) from dual;
output:
DBMS_CRYPTO.HASH(RAWTOHEX('FOO'),2)
--------------------------------------------------------------------------------
ACBD18DB4CC2F85CEDEF654FCCC4A4D8
#user755806 I do not believe that your question was answered. I took your code but used the 'foo' example string, added a lower function and also found the length of the hash returned. In sqlplus or Oracle's sql developer Java database client you can use this to call the md5sum of a value. The column formats clean up the presentation.
column hash_key format a34;
column hash_key_len format 999999;
select dbms_obfuscation_toolkit.md5(
input => UTL_RAW.cast_to_raw('foo')) as hash_key,
length(dbms_obfuscation_toolkit.md5(
input => UTL_RAW.cast_to_raw('foo'))) as hash_key_len
from dual;
The result set
HASH_KEY HASH_KEY_LEN
---------------------------------- ------------
acbd18db4cc2f85cedef654fccc4a4d8 32
is the same value that is returned from a Linux md5sum command.
echo -n foo | md5sum
acbd18db4cc2f85cedef654fccc4a4d8 -
Yes you can call or execute the sql statement directly in sqlplus or sql developer. I tested the sql statement in both clients against 11g.
You can use any C, C#, Java or other programming language that can send a statement to the database. It is the database on the other end of the call that needs to be able to understand the sql statement. In the case of 11 g, the code will work.
#tbone provides an excellent warning about the deprecation of the dbms_obfuscation_toolkit. However, that does not mean your code is unusable in 12c. It will work but you will want to eventually switch to dbms_crypto package. dbms_crypto is not available in my version of 11g.
To calculate MD5 hash of CLOB content field with my desired encoding without implicitly recoding content to AL32UTF8, I've used this code:
create or replace function clob2blob(AClob CLOB) return BLOB is
Result BLOB;
o1 integer;
o2 integer;
c integer;
w integer;
begin
o1 := 1;
o2 := 1;
c := 0;
w := 0;
DBMS_LOB.CreateTemporary(Result, true);
DBMS_LOB.ConvertToBlob(Result, AClob, length(AClob), o1, o2, 0, c, w);
return(Result);
end clob2blob;
/
update my_table t set t.hash = (rawtohex(DBMS_CRYPTO.Hash(clob2blob(t.content),2)));

How to Convert Output of listagg() function into XMLTYPE

I have one stored Procedure P_FP_GET_PATTERN.I expect output in following format
PATTERN_ID |PATTERN_NAME | SHIFT
1 Pattern 1 A,B,C,B,A
2 Pattern 2 C,B,A,C
I'm getting this output in SYS_REFCURSOR ALL_RESULT_SET.I need to pass it in XML format.But the moment i put that output in ALL_RESULT_SET_XML which is my out parameter of stored procedure of XMLTYPE using ALL_RESULT_SET_XML:= XMLTYPE(ALL_RESULT_SET);
Im getting an error as
Error encountered: ORA-31061: XDB error: special char to escaped char conversion failed.
I`m getting this error due to column shown using LISTAGG() function.Anybody can please tell me how to handle this ?
My stored Procedure
create or replace
PROCEDURE P_FP_GET_PATTERN
(
ALL_RESULT_SET_XML OUT XMLTYPE,
P_MESSAGE_ALL OUT VARCHAR2
)
AS
V_ERROR VARCHAR2(2000);
ALL_RESULT_SET SYS_REFCURSOR;
BEGIN
OPEN ALL_RESULT_SET FOR
SELECT PM.PATTERN_ID ,PM.PATTERN_NAME,
LISTAGG(SM.SHIFT_NUMBER) WITHIN GROUP (ORDER BY PD.INSTANCE_DAY) "SHIFT"
FROM T_FP_PATTERN_MASTER PM,
T_FP_PATTERN_DETAILS PD,
T_FP_SHIFT_MASTER SM
WHERE SM.SHIFT_ID= PD.SHIFT_ID
AND PM.PATTERN_ID = PD.PATTERN_ID
GROUP BY PM.PATTERN_NAME,PM.PATTERN_ID;
--Adding output in XML output parameter
ALL_RESULT_SET_XML:= XMLTYPE(ALL_RESULT_SET);
EXCEPTION
WHEN OTHERS
THEN
V_ERROR := SUBSTR(SQLERRM,1,1000);
P_MESSAGE_ALL := 'Error encountered: '||V_ERROR ;
END;

Adding Many (UDFs) Validation Functions to Oracle - Which Method Run Fastest

I have to move around 50+ validation functions into Oracle. I'm looking for the approach that runs fastest, but also would like to get around a boolean issue if possible. The return object for them all needs to be the same so that the application can react off the result in a consistent fashion and alert the user or display whatever popups, messages we may need. I created a valObj for this, but not sure yet if that is the best approach. The return format can be changed because the front-end that reacts off of it is not developed yet. In the end it will contain many different validation functions, from integer, number, phone, email, IPv4, IPv6, etc... This is what I have so far...
/***
This is the validation object.
It stores 1 for valid, 0 for not valid and some helper text that can be relayed back to the user.
***/
create or replace type valObj as object (
result number(1),
resultText varchar(32000)
);
/***
Coming from ColdFusion this seems clean to me but the function
will end up being a couple thousand lines long.
***/
create or replace function isValid(v in varchar2, format in varchar2)
return valObj
is
test number;
begin
if format = 'number' then
begin
test := to_number(v);
return valObj(1,null);
exception when VALUE_ERROR then return valObj(0,'Invalid number. Valid formats are: 12345, 12345.67, -12345, etc...');
end;
elsif format = 'integer' then
null; --TO DO
elsif format = 'email' then
null; --TO DO
elsif format = 'IPv4' then
null; --TO DO
elsif format = 'IPv6' then
null; --TO DO
end if;
--dozens of others to follow....
end;
/
/* Example Usage in SQL */
select isValid('blah','number') from dual; -- returns: (0, Invalid number. Valid formats are: 12345, 12345.67, -12345, etc...)
select isValid('blah','number').result from dual; -- returns: 0
select isValid('blah','number').resulttext from dual; -- returns: Valid formats are: 12345, 12345.67, -12345, etc...
select isValid(1234567890.123,'number') from dual; -- returns: 1,{null}
select isValid(1234567890.123,'number').result from dual; -- returns: 1
select isValid(1234567890.123,'number').resulttext from dual; -- returns: {null}
/* Example Usage in PL/SQL */
declare
temp valObj;
begin
temp := isValid('blah','number');
if (temp.result = 0) then
dbms_output.put_line(temp.resulttext);
else
dbms_output.put_line('Valid');
end if;
end;
/
My questions are:
When using it in PL/SQL I would love to be able to do boolean checks instead like this: if (temp.result) then but I can't figure out a way, cause that won't work in SQL. Should I just add a 3rd boolean attribute to the valObj or is there another way I don't know of?
These validation functions could end up being called within large loops. Knowing that, is this the most efficient way to accomplish these validations?
I'd appreciate any help. Thanks!
UPDATE: I forgot about MEMBER FUNCTIONS. Thanks #Brian McGinity for reminding me. So I'd like to go with this method since it keeps the type and its functions encapsulated together. Would there be any speed difference between this method and a stand-alone function? Would this be compiled and stored the same as a stand-alone function?
create or replace type isValid as object (
result number(1),
resulttext varchar2(32000),
constructor function isValid(v varchar, format varchar) return self as result );
/
create or replace type body isValid as
constructor function isValid(v varchar, format varchar) return self as result as
test number;
begin
if format = 'number' then
begin
test := to_number(v);
self.result := 1;
self.resulttext := null;
return;
exception when VALUE_ERROR then
self.result := 0;
self.resulttext := 'Invalid number. Valid formats are: 12345, 12345.67, -12345, etc...';
return;
end;
elsif format = 'phone' then
null; --TO DO
end if;
--and many others...
end;
end;
/
/* Example Usage in SQL */
select isValid('a','number') from dual;
/* Example Usage in PL/SQL */
declare
begin
if (isValid('a','number').result = 1) then
null;
end if;
end;
/
TEST RESULTS:
/* Test isValid (the object member function), this took 7 seconds to run */
declare
begin
for i in 1 .. 2000000 loop
if (isValid('blah','number').result = 1) then
null;
end if;
end loop;
end;
/* Test isValid2 (the stand-alone function), this took 16 seconds to run */
declare
begin
for i in 1 .. 2000000 loop
if (isValid2('blah','number').result = 1) then
null;
end if;
end loop;
end;
Both isValid and isValid2 do the same exact code, they just run this line test := to_number(v); then do the exception if it fails and return the result. Does this appear to be a valid test? The Object member function method is actually faster than a stand-alone function???
The stand-alone function can be much faster if you set it to DETERMINISTIC and if the data is highly repetitive. On my machine this setting decreased run time from 9 seconds to 0.1 seconds. For reasons I don't understand that setting does not improve performance of the object function.
create or replace function isValid2(v in varchar2, format in varchar2)
return valObj
deterministic --<< Hit the turbo button!
is
test number;
begin
if format = 'number' then
begin
test := to_number(v);
return valObj(1,null);
exception when VALUE_ERROR then return valObj(0,'Invalid number. Valid formats are: 12345, 12345.67, -12345, etc...');
end;
end if;
end;
/
May also want to consider utilizing pls_integer over number. Don't know if it will buy you much, but documents suggest some gain will be had.
http://docs.oracle.com/cd/B10500_01/appdev.920/a96624/03_types.htm states,
"You use the PLS_INTEGER datatype to store signed integers. Its magnitude range is -2*31 .. 2*31. PLS_INTEGER values require less storage than NUMBER values. Also, PLS_INTEGER operations use machine arithmetic, so they are faster than NUMBER and BINARY_INTEGER operations, which use library arithmetic. For efficiency, use PLS_INTEGER for all calculations that fall within its magnitude range."