How to call a sql function? - sql

I have written a function minimum2 which take two numbers as arguments and returns the minimum number. This function compiles without any error. when I call the function minimum(1,2); I get the errors PLS-00103. Here is the function and the function call:
CREATE OR REPLACE FUNCTION minimum2(v1 number, v2 number) RETURN number IS
BEGIN
IF v1 < v2 THEN
RETURN v1;
ELSE
RETURN v2;
END IF;
END;
--I call the function asl follow
minimum2(1,2);
What did I do wrong? I wrote this code in sql developer

You need to run a select
select minimum2(1,2)
from dual
You also need to end the function with a /:
For details on how and why to use the / see here
Are you aware that there is a built-in function for that?
select least(1,2)
from dual

--specifying 'in' and 'out' to parameters is important or it can work as it is, but best practice is to use:
CREATE OR REPLACE FUNCTION minimum2(v1 IN number, v2 IN number) RETURN number IS
BEGIN
IF v1 < v2 THEN
RETURN v1;
ELSE
RETURN v2;
END IF;
END;
--I call the function as follows through anonymous block and you can not call the function directly.
set serveroutput on;
begin
dbms_output.put_line('Output is ' || minimum2(1,2));
END;
/

Related

Toad oracle 10.5 [duplicate]

I have this anonymous PL/SQL block which calculates and prints a value return from a table.
DECLARE
U_ID NUMBER :=39;
RETAIL BINARY_FLOAT:=1;
FLAG NUMBER;
BEGIN
SELECT NVL(RETAIL_AMOUNT,1),UNIT_ID INTO RETAIL, FLAG FROM UNITS WHERE UNIT_ID=U_ID;
LOOP
SELECT NVL(MAX(UNIT_ID),U_ID) INTO FLAG FROM UNITS WHERE FATHER_ID=FLAG;
IF FLAG=U_ID THEN EXIT; END IF;
SELECT RETAIL* RETAIL_AMOUNT INTO RETAIL FROM UNITS WHERE UNIT_ID=FLAG;
EXIT WHEN FLAG=U_ID;
END LOOP;
DBMS_OUTPUT.PUT_LINE( RETAIL);
END;
This block work correctly, but I wanted to do the same thing using a PL/SQL Function
I wrote the function as follow:
CREATE OR REPLACE FUNCTION GET_UNIT_RETAIL(U_ID NUMBER)
RETURN NUMBER
IS
RETAIL BINARY_FLOAT:=1;
FLAG NUMBER;
BEGIN
SELECT NVL(RETAIL_AMOUNT,1),UNIT_ID
INTO RETAIL, FLAG
FROM UNITS
WHERE UNIT_ID=U_ID;
LOOP
SELECT NVL(MAX(UNIT_ID),U_ID)
INTO FLAG
FROM UNITS
WHERE FATHER_ID=FLAG;
IF FLAG=U_ID THEN
EXIT;
END IF;
SELECT RETAIL* RETAIL_AMOUNT
INTO RETAIL
FROM UNITS
WHERE UNIT_ID=FLAG;
EXIT WHEN FLAG=U_ID;
END LOOP;
RETURN NUMBER;
END;
/
When I try to execute the above code to save the function to the database, the environment (SQL*PLUS) hangs for a long time and at the end returns this error:
ERROR at line 1:
ORA-04021: timeout occurred while waiting to lock object
What is the problem ??? Please !
Sounds like ddl_lock problem
Take a look at
dba_ddl_locks to see who is "blocking" a create or replace.
Also try to create under different name - and see what happens.
The problem was because the Object GET_UNIT_RETAIL was busy by other environment
Here is the answer:
https://community.oracle.com/thread/2321256

How to shorter SQL function

Is it possible to shortner your SQL function like in C# or Java with lambda.
set serveroutput on;
CREATE OR REPLACE
FUNCTION Two(n IN INT) RETURN FLOAT AS
s FLOAT;
Begin
s:=0;
FOR i in 0..n LOOP
s:=s+1 / POWER(2,i);
END LOOP;
return s;
END Two;
/
Begin
DBMS_OUTPUT.PUT_LINE(Two(2));
END;
/
This is how my functions looks like, I feel it's kind of long for the job it does.
How could I shortner it ?
Is it possible to shortner your SQL function like in C# or Java with lambda.
No, there is no lambda syntax in PL/SQL.
How could I shortner it ?
You can just use 2 - 2-n:
CREATE FUNCTION Two(n IN INT) RETURN FLOAT
AS
BEGIN
RETURN 2 - POWER(2,-n);
END Two;
/
db<>fiddle here

