SDO_INTERSECTION unable to read return properly - sql

So this is the documentation of SDO_INTERSECTION : https://docs.oracle.com/cd/B28359_01/appdev.111/b28400/sdo_objgeom.htm#SPATL1118
And this is my code:
PROCEDURE ciudadInterseccionCarretera (carretera1 IN VARCHAR2,carretera2 IN
VARCHAR2) IS
tupla Extremadura%ROWTYPE;
geomResultado SDO_GEOMETRY;
BEGIN
SELECT SDO_GEOM.SDO_INTERSECTION(ex1.Geom,ex2.Geom,0.05) INTO geomResultado
FROM Extremadura ex1, Extremadura ex2
WHERE ex1.Nombre = carretera1 AND ex2.Nombre = carretera2;
DBMS_OUTPUT.PUT_LINE('ciudad Intersección : '||geomResultado.SDO_GTYPE||' '||geomResultado.SDO_POINT.X);
END ciudadInterseccionCarretera;
ciudad Intersección : 2001
geomResultado is returning SDO_GTYPE properly when I execute the code, it returns 2001 as the INTERSECTION between carretera1 and carretera2 is a POINT. However I'm not able to return, for example the X coordinate of such POINT using geomResultado.SDO_POINT.X . Any ideas? I think my code is ok..
EDIT: Furthermore, when I try to use this code trying to find the intersection of two lines that doesn't really intersect, the result of the query is empty, so the problem is in SDO_POINT 100%.

Oracle has two ways of storing a point geometry
The first is in the sdo_point part of the sdo_geometry.
sdo_geometry(2001,null,sdo_point(x,y,null),null,null)
The other is as ordinates in the ordinate array:
sdo_geometry(2001,null,null,sdo_elem_info_array(1,1,1),sdo_ordinate_array(x,y))
In this case sdo_intersection returns a geometry with coordinates in the ordinate array. You have to get the values from there.
Example:
declare
l_geo1 sdo_geometry := sdo_geometry(2001
,null
,null
,sdo_elem_info_array(1, 2, 1)
,sdo_ordinate_array(0, 0, 10, 10));
l_geo2 sdo_geometry := sdo_geometry(2001
,null
,null
,sdo_elem_info_array(1, 2, 1)
,sdo_ordinate_array(0, 10, 10, 0));
l_geo3 sdo_geometry;
l_x number;
l_y number;
begin
l_geo3 := sdo_geom.sdo_intersection(l_geo1, l_geo2, 0.05);
l_x := l_geo3.sdo_ordinates(1);
l_y := l_geo3.sdo_ordinates(2);
dbms_output.put_line(l_x || ' ' || l_y);
end;

As Rene suggested, the solution of this problem was just to ask for the coordinates using:
geomResultado.SDO_ORDINATES(1);
geomResultado.SDO_ORDINATES(2);
For X and Y coordinates.

Related

Look up SDO_GEOMETRY validation error code using 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

Transforming Strings

I need to accept the following strings and put them into a type collection and pass it to a procedure
String Cars = Dodge Charger||Ford Mustang||Chevy Camro||Ford GT
String Cost = 35,000||25,000||29,000|55,000
String CarDesc = Power House||Sweet Ride||Too Cool||Blow your door off
How do I transform the records so they will be like the following?
Cars:
Dodge Charger||35,000||Power House
Ford Mustang||25,00||Sweet Ride
Chevy Camro||29,000||Too Cool
Ford GT||55,000||Blow your door off
How do I parse them into an array?
The types:
create or replace TYPE "CAR_OBJ"
AS
OBJECT (CAR_NAME VARCHAR2 (50),
Price Number,
CarDesc VARCHAR2 (100));
/
create or replace TYPE "CAR_IN_ARR"
IS
TABLE OF CAR_OBJ;
/
procedure car_values (
p_in_upd_car car_in_arr,
p_out_upd_results out car_out_cur
)
as
I have tried all kinds of for loops and I just can't get it in the right order
Thank you soo much
The double delimiter || makes this hard, so I cheated by replacing them with ~. Probably there is a neater way to handle this with a single regex, or a more elaborate approach using substr and instr.
Also I've assumed the cost example should be 35,000||25,000||29,000||55,000.
The real code should probably confirm that all the strings contain the same number of delimiters. Also you might want to parse the cost value into a number.
declare
inCars long := 'Dodge Charger||Ford Mustang||Chevy Camro||Ford GT';
inCost long := '35,000||25,000||29,000||55,000';
inCarDesc long := 'Power House||Sweet Ride||Too Cool||Blow your door off';
type varchar2_tt is table of varchar2(50);
cars varchar2_tt := varchar2_tt();
costs varchar2_tt := varchar2_tt();
carDescs varchar2_tt := varchar2_tt();
begin
inCars := replace(inCars,'||','~');
inCost := replace(inCost,'||','~');
inCarDesc := replace(inCarDesc,'||','~');
cars.extend(regexp_count(inCars,'~') +1);
costs.extend(regexp_count(inCost,'~') +1);
carDescs.extend(regexp_count(inCarDesc,'~') +1);
for i in 1..cars.count loop
cars(i) := regexp_substr(inCars,'[^~]+', 1, i);
costs(i) := regexp_substr(inCost,'[^~]+', 1, i);
carDescs(i) := regexp_substr(inCarDesc,'[^~]+', 1, i);
dbms_output.put_line(cars(i) || '||' || costs(i) || '||' || carDescs(i));
end loop;
end;

