If you’re familiar with java, c, c# (or, I’m sure other languages as well) then you’ve probably encountered functions that allow a variable number of input parameters without requiring a fixed set of overloading.
Java
int my_variable_function(String str, int ...intlist)
c
int my_variable_function(char *str, …)
c#
int my_variable_function(string str, params int[] intlist)
PL/SQL does not expose a similar syntax for user-defined functions. However, it is possible to pass in a collection containing a variable number of values to achieve the same functionality, albeit with a slightly bulkier syntax.
For example…
FUNCTION my_variable_function(str IN VARCHAR2,
intlist IN inttab:= NULL)
RETURN INTEGER
Such a function could then be used as follows:
SELECT my_variable_function('test one'),
my_variable_function('test two', inttab(1, 2, 3)),
my_variable_function('test three', inttab()),
my_variable_function('test four', NULL),
my_variable_function('test five', inttab(9,8,7,6,5,4,3,2,1))
FROM DUAL;
One of the more common requests I’ve received that I solved with this technique is a multi-format date validator and/or date constructor. First, we need a collection type. In the example above I had a collection of integers, in this next one it will be a collection of varchar2 values, defined as follows:
CREATE TYPE vctab AS TABLE OF VARCHAR2(4000);
Then we can define a function with the collection parameter like this:
CREATE FUNCTION dynamic_to_date(p_str IN VARCHAR2,
p_formats IN vctab)
RETURN DATE
DETERMINISTIC
IS
v_temp DATE;
v_successful BOOLEAN := FALSE;
v_index INTEGER := p_formats.FIRST;
BEGIN
WHILE v_index <= p_formats.LAST AND NOT v_successful
LOOP
BEGIN
v_temp := TO_DATE(p_str, p_formats(v_index));
v_successful := TRUE;
EXCEPTION
WHEN OTHERS
THEN
-- Conversion failed, try next format in the list
v_index := v_index + 1;
END;
END LOOP;
IF v_successful
THEN
RETURN v_temp;
ELSE
RAISE VALUE_ERROR;
END IF;
END;
And then invoke it as follows:
SQL> SELECT dynamic_to_date('21 AUG 2009',
2 vctab('DD/MM/YYYY', 'DD/MM/YYYY HH24:MI', 'DD MON YYYY'))
3 FROM DUAL;
DYNAMIC_TO_DATE('21
-------------------
2009-08-21 00:00:00
SQL> SELECT dynamic_to_date('21 AUG 2009 AD',
2 vctab('DD/MM/YYYY', 'DD/MM/YYYY HH24:MI', 'DD MON YYYY'))
3 FROM dual;
SELECT dynamic_to_date('21 AUG 2009 AD',
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SDS.DYNAMIC_TO_DATE", line 26
Often when I build these types of functions I will include an optional parameter to provide alternate error handling. For example, returning an invoker-defined message on exception, or returning NULL.
CREATE FUNCTION dynamic_to_date(p_str IN VARCHAR2,
p_formats IN vctab,
p_on_error IN VARCHAR2:= NULL)
RETURN DATE
DETERMINISTIC
IS
v_temp DATE;
v_ok BOOLEAN := FALSE;
v_index INTEGER := p_formats.FIRST;
BEGIN
WHILE v_index <= p_formats.LAST AND NOT v_ok
LOOP
BEGIN
v_temp := TO_DATE(p_str, p_formats(v_index));
v_ok := TRUE;
EXCEPTION
WHEN OTHERS
THEN
-- Conversion failed, try next format in the list
v_index := v_index + 1;
END;
END LOOP;
IF v_ok
THEN
RETURN v_temp;
ELSIF p_on_error IS NULL
THEN
RETURN NULL;
ELSE
raise_application_error(-20001, p_on_error, FALSE);
END IF;
END dynamic_to_date;
Then we can use the function the same with success or different results on failure.
SQL> SELECT dynamic_to_date('21 AUG 2009',
2 vctab('DD/MM/YYYY', 'DD/MM/YYYY HH24:MI', 'DD MON YYYY'))
3 FROM DUAL;
DYNAMIC_TO_DATE('21
-------------------
2009-08-21 00:00:00
SQL> SELECT dynamic_to_date('21 AUG 2009 AD',
2 vctab('DD/MM/YYYY', 'DD/MM/YYYY HH24:MI', 'DD MON YYYY'))
3 FROM dual;
DYNAMIC_TO_DATE('21
-------------------
SQL> SELECT dynamic_to_date('21 AUG 2009 AD',
2 vctab('DD/MM/YYYY', 'DD/MM/YYYY HH24:MI', 'DD MON YYYY'),
3 'Input string does not match any of the supplied formats')
4 FROM dual;
SELECT dynamic_to_date('21 AUG 2009 AD',
*
ERROR at line 1:
ORA-20001: Input string does not match any of the supplied formats
ORA-06512: at "SDS.DYNAMIC_TO_DATE", line 29
ORA-06512: at line 1
Using the same idea you can extend your own functions with input collections of dates, numbers, user-defined types, or even ANYDATA values. I hope you find it useful.