Invalid Identifier When Trying to Use Package Function in Summation Expression

I'm trying to use the sum function with a package function but running into an "invalid identifier" bug. Here's some example code with the error causing function commented
create or replace type numType as object
(
myNum number
)
;
/
create or replace type numTypes is table of numType;
/
create or replace package testNumberPackage as
function ReturnNum(in_numType numType) return number;
end;
/
create or replace package body testNumberPackage as
function ReturnNum(in_numType numType) return number is
begin
return in_numType.myNum;
end;
end;
/
declare l_numTypes numTypes;
l_count number;
begin
l_numTypes := numTypes();
for i in 1 .. 100 loop
l_numTypes.extend(1);
l_numTypes(l_numTypes.last) := numType(i);
end loop;
select sum(n.myNum) into l_count from table(l_numTypes) n;
select sum(testNumberPackage.ReturnNum(n)) into l_count from table(l_numTypes) n; --causes the error
dbms_output.put_line(l_count);
end;
/
The exact error for this code is
ORA-06550: line 11, column 42
PL/SQL: ORA-00904: "N": invalid identifier
ORA-6550: line 11, column 3:
PL/SQL: SQL Statement ignored
Thanks for any help.
The first issue is that you can't pass a table into a parameter by using its alias. It doesn't even make sense to try doing that.
The next issue is how to get the column mynum that is returned from the table(l_numTypes) into the correct format to pass into testNumberPackage.ReturnNum, since it's of NUMBER datatype, and the function is expecting a numtype parameter.
To do that, you need to pass in an object with that column, like so: numtype(n.mynum).
The following works for me:
declare
l_numTypes numTypes;
l_count number;
begin
l_numTypes := numTypes();
for i in 1 .. 100 loop
l_numTypes.extend(1);
l_numTypes(l_numTypes.last) := numType(i);
end loop;
select sum(n.myNum) into l_count from table(l_numTypes) n;
select sum(testNumberPackage.ReturnNum(numtype(n.mynum))) into l_count from table(l_numTypes) n; --causes the error
dbms_output.put_line(l_count);
end;
/
5050
Clear as mud?

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

Oracle RLS/VPD with for loop in policy function

This is a followup to an older question about Oracle's row level security feature originally discussed here
I needed to modify the because the original code would return more than one results if the user was attached to multiple projects. So now I need to have multiple where conditions (ie where project = project_1 or project = project_2) passed to the security policy for this to work. To do this I tried modifying the code by using a for loop and it is not working...
--create function
create or replace function
table_access_policy
(obj_schema varchar2, obj_name varchar2) return varchar2
is
v_project_temp varchar2(9000);
begin
v_project_temp:= 'declare v_project varchar2(9000);
begin
v_project:= ''project = '';
for c in (select admin.access_list.project from admin.access_list where upper(admin.access_list.user_id) = SYS_CONTEXT (''USERENV'', ''SESSION_USER''))
loop
v_project := v_project || c.project_sn || '' or project = '' ;
end loop;
v_project := rtrim(v_project, '' or project = '');
end;';
return v_project_temp;
end;
The function saves/runs without any errors, but the policy itself throws an error when it's called. Is there a better way to do this?
Instead of putting the PL/SQL in a string you should run it and build up the v_project string to return. Such as:
--create function
create or replace function
table_access_policy
(obj_schema varchar2, obj_name varchar2) return varchar2
is
v_project varchar2(9000);
begin
v_project:= 'project = ';
for c in (select admin.access_list.project from admin.access_list where upper(admin.access_list.user_id) = SYS_CONTEXT ('USERENV', 'SESSION_USER'))
loop
v_project := v_project ||''''|| c.project_sn ||''''|||| ' or project = ' ;
end loop;
v_project := rtrim(v_project, ' or project = ');
return v_project;
end;
Ultimately the value that appears in v_project will go straight after a where in an SQL statement such as:
select * from data;
will become
select * from date where <v_project>;
So only something that follows a where should go in v_project.