numeric or value error: character to number conversion error pl/sql

so I'm really just trying to output the lines to the 12 days of Christmas, just to get a handle on loops in PL/SQL, and I can't even seem to get a simple array declared correctly. Here is the code:
CREATE OR REPLACE
TYPE all_gifts is object (first_words varchar2(20), second_words varchar2(50));
/
DECLARE
type gifts is table of all_gifts;
type daysarray IS VARRAY(12) OF VARCHAR2(20);
lv_gifts GIFTS := gifts( all_gifts('and a','Partridge in a pear tree')
, all_gifts('Two','Turtle Doves')
, all_gifts('Three','French Hens')
, all_gifts('Four','Calling Birds')
, all_gifts('Five','Golden Rings')
, all_gifts('Six','Geese a laying')
, all_gifts('Seven','Swans a Swimming')
, all_gifts('Eight','Maids a milking')
, all_gifts('Nine','Ladies Dancing')
, all_gifts('Ten','Lords a leaping')
, all_gifts('Eleven','Pipers piping')
, all_gifts('Twelve','Drummers drumming')
);
second_array_elem varchar2(50);
lv_counter NUMBER := 1;
lv_days daysarray;
lv_first_word VARCHAR2(50);
BEGIN
lv_days := daysarray('First','Second','Third','Fourth','Fifth','Sixth','Seventh','Eigth','Ninth','Tenth','Eleventh','Twelefth');
FOR day in 1 .. lv_days.count loop
IF day != 'first' THEN
lv_first_word := second_array_elem;
ELSE
lv_first_word := 'A';
second_array_elem := lv_gifts.first;
END IF;
dbms_output.put_line('On the ['||lv_days(day)||'] day of Christmas');
second_array_elem := lv_gifts.next(second_array_elem);
END LOOP;
END;
/
And the console is throwing an error on the line right after my BEGIN statement (where I'm trying to declare the lv_days array) saying:
numeric or value error: character to number conversion error
But above, in my declare statement, I have it as a varying array of 12 indexes with the type of varchar2(20).
What simple step did I miss here?
Issue is with the below line of code..
IF day != 'first' THEN
As per the code, system is trying to compare the loop variable with string 'first'
You may use the below code
IF lv_days(day) != 'first' THEN

How to generate a random, unique, alphanumeric ID of length N in Postgres 9.6+?

I've seen a bunch of different solutions on StackOverflow that span many years and many Postgres versions, but with some of the newer features like gen_random_bytes I want to ask again to see if there is a simpler solution in newer versions.
Given IDs which contain a-zA-Z0-9, and vary in size depending on where they're used, like...
bTFTxFDPPq
tcgHAdW3BD
IIo11r9J0D
FUW5I8iCiS
uXolWvg49Co5EfCo
LOscuAZu37yV84Sa
YyrbwLTRDb01TmyE
HoQk3a6atGWRMCSA
HwHSZgGRStDMwnNXHk3FmLDEbWAHE1Q9
qgpDcrNSMg87ngwcXTaZ9iImoUmXhSAv
RVZjqdKvtoafLi1O5HlvlpJoKzGeKJYS
3Rls4DjWxJaLfIJyXIEpcjWuh51aHHtK
(Like the IDs that Stripe uses.)
How can you generate them randomly and safely (as far as reducing collisions and reducing predictability goes) with an easy way to specify different lengths for different use cases, in Postgres 9.6+?
I'm thinking that ideally the solution has a signature similar to:
generate_uid(size integer) returns text
Where size is customizable depending on your own tradeoffs for lowering the chance of collisions vs. reducing the string size for usability.
From what I can tell, it must use gen_random_bytes() instead of random() for true randomness, to reduce the chance that they can be guessed.
Thanks!
I know there's gen_random_uuid() for UUIDs, but I don't want to use them in this case. I'm looking for something that gives me IDs similar to what Stripe (or others) use, that look like: "id": "ch_19iRv22eZvKYlo2CAxkjuHxZ" that are as short as possible while still containing only alphanumeric characters.
This requirement is also why encode(gen_random_bytes(), 'hex') isn't quite right for this case, since it reduces the character set and thus forces me to increase the length of the strings to avoid collisions.
I'm currently doing this in the application layer, but I'm looking to move it into the database layer to reduce interdependencies. Here's what the Node.js code for doing it in the application layer might look like:
var crypto = require('crypto');
var set = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
function generate(length) {
var bytes = crypto.randomBytes(length);
var chars = [];
for (var i = 0; i < bytes.length; i++) {
chars.push(set[bytes[i] % set.length]);
}
return chars.join('');
}
Figured this out, here's a function that does it:
CREATE OR REPLACE FUNCTION generate_uid(size INT) RETURNS TEXT AS $$
DECLARE
characters TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
bytes BYTEA := gen_random_bytes(size);
l INT := length(characters);
i INT := 0;
output TEXT := '';
BEGIN
WHILE i < size LOOP
output := output || substr(characters, get_byte(bytes, i) % l + 1, 1);
i := i + 1;
END LOOP;
RETURN output;
END;
$$ LANGUAGE plpgsql VOLATILE;
And then to run it simply do:
generate_uid(10)
-- '3Rls4DjWxJ'
Warning
When doing this you need to be sure that the length of the IDs you are creating is sufficient to avoid collisions over time as the number of objects you've created grows, which can be counter-intuitive because of the Birthday Paradox. So you will likely want a length greater (or much greater) than 10 for any reasonably commonly created object, I just used 10 as a simple example.
Usage
With the function defined, you can use it in a table definition, like so:
CREATE TABLE users (
id TEXT PRIMARY KEY DEFAULT generate_uid(10),
name TEXT NOT NULL,
...
);
And then when inserting data, like so:
INSERT INTO users (name) VALUES ('ian');
INSERT INTO users (name) VALUES ('victor');
SELECT * FROM users;
It will automatically generate the id values:
id | name | ...
-----------+--------+-----
owmCAx552Q | ian |
ZIofD6l3X9 | victor |
Usage with a Prefix
Or maybe you want to add a prefix for convenience when looking at a single ID in the logs or in your debugger (similar to how Stripe does it), like so:
CREATE TABLE users (
id TEXT PRIMARY KEY DEFAULT ('user_' || generate_uid(10)),
name TEXT NOT NULL,
...
);
INSERT INTO users (name) VALUES ('ian');
INSERT INTO users (name) VALUES ('victor');
SELECT * FROM users;
id | name | ...
---------------+--------+-----
user_wABNZRD5Zk | ian |
user_ISzGcTVj8f | victor |
I'm looking for something that gives me "shortcodes" (similar to what Youtube uses for video IDs) that are as short as possible while still containing only alphanumeric characters.
This is a fundamentally different question from what you first asked. What you want here then is to put a serial type on the table, and to use hashids.org code for PostgreSQL.
This returns 1:1 with the unique number (serial)
Never repeats or has a chance of collision.
Also base62 [a-zA-Z0-9]
Code looks like this,
SELECT id, hash_encode(foo.id)
FROM foo; -- Result: jNl for 1001
SELECT hash_decode('jNl') -- returns 1001
This module also supports salts.
Review,
26 characters in [a-z]
26 characters in [A-Z]
10 characters in [0-9]
62 characters in [a-zA-Z0-9] (base62)
The function substring(string [from int] [for int]) looks useful.
So it looks something like this. First we demonstrate that we can take the random-range and pull from it.
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
1, -- 1 is 'a', 62 is '9'
1,
);
Now we need a range between 1 and 63
SELECT trunc(random()*62+1)::int+1
FROM generate_series(1,1e2) AS gs(x)
This gets us there.. Now we just have to join the two..
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1
1
)
FROM generate_series(1,1e2) AS gs(x);
Then we wrap it in an ARRAY constructor (because this is fast)
SELECT ARRAY(
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1,
1
)
FROM generate_series(1,1e2) AS gs(x)
);
And, we call array_to_string() to get a text.
SELECT array_to_string(
ARRAY(
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1,
1
)
FROM generate_series(1,1e2) AS gs(x)
)
, ''
);
From here we can even turn it into a function..
CREATE FUNCTION random_string(randomLength int)
RETURNS text AS $$
SELECT array_to_string(
ARRAY(
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1,
1
)
FROM generate_series(1,randomLength) AS gs(x)
)
, ''
)
$$ LANGUAGE SQL
RETURNS NULL ON NULL INPUT
VOLATILE LEAKPROOF;
and then
SELECT * FROM random_string(10);
Thanks to Evan Carroll answer, I took a look on hashids.org.
For Postgres you have to compile the extension or run some TSQL functions.
But for my needs, I created something simpler based on hashids ideas (short, unguessable, unique, custom alphabet, avoid curse words).
Shuffle alphabet:
CREATE OR REPLACE FUNCTION consistent_shuffle(alphabet TEXT, salt TEXT) RETURNS TEXT AS $$
DECLARE
SALT_LENGTH INT := length(salt);
integer INT = 0;
temp TEXT = '';
j INT = 0;
v INT := 0;
p INT := 0;
i INT := length(alphabet) - 1;
output TEXT := alphabet;
BEGIN
IF salt IS NULL OR length(LTRIM(RTRIM(salt))) = 0 THEN
RETURN alphabet;
END IF;
WHILE i > 0 LOOP
v := v % SALT_LENGTH;
integer := ASCII(substr(salt, v + 1, 1));
p := p + integer;
j := (integer + v + p) % i;
temp := substr(output, j + 1, 1);
output := substr(output, 1, j) || substr(output, i + 1, 1) || substr(output, j + 2);
output := substr(output, 1, i) || temp || substr(output, i + 2);
i := i - 1;
v := v + 1;
END LOOP;
RETURN output;
END;
$$ LANGUAGE plpgsql VOLATILE;
The main function:
CREATE OR REPLACE FUNCTION generate_uid(id INT, min_length INT, salt TEXT) RETURNS TEXT AS $$
DECLARE
clean_alphabet TEXT := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
curse_chars TEXT := 'csfhuit';
curse TEXT := curse_chars || UPPER(curse_chars);
alphabet TEXT := regexp_replace(clean_alphabet, '[' || curse || ']', '', 'gi');
shuffle_alphabet TEXT := consistent_shuffle(alphabet, salt);
char_length INT := length(alphabet);
output TEXT := '';
BEGIN
WHILE id != 0 LOOP
output := output || substr(shuffle_alphabet, (id % char_length) + 1, 1);
id := trunc(id / char_length);
END LOOP;
curse := consistent_shuffle(curse, output || salt);
output := RPAD(output, min_length, curse);
RETURN output;
END;
$$ LANGUAGE plpgsql VOLATILE;
How-to use examples:
-- 3: min-length
select generate_uid(123, 3, 'salt'); -- output: "0mH"
-- or as default value in a table
CREATE SEQUENCE IF NOT EXISTS my_id_serial START 1;
CREATE TABLE collections (
id TEXT PRIMARY KEY DEFAULT generate_uid(CAST (nextval('my_id_serial') AS INTEGER), 3, 'salt')
);
insert into collections DEFAULT VALUES ;
This query generate required string. Just change second parasmeter of generate_series to choose length of random string.
SELECT
string_agg(c, '')
FROM (
SELECT
chr(r + CASE WHEN r > 25 + 9 THEN 97 - 26 - 9 WHEN r > 9 THEN 64 - 9 ELSE 48 END) AS c
FROM (
SELECT
i,
(random() * 60)::int AS r
FROM
generate_series(0, 62) AS i
) AS a
ORDER BY i
) AS A;
So I had my own use-case for something like this. I am not proposing a solution to the top question, but if you are looking for something similar like I am, then try this out.
My use-case was that I needed to create a random external UUID (as a primary key) with as few characters as possible. Thankfully, the scenario did not have a requirement that a large amount of these would ever be needed (probably in the thousands only). Therefore a simple solution was a combination of using generate_uid() and checking to make sure that the next sequence was not already used.
Here is how I put it together:
CREATE OR REPLACE FUNCTION generate_id (
in length INT
, in for_table text
, in for_column text
, OUT next_id TEXT
) AS
$$
DECLARE
id_is_used BOOLEAN;
loop_count INT := 0;
characters TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
loop_length INT;
BEGIN
LOOP
next_id := '';
loop_length := 0;
WHILE loop_length < length LOOP
next_id := next_id || substr(characters, get_byte(gen_random_bytes(length), loop_length) % length(characters) + 1, 1);
loop_length := loop_length + 1;
END LOOP;
EXECUTE format('SELECT TRUE FROM %s WHERE %s = %s LIMIT 1', for_table, for_column, quote_literal(next_id)) into id_is_used;
EXIT WHEN id_is_used IS NULL;
loop_count := loop_count + 1;
IF loop_count > 100 THEN
RAISE EXCEPTION 'Too many loops. Might be reaching the practical limit for the given length.';
END IF;
END LOOP;
END
$$
LANGUAGE plpgsql
STABLE
;
here is an example table usage:
create table some_table (
id
TEXT
DEFAULT generate_id(6, 'some_table', 'id')
PRIMARY KEY
)
;
and a test to see how it breaks:
DO
$$
DECLARE
loop_count INT := 0;
BEGIN
-- WHILE LOOP
WHILE loop_count < 1000000
LOOP
INSERT INTO some_table VALUES (DEFAULT);
loop_count := loop_count + 1;
END LOOP;
END
$$ LANGUAGE plpgsql
;

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."