DETERMINISTIC Functions in Oracle

July 8, 2019

in Keywords, Performance, PL/SQL

DETERMINISTIC functions in Oracle

A PL/SQL function in Oracle can be tagged with a DETERMINISTIC clause, to indicate that the function will always produce the same output for a given input and will have no side effects.

A little elaboration is in order here.

1. …will always produces the same output for a given input

Let’s see this with an example: a function get_primary_phone takes as input customer_id and returns the customer’s primary phone. Internally, the function executes SQL on a customer contact table, ranks and filters the result to get the customer’s primary phone number.

If the customer’s contact information changes in the table, the value returned by get_primary_phone(customer_id) will not produce the same output for the same input.

This implies that get_primary_phone(customer_id) cannot be deterministic.

In general, any function whose output depends on the contents of variables / conditions / tables / datasets not passed as IN parameters is non-deterministic.

So,
SYSDATE is non-deterministic – the current date and time will change depending on when SYSDATE is called, but
LAST_DAY(date) is deterministic – for a given input date, the last date of the month will be unchanged no matter when or how many times LAST_DAY(date) is called.

Another eligible deterministic function: Java arithmetic calculation.

2. …will have no side effects

This means that the function produces no other changes in local static variables, non-local variables, reference arguments, and performs no DML operations on other tables.

What’s the use of qualifying a function as DETERMINISTIC?

You may well ask. PL/SQL functions work fine without any of this DETERMINISTIC business: why, then, should we add this clause?

Here are a couple of reasons.

  • The DETERMINISTIC clause is a shorthand to document a function’s special properties – that its output depends wholly on its inputs and that it produces no side effects

  • Making a function DETERMINISTIC has a performance benefit – if the function is called multiple times within the scope of a single server call (e.g. execution of a PL/SQL block) with the same parameter values, the optimizer can choose to use the previously calculated result instead of invoking the function repeatedly.

More of the performance benefit in a demo below.

DETERMINISTIC functions: Syntax

Just plop in the word “DETERMINISTIC” after specifying the return type of the function.

Example: deterministic function is_number(p_value) – this checks if a string input is a valid number or not.

-- Function that tests if the input is a number
-- Returns 'Y' if number, else 'N'
create or replace function is_number 
(p_value in varchar2) 
return char 
deterministic 
is
   p_num number;
begin
   p_num := to_number(p_value);
   return 'Y';
exception
   when value_error then
      return 'N';
end;
/

DETERMINISTIC functions: Optimization in action

We’ll see how Oracle leverages a function’s deterministic property to avoid executing it multiple times for the same input.

For our tests, we will query a table EMPLOYEE (EMP_ID, EMP_NAME, DEPT) with 20 rows.

[Tests run on Oracle 11.2.0.2]

SQL> select * from employee;

EMP_ID EMP_NAME             DEPT
------ -------------------- ----------
   100 Steven King          515
   101 Neena Kochhar        515
   102 Lex De Haan          515
   103 Alexander Hunold     590
   104 Bruce Ernst          590
   105 David Austin         A90
   106 Valli Pataballa      590
   107 Diana Lorentz        590
   108 Nancy Greenberg      515
   109 Daniel Faviet        515
   110 John Chen            515
   111 Ismael Sciarra       515
   112 Jose Manuel Urman    A1A
   113 Luis Popp            515
   114 Den Raphaely         515
   115 Alexander Khoo       515
   116 Shelli Baida         515
   117 Sigal Tobias         515
   118 Guy Himuro           515
   119 Karen Colmenares     A1A

20 rows selected.

Column DEPT in this table is a string which may or may not be a number: we will call the function is_number(dept) defined above to ascertain if DEPT is a number or not.

SQL> select emp_id
  2       , emp_name
  3       , dept
  4       , is_number(dept) is_dept_numeric
  5  from employee;

EMP_ID EMP_NAME             DEPT       IS_DEPT_NUMERIC
------ -------------------- ---------- --------------------
   100 Steven King          515        Y
   101 Neena Kochhar        515        Y
   102 Lex De Haan          515        Y
   103 Alexander Hunold     590        Y
   104 Bruce Ernst          590        Y
   105 David Austin         A90        N
   106 Valli Pataballa      590        Y
   107 Diana Lorentz        590        Y
   108 Nancy Greenberg      515        Y
   109 Daniel Faviet        515        Y
   110 John Chen            515        Y
   111 Ismael Sciarra       515        Y
   112 Jose Manuel Urman    A1A        N
   113 Luis Popp            515        Y
   114 Den Raphaely         515        Y
   115 Alexander Khoo       515        Y
   116 Shelli Baida         515        Y
   117 Sigal Tobias         515        Y
   118 Guy Himuro           515        Y
   119 Karen Colmenares     A1A        N

20 rows selected.

Note that the same value of DEPT is repeated across several rows in the table: Oracle need not reevaluate the Y/N return for an input if it has done so already in the SQL for another row.

Without a DETERMINISTIC clause, Oracle will not be aware that it can avoid reevaluation, and proceed to execute is_number(dept) N times for N EMPLOYEE rows.

Let’s compare the SQL behavior before and after making the function DETERMINISTIC.

Before making the function DETERMINISTIC:

We will add a dbms_output.put_line to is_number(p_value), to check how many times the function gets invoked in our SQL.

Non-deterministic function:

-- Function that tests if the input is a number
-- Returns 'Y' if number, else 'N'
create or replace function is_number 
(p_value in varchar2) 
return char 
is
   p_num number;
begin
   dbms_output.put_line('is_number ('||p_value||') called');
   p_num := to_number(p_value);
   return 'Y';
exception
   when value_error then
      return 'N';
end;
/

Compile the function:

