Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  1. 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?
  2. 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???

like image 977
gfrobenius Avatar asked Feb 19 '14 22:02

gfrobenius


2 Answers

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;
/
like image 149
Jon Heller Avatar answered Nov 15 '22 00:11

Jon Heller


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

like image 24
nwitt Avatar answered Nov 15 '22 00:11

nwitt