Suppose I have a PL/SQL stored procedure as follows:
PROCEDURE do_something(foo VARCHAR2 DEFAULT NULL) IS
BEGIN
/* Do something */
END;
Now, suppose do_something is invoked two different ways:
/* Scenario 1: The 'foo' parameter defaults to NULL */
do_something();
/* Scenario 2: The 'foo' parameter is explicitly set to NULL */
do_something(foo => NULL)
How can I define the do_something procedure to determine which scenario is calling it?
Edit: Clarifying my intentions for this procedure:
FUNCTION find_customer(name VARCHAR2 DEFAULT NULL, number VARCHAR2 DEFAULT NULL) RETURN NUMBER IS
BEGIN
/* Query the "customer" table using only those parameters provided */
END;
Below are example uses of this procedure with the associated SQL clauses desired:
/* SELECT * FROM customer WHERE customer.name = 'Sam' */
find_customer(name => 'Sam')
/* SELECT * FROM customer WHERE customer.name = 'Sam' AND customer.number = '1588Z' */
find_customer(name => 'Sam', number => '1588Z')
/* SELECT * FROM customer WHERE customer.name = 'Sam' AND customer.number IS NULL */
find_customer(name => 'Sam', number => NULL)
/* SELECT * FROM customer WHERE customer.name IS NULL */
find_customer(name => NULL)
/* SELECT * FROM customer WHERE customer.name IS NULL AND customer.number IS NULL */
find_customer(name => NULL, number => NULL)
How about instead of defaulting to null, default the omitted parameter values to something you will never use in the real world? The values you use should belong to some domain so choose values outside that domain.
eg
PROCEDURE do_something(foo VARCHAR2 DEFAULT '*##') IS
l_foo VARCHAR2(32000); -- local copy of foo parm
BEGIN
IF foo = '*##' THEN
-- I know the parm was omitted
l_foo := NULL;
ELSE
l_foo := foo;
END IF;
END;
You could overload the procedure instead of using a default value:
PROCEDURE do_something(foo VARCHAR2) IS
BEGIN
/* Do something */
END;
PROCEDURE do_something IS
BEGIN
/* here you know: no argument. Then call do_something(null) */
END;
Related
I've looked high and low but have not been able to figure this out.
I have this function defined in Oracle:
FUNCTION MY_FUNCTION(
INPUTVAR1 IN OUT NUMBER,
INPUTVAR2 VARCHAR2
) RETURN NUMBER;
The person who created this function is basically returning the input parameter if it's not 0. Otherwise if INPUTVAR1 is 0, it will return a new value. I want that new value.
In T-SQL it's so simple it's stupid:
DECLARE #MyVar INT = 0;
SET #MyVar = MY_FUNCTION(#MyVar, NULL);
SELECT #MyVar as Result;
But In Oracle, I cannot figure out how to return the return value as a result. This is what I have so far:
DECLARE MyVar NUMBER := 0;
BEGIN
MyVar := MY_FUNCTION(INPUTVAR1 => MyVar, INPUTVAR2 => NULL)
END;
But I can't figure out how to use MyVar in the result. I definitely cannot select it. I tried DBMS_OUTPUT.PUT_LINE(MyVar); but no luck.
Anyone know how I can return the value of MyVar?
So I was able to find this solution which works only if you have 12c or above JDBC drivers:
DECLARE MyVar NUMBER := 0;
rc sys_refcursor;
BEGIN
MyVar := MY_FUNCTION(INPUTVAR1 => MyVar, INPUTVAR2 => NULL)
open rc for SELECT MyVar FROM dual;
dbms_sql.return_result(rc);
END;
I'd like to meet the person who thought it was a good idea to have a FUNCTION with both IN/OUT parameters and a return value.
Redefine the function to not having an OUT parameter:
CREATE FUNCTION MY_FUNCTION(
INPUTVAR1 IN NUMBER,
INPUTVAR2 IN VARCHAR2
) RETURN NUMBER
IS
BEGIN
RETURN INPUTVAR1;
END;
/
Then you can use it in an SQL query:
SELECT MY_FUNCTION( INPUTVAR1 => 0, INPUTVAR2 => NULL ) AS value
FROM DUAL
Output:
| VALUE |
| ----: |
| 0 |
db<>fiddle here
Update
If you cannot redefine the function to remove the OUT parameter then write a wrapper around it:
CREATE FUNCTION MY_FUNCTION_WRAPPER(
INPUTVAR1 IN NUMBER,
INPUTVAR2 IN VARCHAR2
) RETURN NUMBER
IS
var1 NUMBER := INPUTVAR1;
BEGIN
RETURN MY_FUNCTION( INPUTVAR1 => var1, INPUTVAR2 => INPUTVAR2 );
END;
/
Then you can call it from an SQL statement:
SELECT MY_FUNCTION_WRAPPER( INPUTVAR1 => 0, INPUTVAR2 => NULL ) AS value
FROM DUAL;
Output:
| VALUE |
| ----: |
| 0 |
db<>fiddle here
dbms_output works fine for me:
create or replace FUNCTION MY_FUNCTION(
INPUTVAR1 IN OUT NUMBER,
INPUTVAR2 VARCHAR2
) RETURN NUMBER is
begin
if inputvar1 = 0 then
return -1;
else
return inputvar1;
end if;
end;
/
declare
my_var number := 0;
my_var2 number := 0;
begin
my_var2 := my_function(my_var, 'A');
dbms_output.put_line('my_var:' || my_var);
dbms_output.put_line('my_var2:' || my_var2);
end;
/
my_var:0
my_var2:-1
Edit: I am a little concerned about having an OUT variable in a function. That's usually a sign of poor coding, which is why I used 2 variables above, so you could see whether the function was "returning" the new value by modifying INPUTVAR1, or by using the return value. Or both.
Your original Oracle code, with dbms_output, is correct. Basically, after calling a function you use the assigned variable exactly as if you had assigned it a value (same as SQL Server).
So a function call "x := some_function(y)" that returns a value (lets say 9), is after the call exactly the same as if you had written "x := 9". So again your function was correct.
However, you indicated you use SQL Developer. My guess is you didn't have dbms_output turned on. To do that on SQL Developer menu bar select View then click Dbms output. This will open a dbms_output window. But your not quite done. It the Dbms output window click the large green +, select the appropriate schema (if needed) and click ok. Dbms_output will now be shown in that window.
try this
DECLARE MyVar NUMBER := 0;
varout VARCHAR2();
BEGIN
--MyVar := MY_FUNCTION(INPUTVAR1 => MyVar, INPUTVAR2 => NULL)
select MY_FUNCTION(INPUTVAR1 => MyVar, INPUTVAR2 => NULL) INTO varout from dual;
dbms_output.put_line(MyVar);
END;
This link shows how to get a procedure/function variable's type in Oracle: View Type of a variable.
It does so through the function "get_plsql_type_name":
create or replace function get_plsql_type_name
(
p_object_name varchar2,
p_name varchar2
) return varchar2 is
v_type_name varchar2(4000);
begin
select reference.name into v_type_name
from user_identifiers declaration
join user_identifiers reference
on declaration.usage_id = reference.usage_context_id
and declaration.object_name = reference.object_name
where
declaration.object_name = p_object_name
and declaration.usage = 'DECLARATION'
and reference.usage = 'REFERENCE'
and declaration.name = p_name;
return v_type_name;
end;
/
alter session set plscope_settings = 'IDENTIFIERS:ALL';
create or replace type my_weird_type is object
(
a number
);
create or replace procedure test_procedure is
var1 number;
var2 integer;
var3 my_weird_type;
subtype my_subtype is pls_integer range 42 .. 43;
var4 my_subtype;
begin
dbms_output.put_line(get_plsql_type_name('TEST_PROCEDURE', 'VAR1'));
dbms_output.put_line(get_plsql_type_name('TEST_PROCEDURE', 'VAR2'));
dbms_output.put_line(get_plsql_type_name('TEST_PROCEDURE', 'VAR3'));
dbms_output.put_line(get_plsql_type_name('TEST_PROCEDURE', 'VAR4'));
end;
/
begin
test_procedure;
end;
/
The problem with the above method is that it is static and I need to verify the type of a variable that can be a subtype of the one declared in the procedure/function scope.
Using the above method I get the following.
Create the type and its subtype:
create or replace type my_weird_type is object
(
a number
) NOT FINAL;
CREATE OR REPLACE TYPE my_weird_subtype UNDER my_weird_type(
b number
);
/
Create a table and populates it:
create table test_my_weird_type(
x my_weird_type,
y my_weird_subtype
);
INSERT INTO test_my_weird_type (x,y) VALUES (my_weird_type(100),my_weird_subtype(100,200));
COMMIT;
Function creation (it has two my_weird_type parameters, and sometimes I am going need to use its subtypes):
create or replace function test_procedure (
inn_type my_weird_type,
out_subtype my_weird_type
) RETURN number is
var1 number;
var2 integer;
begin
dbms_output.put_line(get_plsql_type_name('TEST_PROCEDURE', 'VAR1'));
dbms_output.put_line(get_plsql_type_name('TEST_PROCEDURE', 'VAR2'));
dbms_output.put_line(get_plsql_type_name('TEST_PROCEDURE', 'INN_TYPE'));
dbms_output.put_line(get_plsql_type_name('TEST_PROCEDURE', 'OUT_SUBTYPE'));
return 1;
end;
/
The below query:
select test_procedure(x,y) from test_my_weird_type;
Gives the following output:
NUMBER
INTEGER
MY_WEIRD_TYPE
MY_WEIRD_TYPE
However, the right output is:
NUMBER
INTEGER
MY_WEIRD_TYPE
MY_WEIRD_SUBTYPE
The function needs to recognize which subtype is being used, therefore the
function "get_plsql_type_name" needs to be improved. Is there a way to do it?
You can't test the type based on the function specification but you can test the type of the passed in objects using the IS OF( type ) operator or the SYS_TYPEID function:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE type my_weird_type IS OBJECT
(
a NUMBER
) NOT FINAL
/
CREATE TYPE my_weird_subtype UNDER my_weird_type
(
b NUMBER
)
/
CREATE FUNCTION getType(
i_type my_weird_type
) RETURN VARCHAR2
IS
BEGIN
IF i_type IS OF( my_weird_subtype ) THEN
RETURN 'subtype';
ELSIF i_type IS OF( my_weird_type ) THEN
RETURN 'type';
ELSE
RETURN 'other';
END IF;
END;
/
CREATE FUNCTION getType2(
i_type my_weird_type
) RETURN VARCHAR2
IS
o_type USER_TYPES.TYPE_NAME%TYPE;
BEGIN
SELECT type_name
INTO o_type
FROM user_types
WHERE typeid = SYS_TYPEID( i_type );
RETURN o_type;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
END;
/
create table test_my_weird_type(
value my_weird_type
)
/
INSERT INTO test_my_weird_type (value)
SELECT my_weird_type(1) FROM DUAL UNION ALL
SELECT my_weird_subtype(2,3) FROM DUAL UNION ALL
SELECT NULL FROM DUAL
/
Query 1:
SELECT t.value.a AS a,
TREAT( t.value AS my_weird_subtype ).b AS b,
getType( value ),
getType2( value )
FROM test_my_weird_type t
Results:
| A | B | GETTYPE(VALUE) | GETTYPE2(VALUE) |
|--------|--------|----------------|------------------|
| 1 | (null) | type | MY_WEIRD_TYPE |
| 2 | 3 | subtype | MY_WEIRD_SUBTYPE |
| (null) | (null) | other | (null) |
The function needs to recognize wich subtype is beeing used, therefore
the function "get_plsql_type_name" needs to be improved. Is there a
way to do it?
No. there is no way. USER_IDENTIFIERS displays information about the identifiers in the stored objects like (Packages/Procedure/Function etc) owned by the current user.
Oracle doesnot provide any data dictionary for standalone Object created under SQL scope to identify TYPE and SUBTYPE. You can at max identify them as TYPE.
for example in your case the below one will only return TYPE even thought its a SUBTYPE.
SELECT *
FROM all_objects
WHERE object_name = 'MY_WEIRD_SUBTYPE'
Edit:
One other way i can think of is to check if for any Type you pass has a SUPERTYPE. If so then it would imply that the type is a subtype.
You can use a query like:
SELECT 1
FROM user_types
WHERE type_name = 'MY_WEIRD_SUBTYPE'
and supertype_name is not null;
You can implement this feature in your function to check if its a SUBTYPE or not
ANYDATA and ANYTYPE allow complete, dynamic control over Oracle objects. This approach is unrelated to the static code analysis approach.
For example, this function returns the real type name for any object input:
create or replace function get_dynamic_type_name(
p_anydata anydata
) return varchar2 is
v_typecode pls_integer;
v_anytype anytype;
v_prec pls_integer;
v_scale pls_integer;
v_len pls_integer;
v_csid pls_integer;
v_csfrm pls_integer;
v_schema_name varchar2(128);
v_type_name varchar2(128);
v_version varchar2(32767);
v_numelems pls_integer;
v_result pls_integer;
begin
v_typecode := p_anydata.getType(v_anytype);
v_result := v_anytype.GetInfo
(
prec => v_prec,
scale => v_scale,
len => v_len,
csid => v_csid,
csfrm => v_csfrm,
schema_name => v_schema_name,
type_name => v_type_name,
version => v_version,
numelems => v_numelems
);
return v_type_name;
end get_dynamic_type_name;
/
Before calling the function the objects must be converted with AnyData.ConvertObject:
select
get_type_name(AnyData.ConvertObject(x)) x_type,
get_type_name(AnyData.ConvertObject(y)) y_type
from test_my_weird_type;
X_TYPE Y_TYPE
------ ------
MY_WEIRD_TYPE MY_WEIRD_SUBTYPE
That function is probably not the most convenient way to simply get the type name. But it demonstrates how to use the ANY types to implement PL/SQL reflection and manipulate objects without knowing anything about them ahead of time. For example, my answer is based on my other answer here, which demonstrates how to find the first attribute of an object.
The ANY types are interesting but they should be used sparingly. It's usually faster and easier to use dynamic SQL to generate static code that handles the data, rather than doing all the processing in dynamic code. I try to avoid object-relational database features when possible. Make your schema smart but keep your columns dumb.
I have written this stored procedure in Oracle:
CREATE OR REPLACE FUNCTION GET_SOLVER_ID(username_in IN VARCHAR2)
RETURN NUMBER
IS
solver_id number(19);
system_user_id number(19);
BEGIN
SELECT id
INTO solver_id
FROM usr_solver
WHERE username = username_in;
select ID into system_user_id from USR_USER where USER_TYPE = 'X';
solver_id := nvl(solver_id, system_user_id);
RETURN(solver_id);
END;
When I call the function with username that doesn't exist in table usr_solver I get null for the result. I expect to get system_user_id instead.
It seems like the other select statement and nvl function in begin block didn't execute.
Could you help, I can't see the reason why...
Thanks,
mismas
This should do what you want
CREATE OR REPLACE FUNCTION GET_SOLVER_ID(
username_in IN VARCHAR2)
RETURN NUMBER
IS
some_id NUMBER(19);
BEGIN
BEGIN
SELECT id
INTO some_id
FROM usr_solver
WHERE username = username_in;
EXCEPTION
WHEN NO_DATA_FOUND THEN
SELECT ID
INTO some_id
FROM USR_USER
WHERE USER_TYPE = 'X';
END;
RETURN(some_id);
END;
I was executing query in Oracle Application Express and it was fine. Until I run my application, it gives me error
Error in PLSQL code raised during plug-in processing.
ORA-06550: line 4, column 1: PLS-00905: object
PURCHASEORDER.ACLSTUDENT_CUSTOM_AUTH is invalid ORA-06550: line 4,
column 1: PL/SQL: Statement ignored
and this is my sql
create or replace FUNCTION aclstudent_custom_auth (
p_username IN VARCHAR2(50),
p_password IN VARCHAR2(20))
RETURN boolean IS
valid boolean;
BEGIN
FOR c1 IN (SELECT 1 FROM students
WHERE upper(student_userid) = upper(p_username)
AND upper(student_last_name) = upper(p_password))
LOOP
valid := TRUE;
RETURN valid;
END LOOP;
valid := FALSE;
RETURN valid;
END;
Declaration of input parameters of the function is without length.
CREATE OR REPLACE FUNCTION aclstudent_custom_auth (
p_username IN VARCHAR2,
p_password IN VARCHAR2)
RETURN boolean IS
valid boolean;
BEGIN
FOR c1 IN (SELECT 1 FROM students
WHERE upper(student_userid) = upper(p_username)
AND upper(student_last_name) = upper(p_password))
LOOP
valid := TRUE;
RETURN valid;
END LOOP;
valid := FALSE;
RETURN valid;
END;
This must work better.
As Massie says, don't specify length in your arguments. Also, as hinted by Lalit, your use of a return variable as well as two RETURN statements is superfluous. You should either just RETURN the required BOOLEAN value in one of two places or set the variable and have one RETURN at the end of the function. I'd go with the first approach as your code will be more concise.
Incidentally, I don't like using loops to check for the existence of one record, but that's by the by.
CREATE OR REPLACE FUNCTION aclstudent_custom_auth(p_username IN VARCHAR2,
p_password IN VARCHAR2)
RETURN BOOLEAN IS
BEGIN
FOR c1 IN (SELECT 1
FROM students
WHERE upper(student_userid) = upper(p_username)
AND upper(student_last_name) = upper(p_password)) LOOP
RETURN TRUE;
END LOOP;
RETURN FALSE;
END;
OR
CREATE OR REPLACE FUNCTION aclstudent_custom_auth(p_username IN VARCHAR2,
p_password IN VARCHAR2)
RETURN BOOLEAN IS
valid BOOLEAN;
BEGIN
FOR c1 IN (SELECT 1
FROM students
WHERE upper(student_userid) = upper(p_username)
AND upper(student_last_name) = upper(p_password)) LOOP
valid := TRUE;
END LOOP;
valid := FALSE;
RETURN valid;
END;
A bit green with PL/SQL functions but trying to add a function where if the current person does not have an alt_id to get the relative alt_id via the application. Many thanks in advance.
CREATE OR REPLACE PROCEDURE CLIENT.p_Verification(pin_Member_ID IN dbo.member.member_id%Type,
pin_Person_ID IN dbo.person.person_id%type,
pin_user_id IN NUMBER default null,
pioc_ref_cursor IN OUT dbo.pkg_benefit_q.ref_cursor) IS
lv_member_id dbo.person.alt_identifier%Type;
-- other variables
...
Procedure p_get_other_deductions(...
Begin
...
end;
new function here
FUNCTION f_get_alt_id
RETURN Varchar2
IS-- dbo.person.alt_identifier%Type IS
alt_id Varchar2(10); --dbo.person.alt_identifier%Type;
BEGIN
SELECT p.ALT_IDENTIFIER
into alt_id
FROM DBO.PERSON p , DBO.MEMBER m, DBO.APPLICATION a
where p.PERSON_ID = m.PERSON_ID
AND m.MEMBER_ID = a.MEMBER_ID
AND a.APPLICATION_ID= cn_EntAppId
;
RETURN alt_id;
END;
calling it here...
Select dbo.pkg_benefit_s.f_get_value_description('PREFIX',
p.prefix,
'Y',
'N') || case
when p.prefix is not null then
' '
else
Null
end || nvl2(p.first_name, initcap(p.first_name) || ' ', '') ||
nvl2(p.middle_name, initcap(p.middle_name) || ' ', '') ||
nvl2(p.last_name, initcap(p.last_name), '') || ' ' ||
dbo.pkg_benefit_s.f_get_value_description('SUFFIX',
p.suffix,
'Y',
'N'),
nvl2(p.alt_identifier,
f_get_alt_id, -- errors here
''),
nvl2(p.first_name, initcap(p.first_name), ''),
nvl2(p.last_name, initcap(p.last_name), '')
into lv_member_name,
lv_member_id,
lv_member_first_name,
lv_member_last_name
From dbo.person p
where p.person_id = ln_person_id;
... more code
END p_Verification;
/
It would be better practice in a number of ways to create a package that has your procedure and your function listed in the header and the body.
CREATE OR REPLACE PACKAGE CLIENT IS
PROCEDURE p_Verification(pin_Member_ID IN dbo.member.member_id%Type,
pin_Person_ID IN dbo.person.person_id%type,
pin_user_id IN NUMBER default null,
pioc_ref_cursor IN OUT dbo.pkg_benefit_q.ref_cursor);
Procedure p_get_other_deductions(...
FUNCTION f_get_alt_id(alt_id_in IN NUMBER:= NULL) RETURN Varchar2;
END CLIENT;
CREATE OR REPLACE PACKAGE BODY CLIENT IS
END CLIENT;
Notice how I have added a null parameter so if you need to redesign the function you can do so without invalidating any dependent objects.
I added the
alt_id varchar(10) in the declaration section
restored the select query above to simply return
dbo.pkg_benefit_s.f_get_value_description('SUFFIX',
p.suffix,
'Y',
'N'),
p.alt_identifier,
nvl2(p.first_name, initcap(p.first_name), ''),
nvl2(p.last_name, initcap(p.last_name), '')
into lv_member_name,
lv_member_id,
lv_member_first_name,
lv_member_last_name
... everything else is the same...until
I added another section to set the lv_member_id
BEGIN
Select p.alt_identifier
into alt_id
from dbo.person p
where P.PERSON_ID = pin_Person_id;
if alt_id is not null
then
lv_member_id := alt_id;
else
lv_member_id := f_get_alt_id;
end if;
END;
... rest of procedure and end; -- it now compiles