SQL> -- Function that tests if the input is a number
SQL> -- Returns 'Y' if number, else 'N'
SQL> -- non-deterministic
SQL> create or replace function is_number
  2  (p_value in varchar2)
  3  return char
  4  is
  5     p_num number;
  6  begin
  7     dbms_output.put_line('is_number ('||p_value||') called');
  8     p_num := to_number(p_value);
  9     return 'Y';
 10  exception
 11     when value_error then
 12        return 'N';
 13  end;
 14  /

Function created.

Now, run the SQL:

SQL> -- SQL result when the called function
SQL> -- is non-deterministic
SQL> select emp_id
  2       , emp_name
  3       , dept
  4       , is_number(dept) is_dept_numeric
  5  from employee;

EMP_ID EMP_NAME             DEPT       IS_DEPT_NUMERIC
------ -------------------- ---------- --------------------
   100 Steven King          515        Y
   101 Neena Kochhar        515        Y
   102 Lex De Haan          515        Y
   103 Alexander Hunold     590        Y
   104 Bruce Ernst          590        Y
   105 David Austin         A90        N
   106 Valli Pataballa      590        Y
   107 Diana Lorentz        590        Y
   108 Nancy Greenberg      515        Y
   109 Daniel Faviet        515        Y
   110 John Chen            515        Y
   111 Ismael Sciarra       515        Y
   112 Jose Manuel Urman    A1A        N
   113 Luis Popp            515        Y
   114 Den Raphaely         515        Y
   115 Alexander Khoo       515        Y
   116 Shelli Baida         515        Y
   117 Sigal Tobias         515        Y
   118 Guy Himuro           515        Y
   119 Karen Colmenares     A1A        N

20 rows selected.

is_number (515) called
is_number (515) called
is_number (515) called
is_number (590) called
is_number (590) called
is_number (A90) called
is_number (590) called
is_number (590) called
is_number (515) called
is_number (515) called
is_number (515) called
is_number (515) called
is_number (A1A) called
is_number (515) called
is_number (515) called
is_number (515) called
is_number (515) called
is_number (515) called
is_number (515) called
is_number (A1A) called

You can see that the non-deterministic function was called once per row i.e. 20 times for 20 rows.

After making the function DETERMINISTIC:

We will make exactly one change to the is_number(p_value) code: add the clause DETERMINISTIC.

Deterministic function:

-- Function that tests if the input is a number
-- Returns 'Y' if number, else 'N'
-- deterministic
create or replace function is_number 
(p_value in varchar2) 
return char 
deterministic
is
   p_num number;
begin
   dbms_output.put_line('is_number ('||p_value||') called');
   p_num := to_number(p_value);
   return 'Y';
exception
   when value_error then
      return 'N';
end;
/

Compile the function:

SQL> -- Function that tests if the input is a number
SQL> -- Returns 'Y' if number, else 'N'
SQL> -- determinitic
SQL> create or replace function is_number
  2  (p_value in varchar2)
  3  return char
  4  deterministic
  5  is
  6     p_num number;
  7  begin
  8     dbms_output.put_line('is_number ('||p_value||') called');
  9     p_num := to_number(p_value);
 10     return 'Y';
 11  exception
 12     when value_error then
 13        return 'N';
 14  end;
 15  /

Function created.

Now, run the SQL:

SQL> -- SQL result when the called function
SQL> -- is deterministic
SQL> select emp_id
  2       , emp_name
  3       , dept
  4       , is_number(dept) is_dept_numeric
  5  from employee;

EMP_ID EMP_NAME             DEPT       IS_DEPT_NUMERIC
------ -------------------- ---------- --------------------
   100 Steven King          515        Y
   101 Neena Kochhar        515        Y
   102 Lex De Haan          515        Y
   103 Alexander Hunold     590        Y
   104 Bruce Ernst          590        Y
   105 David Austin         A90        N
   106 Valli Pataballa      590        Y
   107 Diana Lorentz        590        Y
   108 Nancy Greenberg      515        Y
   109 Daniel Faviet        515        Y
   110 John Chen            515        Y
   111 Ismael Sciarra       515        Y
   112 Jose Manuel Urman    A1A        N
   113 Luis Popp            515        Y
   114 Den Raphaely         515        Y
   115 Alexander Khoo       515        Y
   116 Shelli Baida         515        Y
   117 Sigal Tobias         515        Y
   118 Guy Himuro           515        Y
   119 Karen Colmenares     A1A        N

20 rows selected.

is_number (515) called
is_number (515) called
is_number (590) called
is_number (A90) called
is_number (A1A) called
is_number (515) called
is_number (A1A) called

Lo! The non-deterministic function was called far fewer times this time round.

This difference in performance with the DETERMINISTIC clause can be significant when dealing with huge volumes of data.

Caution: Don’t mark non-deterministic functions as DETERMINISTIC!

As the saying goes, with great power comes great responsibility. Don’t let this performance benefit tempt you into marking non-deterministic functions as deterministic. Doing so might mislead Oracle to believe that the result of the function, once calculated, is good for reuse when in fact it isn’t — and you can end up with query results that are plain inaccurate.

Rule of thumb: If a function uses package variables or accesses the database in any way that might affect its output, do not make it deterministic.

Summary

This article describes when to use the DETERMINISTIC clause with Oracle functions and what optimization you might get when marking functions as deterministic.

For Further Reading

{ 2 comments… read them below or add one }

1 gary July 9, 2019 at 7:51 am

Just a correction “Regular expressions are NOT deterministic when you take NLS settings into account and thus cannot be used in constraints and/or function-based indexes.”

https://connor-mcdonald.com/2018/08/22/take-care-with-regular-expressions/

2 oratabler July 11, 2019 at 6:45 pm

@gary: Thanks for that. Something new learned today! Post updated to remove the regex reference.

Leave a Comment

Previous post: