Skip to Main Content

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Isaac.

Asked: December 19, 2002 - 10:23 am UTC

Last updated: August 04, 2017 - 7:20 am UTC

Version: 8.1.7.3

Viewed 50K+ times! This question is

You Asked

Hi Tom,

Recently I created a function with DETERMINISTIC definition to get performance on my query, but I check that the function is called every
time even that it receive the same input.

Here the script that I used to check this function:

CREATE OR REPLACE PACKAGE T2 AS
IND INTEGER;
END T2;
/
CREATE OR REPLACE
function Component_type
( i_blood_code in varchar2 ) return varchar2
DETERMINISTIC is
begin
T2.IND := T2.IND + 1;
if ( i_blood_code in ( 'WB', 'PC', 'PC BCP', 'GRAN' ) ) then
return( 'RED_CELLS' );
elsif( i_blood_code in ( 'PLT', 'SDP' ) ) then
return( 'PLT' );
elsif( i_blood_code in ( 'FFP', 'FFP CP', '2FFP', '2FFP CP', 'FP',
'2FP' ) ) then
return( 'PLASMA' );
elsif( i_blood_code in ( 'CRYO', '2CRYO' ) ) then
return( 'CRYO' );
else return( '' );
end if;
end;
/
create table ARCHIV_BLOOD_UNITS
( code varchar2(20))
/
BEGIN
for i in 1..50 loop
insert
into archiv_blood_units
values( 'PC' );
end loop;
commit;
T2.IND := 1;
END;
/
SELECT COMPONENT_TYPE(CODE) FROM ARCHIV_BLOOD_UNITS WHERE ROWNUM<50
/
BEGIN
dbms_output.put_line(T2.IND);
END;
/

The output that i received is: 50

Do you can help me, if I need a distinct definition or anything?

Thanks on advance

and Tom said...

it may well be deterministic however -- CODE is changing for each and every row! We don't know that you put 50 of the same one in there, we evaluate the function for each row..

In earlier releases of Oracle, deterministic is only used for function based indexes. I can make it so your query calls component_type 0 times during the query using that feature.

THis has been improved in later releases. See deterministic function caching which came in 10g here:

http://www.oracle.com/technetwork/issue-archive/2011/11-sep/o51asktom-453438.html


But, in your case -- I would NOT use a plsql function at all. I would:

create view v
as
select archiv_blood_units.*,
case when ( code in ( 'WB', 'PC', 'PC BCP', 'GRAN' ) )
then 'RED_CELLS'
when ( code in ( 'PLT', 'SDP' ) )
then 'PLT'
....
else NULL
end COMPONENT_TYPE
from archiv_blood_units
/




Rating

  (31 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Determinism

Connor McDonald, December 19, 2002 - 11:37 am UTC

I still don't see any particular benefit to the deterministic keyword. Yes, you're telling Oracle some information that it needs to know to make it candidate for FBI's, but we are still executing the function 'n' times when we create the index. All that the determinism keyword gives us is that if the function is in fact not deterministic, then its our fault not Oracle's for possibly getting a wrong answer.

I've not seen any example where Oracle has avoided having to run a deterministic function based on previously calculated values except in the FBI case, where we're effectively pre-calculating every possible return value in advance.

Maybe its just the semantics I don't like - the plsql manual implies that using "deterministic" could give a function a performance "edge" over a non-deterministic one. It infers that if I call my function twice with the same parameter value, it may avoid the second execution - something I've never observed.

Understanding your response

Isaac Chocron, December 22, 2002 - 3:18 am UTC

Sorry,
I can't understand your response.
I insert the same value ('PC') 50 times, on the table
ARCHIV_BLOOD_UNITS (see the pl/sql block before the query statment), so the function on the query receive 50 times the same CODE.
Also, it is a general function that I use on all my system on
differents SQL and PL/SQL uses.

Tom Kyte
December 22, 2002 - 9:22 am UTC

And how does the database now that the data in row two is the same as the data in row 1. It does not and it does not know to call the function once.

The only thing deterministic is used for right now is:

a) to mark a function as a candidate to be used in a function based index
b) to make a function as a candidate to be used in a materialized view with Query Rewrite

that is all. It will NOT prevent it from being called twice on a two row table that has the same value in both rows.

Here is a nice trick to reduce the number of times it is called.



select object_name, f(owner)
from
all_objects


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.02 0.03 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 1962 21.21 20.79 0 145428 0 29404
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 1964 21.23 20.83 0 145428 0 29404


select object_name, f_owner
from all_objects, ( select owner, max(f(owner)) f_owner
from (select owner from all_objects group by owner)
group by owner ) b
where all_objects.owner = b.owner

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 1962 4.03 4.06 0 287953 0 29404
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 1964 4.03 4.06 0 287953 0 29404


that'll run the function once per value of owner instead of once per row. In my case, it was 29,000+ times in the first one and 34 in the second one.


But again -- use CASE, hide it in a view, it'll be many many times faster.



The doco

Connor McDonald, December 22, 2002 - 4:33 pm UTC

Its this bit in the doco I don't like...

"This hint helps the optimizer avoid redundant function calls. If a stored function was called previously with the same arguments, the optimizer can elect to use the previous result"



Tom Kyte
December 22, 2002 - 4:57 pm UTC

Hey - it has an easy out "can elect", it just hasn't yet ;)

But actually it does.


select * from t where f(column) = 55;


if you index f(column), the optimizer may elect to use the previously computed resut -- stored in the index.

Or, if you have a materialized view, it can elect to use the previously computed result from that as well.

-

A reader, December 23, 2002 - 11:11 am UTC

Tom,

This query ("select object_name, f_owner from all_objects, ( select owner, max(f(owner)) f_owner from (select owner from all_objects group by owner)group by owner ) b
where all_objects.owner = b.owner") seems to have much higher Logical Reads than the other one. The query with the function call has less Logical reads but more response time.
Why do you prefer the query with higher Logical reads?

regards

Tom Kyte
December 23, 2002 - 11:25 am UTC

because of this:

total     1964     21.23      20.83          0     145428          0       29404

total     1964      4.03       4.06          0     287953          0       29404

4 cpu seconds, versus 21.  The overhead of calling PLSQL from SQL 29,404 times vs 34 times outweighed the LIO's.  

And, if there were a more efficient way to get the answer -- we should use it.  For example, knowing that all_users has all possible owners if we add PUBLIC.... and takes lots less LIO's -- 

ops$tkyte@ORA920.US.ORACLE.COM> set autotrace traceonly statistics
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select object_name, f_owner
  2    from all_objects, ( select owner, max(f(owner)) f_owner
  3                          from (select owner from all_objects group by owner)
  4                         group by owner ) b
  5   where all_objects.owner = b.owner
  6  /

29417 rows selected.


Statistics
----------------------------------------------------------
         34  recursive calls
          0  db block gets
     288061  consistent gets
          0  physical reads
          0  redo size
    1070651  bytes sent via SQL*Net to client
      22070  bytes received via SQL*Net from client
       1963  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      29417  rows processed

ops$tkyte@ORA920.US.ORACLE.COM> select object_name, f_owner
  2    from all_objects, ( select username owner, max(f(username)) f_owner
  3                          from (select username from all_users
  4                                union all
  5                                select 'PUBLIC' from dual)
  6                         group by username ) b
  7   where all_objects.owner = b.owner
  8  /

29417 rows selected.


Statistics
----------------------------------------------------------
         53  recursive calls
          0  db block gets
     145665  consistent gets
          0  physical reads
          0  redo size
    1070651  bytes sent via SQL*Net to client
      22070  bytes received via SQL*Net from client
       1963  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
      29417  rows processed



Remember -- I'm showing a technique here, apply it to your unique problem, with your data!
 

what 's going on here?

A reader, May 28, 2003 - 12:40 pm UTC

why is this function executed over and over again:

function RunOnlyOnce return number deterministic
is
begin
dbms_output.put_line('It''s me: ' || to_char(sysdate, 'dd.mm.yyyy hh24:mi:ss'));
return 1;
end RunOnlyOnce;

(or is it just the wrong way to verify the impact of the "deterministic" via dbms_output?)

Tom Kyte
May 28, 2003 - 7:20 pm UTC

deterministic only counts in a function based index. it is necessary to tell the index that "given the number "5" as input, this function will consistently return the same output"

it does not reduce the number of times it'll be called.

Taken from "Performance Tuning Guide and Reference"

j., May 31, 2003 - 10:54 am UTC

as for "How the CBO Evaluates DETERMINISTIC Functions":

<quote>
Calls to a DETERMINISTIC function might be replaced by the use of an already calculated value when the function is called multiple times within the same query, or if there is a function-based index or a materialized view defined that includes a relevant call to the function.
</quote>

j., June 02, 2003 - 4:27 am UTC

tom, did I misunderstand the documentation or did I just do something that prevented ORACLE from caching the return value? but what?
could you pls elaborate in which cases "might be replaced by the use of an already calculated value" would take place?

Tom Kyte
June 02, 2003 - 7:31 am UTC

they are used for function based indexes and materialized views if and when it so deems it wants to.

You had neither.

how to achieve that?

j, October 31, 2005 - 9:23 am UTC

we have the requirement to index some "part of" a raw type column (payload of a DIY-queue).

consider the following sample:

drop table MyTab ;
create table MyTab( MyData raw( 10 ) ) ;

insert into MyTab( MyData ) values( UTL_Raw.Cast_To_Raw( '0123456789' ) ) ;
commit ;

the following expression should be used by queries (and therefore be indexed):

select substr( UTL_Raw.Cast_To_Varchar2( MyData ), 3, 2 ) from MyTab ;

but FBI creation fails with ORA-30553 since UTL_Raw.Cast_To_Varchar2 is *NOT* deterministic:

create index XIEMyTab
on MyTab( substr( UTL_Raw.Cast_To_Varchar2( MyData ), 3, 2 ) ) ;

one *INCORRECT* implementation of such a feature would be to "wrap" that non-deterministic function with a user-defined function declared to be deterministic:

create or replace function GetStr( p_rawMyData MyTab.MyData%type )
return varchar2
deterministic
is
begin
return substr( UTL_Raw.Cast_To_Varchar2( p_rawMyData ), 3, 2 ) ;
end GetStr ;
/

... and then create the FBI on that ...
create index XIEMyTab on MyTab( GetStr( MyData ) ) ;
analyze table MyTab compute statistics for table for all indexes for all indexed columns ;

... that in turn would get used then ...
select *
from MyTab
where GetStr( MyData ) = '23' ;

... as can be proven by execution plan ...
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=22)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'MYTAB' (Cost=2 Card=1 Bytes=22)
2 1 INDEX (RANGE SCAN) OF 'XIEMYTAB' (NON-UNIQUE) (Cost=1 Card=1)

but how is the *CORRECT* implementation of such a feature?


Tom Kyte
November 01, 2005 - 3:41 am UTC

why does THIS

select substr( UTL_Raw.Cast_To_Varchar2( MyData ), 3, 2 ) from MyTab ;

imply something should be INDEXED????!??!!

And one CORRECT implementation is exactly to wrap the function which Oracle did not define as deterministic, but you know to be deterministic with your own function.

You have lots of assumptions that I "don't get".


But you do have the "answer", write your own "cast to raw", done (and, well, I cannot think of a single reason why this would not be "correct"?)


j., November 01, 2005 - 7:41 am UTC

thank you very much for your reply.

well, we want to index a certain small part of a much bigger raw type payload of our DIY-queue just in order to use that part of the message for dequeuing purposes ...

but i'm really not sure whether my solution is *already* complete/correct.

is it right to assume that my own wrapper function is deterministic (only) as long as any *implicit* character conversion performed by the wrapped NLS-dependent UTL_RAW routine can be avoided?

in my case there are no "special" characters (subject to conversion) contained within the payload.

but what if there were?
how could one handle this?
by providing certain NLS settings?


Tom Kyte
November 01, 2005 - 10:52 am UTC

it would be deterministic regardless of the NLS settings in this case.


another way to do this could be just to use rawtohex - to convert the raw into a hex string, substr off the hex strings you wanted - and index that (or convert the hex back to raw using hextoraw)

they are all builtins and know to be "deterministic"



j., November 01, 2005 - 4:05 pm UTC

yes, i've thought of that built-ins too -- but we want to use "text" (stored within the raw content) instead of hex strings for our queries ...

just to get you right: did your "... deterministic regardless of the NLS settings in this case" confirm that i'm just lucky since no conversion takes place in my case?

but what if such conversion may become an issue: would it be safe then to adjust NLS settings during the function's runtime to force the exact same output regardless of current NLS settings?


Tom Kyte
November 02, 2005 - 4:58 am UTC

right - but


hextoraw( substr( raw_column, 6, 2 ) )


is *raw*

it would not be hex. You would just bind a RAW type to query on that.


there is no conversion happening with the "cast to varchar2", it just treats whatever is in the raw as the string.

j., November 02, 2005 - 8:18 am UTC

hmmm, the very first and the very last sentence of your reply did confuse me:

i take your "right" (at the beginning) as a confirmation of my two statements:

* my wrapper function complies with its associated deterministic pragma as long as NO "special characters" (that are subject to NLS related conversions) are processed.

* if those "special characters" are to be processed then adjustment of NLS settings during the function's runtime is required.

but if your "right" means that why did you mention (at the end) that NO conversion takes place with "cast to varchar2"?

the following note, taken from documentation as for UTL_RAW.CAST_TO_VARCHAR2, made me think of that:
<quote>
When casting to a VARCHAR2, the current Globalization Support character set is used for the characters within that VARCHAR2.
</quote>

it fits with your "right" above but not your last comment.

could you please clarify this?

furthermore i'm really not sure if i did get your hextoraw idea at all.

if my wrapper function would return:

hextoraw( substr( p_rawMyData, 6, 2 ) )

instead of:

substr( UTL_Raw.Cast_To_Varchar2( p_rawMyData ), 3, 2 )

wouldn't that cause an *implicit* conversion too converting the internal raw result of the hextoraw built-in function into the wrapper function's return value of type varchar2?

thank you very much for your patience and assistance.

Tom Kyte
November 03, 2005 - 5:07 am UTC

as far as I know, the cast to varchar2 just changes the flag that says "i am raw" to "i am varchar2" and doesn't touch the data.

I've not observed it to do anything different.


that would cause an implicit conversion of each RAW bytes into two varchar2 HEX digits.


I would be afraid fo the substr( utl_raw...) with multi-byte character sets - as you might end up generating something that is not a valid character and that could cause problems.


I would encourage you to consider the hextoraw( substr( .... ))

convert the raw string into a SAFE varchar2 hex string.

get the hex byte you want (2 characters)

convert that back into raw.


I do not see any chance of NLS issues with that.

maybe i just don't see the wood for the trees ...

j., November 04, 2005 - 2:48 pm UTC

where do you know the UTL_RAW internals from?

are they undocumented (subject to changes without notification)?

and yes: you opened my eyes. with hextoraw applied to it our wrapper function would return two HEX digits for each raw byte -- which is something we DO NOT WANT (and we don't want any *implicit* conversion either).

our requirement is to index the *varchar2 value* of some part of a raw type column. that 's why we need a deterministic function that takes raw content as input and returns the varchar2 value of some certain part of that input. neither hex strings nor raw content are to be used within our queries ...

could you please show me how to achieve this by using built-ins (known to be deterministic)?

Tom Kyte
November 04, 2005 - 5:47 pm UTC

there is no such thing as "the varchar2 value" - that is totally character set specific. I don't think that is really what you mean.

You seem to want to take a BYTE and index that BYTE - using its ascii code.


be more specific, and make sure you know that a character in character set X might be BYTES (plural) M,N and that character in character set Y might be L in BYTES.


your "requirement" of "varchar2" and "deterministic" are not entirely compatible when we cross character sets (eg: you need to recode for each)


DO YOU need to find the raw where byte "x" is "decimal Y"

else, you are very much character set "unsafe"

Maybe you need to explain yourself more?

j., November 05, 2005 - 5:41 am UTC

but apart from such multi-byte character set issues is there any chance of running into problems when using single-byte character sets too?

first we have a varchar2 value of 'ABC' to be stored as raw:

select UTL_Raw.Cast_To_Raw( 'ABC' ) from dual ;

that raw's length is 3 bytes:

select UTL_Raw.length( UTL_Raw.Cast_To_Raw( 'ABC' ) ) from dual ;

and the corresponding hex string is '414243':

select UTL_Raw.Cast_To_Raw( 'ABC' ) from dual ;

if we are using a *single-byte* character set and are interested in the 2nd *character* ('B') of the text 'ABC' we 've stored as raw we could now:

* either extract the 2nd *byte* from this raw and convert it back into varchar2:

select UTL_Raw.Cast_To_Varchar2( UTL_Raw.substr( hextoraw( '414243' ), 2, 1 ) )
from dual ;

* or convert that raw into varchar2 and extract the 2nd *character* from that:

select substr( UTL_Raw.Cast_To_Varchar2( hextoraw( '414243' ) ), 2, 1 )
from dual ;

we do not only need to find the raw where "byte X is decimal Y" but the raw where byte "X" *maps* to *character* "B" -- which depends on the character set (isn't that the reason why UTL_RAW.Cast_To_Varchar2 is NON-deterministic?).

the users don't deal with raw, bytes, hex strings, but want to search for 'text' stored within raw.

so we *are* very much character set "unsafe" after all, right?

but how much character set "safe" could we get at all with this (and *how* do we get there)?

do we have the need, do we have any chance to control the conversion processes caused by character set settings on client and/or database side?


Tom Kyte
November 05, 2005 - 5:51 pm UTC

single byte - it'll just be whatever bits were there, utl_raw doesn't touch the "data", utl_raw touches the datatype, it just says "treat these raw bytes as varchar2's from now on"


If I were you (and I know, I am not), I would use substrs and hextoraw. Seems "simple" doesn't it?? hex - just hex, very straightforward, no conversion issues, questions all disappear - "poof". multibyte - not an issue.




utl_raw.cast_to_varchar2 is a safe cast ...

Alberto Dell'Era, November 05, 2005 - 1:19 pm UTC

Just noticed (on 10.2.0.1) that utl_raw.cast_to_varchar2 actually checks that the raw is a valid encoding (my database uses AL32UTF8 as the database charset):

dellera@ORACLE10> select utl_raw.cast_to_varchar2 (hextoraw ('EF')) from dual;
ERROR:
ORA-29275: partial multibyte character

So here's the reason why it isn't deterministic - change the (database ?) charset and you'll get different results (ie that the cast may turn from being successfull to fail, or vice-versa).

Tom Kyte
November 06, 2005 - 8:12 am UTC

well, that is still "deterministic" :)

but - I'll say it again, "hex" - they have raw data, use HEX - look for the specific hex bytes or if you don't like hex, use hextoraw to make it raw again, or if you don't like raw use to_number to make it decimal and if you don't like

a) hex strings
b) raws
c) decimal numbers

I cannot give any other answer.

self-correction: utl_raw.cast_to_varchar2 does not check

Alberto Dell'Era, November 06, 2005 - 4:54 am UTC

dellera@ORACLE10> select utl_raw.cast_to_varchar2 (hextoraw ('EF')) from dual;
ERROR:
ORA-29275: partial multibyte character

no rows selected

dellera@ORACLE10> declare
2 l_x varchar2(100);
3 begin
4 select utl_raw.cast_to_varchar2 (hextoraw ('EF')) into l_x from dual;
5 end;
6 /

PL/SQL procedure successfully completed.

Since it succeeds in the anonymous block, the failure in the first sql statement occurs after the invocation of utl_raw.cast_to_varchar2.

Sorry!

Nice discussion

Andrew Max, November 06, 2005 - 11:13 am UTC

Reading very first question to this thread, I failed to see why Isaac didnÂ’t use CASE instead of that PL/SQL function (btw, Tom pointed this out too).

Anyway, sometimes CASE and other built-in SQL function just do not help us to satisfy our demands -- then what we have to do is to "use a bit of PL/SQL".

As far as I know, we can not predict in all possible cases how many times this or that function in SQL query will be invoked (or can we???). But if we know that NDV (number of distinct values) for our CODE column is low compared to cardinality of our result set -- then we can make use of scalar subquery caching, I believe.

Consider:

SQL> create or replace function SomeFunc (code in varchar2) return varchar2 is
  2    l_ClientId number :=
  3      nvl(to_number(sys_context('USERENV','CLIENT_IDENTIFIER')), 0);
  4  begin
  5    dbms_session.set_identifier(to_char(l_ClientID + 1));
  6    -- For simplicity, just return that "code" parameter
  7    return code;
  8  end;
  9  /

Function created.

SQL> create table SomeTab as
  2  select 'AskTom' as Code
  3    from all_objects
  4   where rownum <= 1000;

Table created.

SQL> set autotrace traceonly stat
SQL>
SQL> select SomeFunc(code) from SomeTab;

1000 rows selected.


Statistics
----------------------------------------------------------
       1022  recursive calls
          0  db block gets
       3083  consistent gets
          2  physical reads
          0  redo size
      13424  bytes sent via SQL*Net to client
       1229  bytes received via SQL*Net from client
         68  SQL*Net roundtrips to/from client
          5  sorts (memory)
          0  sorts (disk)
       1000  rows processed

SQL> set autotrace off
SQL>
SQL> select sys_context('USERENV','CLIENT_IDENTIFIER') Cnt
  2    from dual;

CNT
------------------------------------------------------------
1000

ThatÂ’s what we might expect: function was called for each row though CODE column contained the same data for all rows.
Now, letÂ’s go on with scalar subquery:

SQL> exec dbms_session.set_identifier(null)

PL/SQL procedure successfully completed.

SQL> set autotrace traceonly stat
SQL>
SQL> select (select SomeFunc(code) from dual) from SomeTab;

1000 rows selected.


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
         77  consistent gets
          0  physical reads
          0  redo size
      13440  bytes sent via SQL*Net to client
       1229  bytes received via SQL*Net from client
         68  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1000  rows processed

SQL> set autotrace off
SQL>
SQL> select sys_context('USERENV','CLIENT_IDENTIFIER') Cnt
  2    from dual;

CNT
------------------------------------------------------------
1

ThatÂ’s what we need, right? Look at CNT and also at number of "recursive calls" -- they both went down.

Cheers,
Andrew. 

Tom Kyte
November 06, 2005 - 11:30 am UTC

Yes, you can use scalar subquery caching to reduce the number of calls.

use a materialized with clause

Andrew, November 08, 2005 - 12:34 am UTC

you could put your function calls into a materialized with clause then join back to it. ie.

assuming table t2 has a small # of distinct values in col1

rather than :

select t2.col2
from t2
where p1.f1(t2.col1) < 30
-- this calls the packed function p1.f1 for each row in t2

use:

with
temp1 as (select /*+ MATERIALIZE */distinct t2.col1 col1 from t2),
temp2 as (select p1.f1(col1) f1_col1, col1 from temp1)
select t2.col2
from t2, temp2
where temp2.col1 = t2.col1 and
f1_col1 < 30
;
-- this calls the packed function p1.f1 once for each distinct input value





Can scalar subquery be useful here??

A reader, November 29, 2005 - 12:57 pm UTC

Tom,

Can scalar subquery be useful here? So as to not
make many passes to the data?

select g.visit
,g.reference_id
,g.action
,g.ssl_user_code
,SUBSTR(ssl_PKG.get_ssl_name_FUNC(g.ssl_user_code),1,30) SSL_USER_DESCRIPTION
,DECODE(gate_PKG.get_DoubleMove_FUNC(g.gate_id),0,'N','Y') DOUBLE_MOVE
,v.processed_date
,h.slotted_date
,SUBSTR(work_order_PKG.get_elapsed_time(h.slotted_date, v.processed_date, 'HH:MI:SS'),1,15) TIME_TO_SLOT
,TO_NUMBER(work_order_PKG.get_elapsed_time(h.slotted_date, v.processed_date, 'MI')) TIME_TO_SLOT_NUM
,h.assigned_date
,SUBSTR(work_order_PKG.get_elapsed_time(h.assigned_date, h.slotted_date, 'HH:MI:SS'),1,15) TIME_TO_ASSIGN
,TO_NUMBER(work_order_PKG.get_elapsed_time(h.assigned_date, h.slotted_date, 'MI')) TIME_TO_ASSIGN_NUM
,h.completed_date
,SUBSTR(work_order_PKG.get_elapsed_time(h.completed_date, h.assigned_date, 'HH:MI:SS'),1,15) TIME_TO_COMPLETE
,TO_NUMBER(work_order_PKG.get_elapsed_time(h.completed_date, h.assigned_date, 'MI')) TIME_TO_COMPLETE_NUM
,v.lane_out_date
,SUBSTR(work_order_PKG.get_elapsed_time(v.lane_out_date, h.completed_date, 'HH:MI:SS'),1,15) TIME_TO_OUT_LANES
,TO_NUMBER(work_order_PKG.get_elapsed_time(v.lane_out_date, h.completed_date, 'MI')) TIME_TO_OUT_LANES_NUM
,v.guard_out_date
,SUBSTR(work_order_PKG.get_elapsed_time(v.guard_out_date, v.lane_out_date, 'HH:MI:SS'),1,15) TIME_TO_OUT_GUARD
,TO_NUMBER(work_order_PKG.get_elapsed_time(v.guard_out_date, v.lane_out_date, 'MI')) TIME_TO_OUT_GUARD_NUM
,SUBSTR(work_order_PKG.get_elapsed_time(v.guard_out_date, v.processed_date, 'HH:MI'),1,15) TOTAL_ELAPSED_TIME ,TO_NUMBER(work_order_PKG.get_elapsed_time(v.guard_out_date, v.processed_date, 'MI')) TOTAL_ELAPSED_TIME_NUM
from gate_containers g
,gate_visit v
,his_work_orders h
where g.visit = v.visit
and g.visit = h.visit
and g.reference_id = h.visit_ref_id

Tom Kyte
November 30, 2005 - 10:51 am UTC

I would definitely look for ways to eliminate the calls to plsql from sql if I was joining three tables together and retrieving all fo the rows - defintely.

I would "inline" those functions - get_elapsed_time, you probably can write that directly in sql, use a view to hide the "complexity" if you want. but get rid of the function calls!

Inline?

Fábio Oliveira, May 10, 2008 - 6:04 pm UTC

About the last comment of yours:
what do you mean by "inline" the function calls?


I'm having a problem of that kind. I have many function calls per row as my application has to translate almost every string stored (but not only that) and that's killing my application performance.
My solution was to use a Table Function to do all the function calls and reduce the context switching. This allows me to filter some calls too (for NULL values) and to cache some results in collections.
What do you think of this?
I'm thinking about using Scalar Subquery Caching and/or Deterministic functions in the future. Can you explain what are the requirements for Oracle to decide when to cache a function result in a scalar subquery? Can I cache a function that access data from a table?
I'm using ORACLE 10gr2.

Thanks in advance for your reply.
Tom Kyte
May 12, 2008 - 1:21 pm UTC

by doing what I did in the original answer - put the code right into sql, do not use plsql - there was no need for plsql at all, just INLINE the function.

Oracle will cache a function result using a scalar subquery - even if it accesses a table.


Just bear in mind, that might *change* what you see under some circumstances:

ops$tkyte%ORA11GR1> create table t as select 1 x from dual;

Table created.

ops$tkyte%ORA11GR1>
ops$tkyte%ORA11GR1> create or replace function f return number
  2  as
  3          pragma autonomous_transaction;
  4          l_x number;
  5  begin
  6          update t set x = x + 1 returning x into l_x;
  7          commit;
  8          return l_x;
  9  end;
 10  /

Function created.

ops$tkyte%ORA11GR1>
ops$tkyte%ORA11GR1> select f from scott.dept;

         F
----------
         2
         3
         4
         5

ops$tkyte%ORA11GR1> select (select f from dual) from scott.dept;

(SELECTFFROMDUAL)
-----------------
                6
                6
                6
                6

Deterministic functions and Result Caching

Badris, August 11, 2008 - 12:30 pm UTC

I was going through the Result caching feature of Oracle 11g. Just eager to know

What are the differences between this and the DETERMINISTIC option.

If RESULT CACHE...RELIES ON(table/view) is an enhanced feature of the DETERMINISTIC option then what are the limitations of the DETERMINISTIC functions.

Thanks in advance
Tom Kyte
August 12, 2008 - 8:39 am UTC

deterministic functions should always be good candidates for caching.

cached functions need not be deterministic.


deterministic functions always return the same results given the same inputs - cacheable functions do not need to have that quality - that is the purpose of the relies on.

so, there are no "limitations" of deterministic - you might just consider caching them, as they are infinitely cacheable by definition.

And you would consider caching other functions on a case by case basis.


Thanks

Badris, August 19, 2008 - 5:32 pm UTC

Thanks Tom for that explanation. Great as usual !!

Deterministic Function and Predicates

A reader, February 25, 2010 - 12:36 pm UTC

I ran the following test to simulate the use of sys_context in handling bind variables. I was surprised to find that the function for retrieving the bind value gets called for every row in the table! However, if I changed the function to be deterministic, the function only gets called once.

I can understand that the function gets called multiple times if it's a part of the SELECT list. However, why would it be called multiple times when it's part of the predicates? Is it the proper behavior? Can you please explain?


CREATE GLOBAL TEMPORARY TABLE Simulate_Sys_Context (
   Attribute  VARCHAR2(30)  NOT NULL,
   Value      INTEGER
)
ON COMMIT DELETE ROWS;

CREATE TABLE x (
   id   INTEGER NOT NULL,
   id2  INTEGER NOT NULL
);

BEGIN
   FOR i IN 1..1000 LOOP
      INSERT INTO x VALUES (1, MOD(i, 100));
   END LOOP;

   COMMIT;
END;
/

-- ----------------------------------------------------------------------
-- Function: returns the bind value.
-- ----------------------------------------------------------------------
CREATE OR REPLACE FUNCTION Get_Bind_Value (
   p_Attribute  IN  VARCHAR2
)
RETURN NUMBER
AS
   CURSOR c IS
      SELECT Value
      FROM   Simulate_Sys_Context
      WHERE  Attribute = p_Attribute;

BEGIN
   FOR x IN c LOOP
      RETURN x.Value;
   END LOOP;
END Get_Bind_Value;
/

-- ----------------------------------------------------------------------
DECLARE
   TYPE REFCURSOR IS REF CURSOR;
   l_Cursor       REFCURSOR;
   l_SQL          LONG;
   l_cnt          PLS_INTEGER;

BEGIN
   INSERT INTO Simulate_Sys_Context VALUES ('id2', 2);

   l_SQL :=
     'SELECT COUNT(*) cnt FROM x WHERE id2 = Get_Bind_Value(''id2'')';

   OPEN l_Cursor FOR l_SQL;
   FETCH l_Cursor INTO l_cnt;
   CLOSE l_Cursor;

   dbms_output.put_line(l_cnt);
END;
/

COMMIT;

********************************************************************************
From the trace file
********************************************************************************
SELECT COUNT(*) cnt 
FROM
 x WHERE id2 = Get_Bind_Value('id2')


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          1          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.06       0.09          0          7          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.06       0.09          0          8          0           1


SELECT VALUE 
FROM
 SIMULATE_SYS_CONTEXT WHERE ATTRIBUTE = :B1 


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute   1000      0.01       0.00          0          0          0           0
Fetch     1000      0.01       0.00          0       3000          0        1000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2000      0.03       0.00          0       3000          0        1000


********************************************************************************
After making Get_Bind_Value function deterministic, it only gets called once.
********************************************************************************

SELECT COUNT(*) cnt 
FROM
 x WHERE id2 = Get_Bind_Value('id2')


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          7          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          7          0           1

SELECT VALUE 
FROM
 SIMULATE_SYS_CONTEXT WHERE ATTRIBUTE = :B1 


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          3          0           1


Tom Kyte
March 01, 2010 - 11:11 am UTC

... I can understand that the function gets called multiple times if it's a part of the SELECT list. However, why would it be called multiple times when it's part of the predicates? Is it the proper behavior? Can you please explain? ...


any function might be called once per row, more than once per row, once per query, more than once per query, whatever. It is not predicable by us (I can usually tell - but it really isn't something you can rely on).

It is proper? Yes, it is the way it is supposed to work.


Index the column you search against and the function will be called twice - without deterministic - to get the start/stop keys for a range scan. That is correct.

Unindex the column and we'll TEND to call it once per row.


use a scalar subquery and we'll TEND to call it once per query per unique set of inputs (eg: code:

SELECT COUNT(*) cnt
FROM
x WHERE id2 = (select Get_Bind_Value('id2') from dual)

that uses scalar subquery caching (search for that term here first before asking about it...)


In general, plsql called from sql should probably:

a) use scalar subqueries
b) be defined DETERMINISTIC ONLY if it truly is deterministic
c) consider using function result caching in 11g


(a) is pretty much always true, even if you do (b) and (c)

A reader, December 03, 2010 - 7:50 am UTC

I don't understand the part of your original answer "it may well be deterministic however -- CODE is changing for each and every row! We
don't know that you put 50 of the same one in there, we evaluate the function for each
row..
"
In the example, we see that the same input has been inserted in the table 50 times. So at the moment of the call, we DO know that the input is the same as in the previous calls (after all, we're accessing the row right now).

To me, this answer would only make sense if the function itself accessed the table again and was thus not really deterministic (in which case, one should rather work with result_set relies on...).

And indeed, in 11g the output of the example code is "2" (because we started with 1 and added 1 for the only execution).
Tom Kyte
December 07, 2010 - 9:21 am UTC

... So at the moment of the call, we DO know that the input is the same as
in the previous calls (after all, we're accessing the row right now).
..


You know it
I know it

but the database doesn't know it until it hits it. We process data as we encounter it. it isn't like your data is copied somewhere to work on it, we work on it in place.

Starting in 10g, the DETERMINISTIC keyword was used to optimize things like this as well. That is what you are seeing in 11g. The database will reduce the number of times the function is called with the same inputs (reduce NOT eliminate all) in that single sql statement.

function more than one value

sunita mishra, December 04, 2010 - 11:36 pm UTC

HI Tom,

I read and got to know that when one value to return it is good to use function but in case more than one value it is good to use procedure(but we can use in case more than one value to return by the help of our parameters in function but it is written in book that it is poor programming practice).

And in some books it has been written is due to side effect not to use function in case of returning more than one value.(but they have not mentioned details about what type of side effects which confuses me and make me more anxious to know deeply about function).

Please tell me what is that side effect and why it is poor programming practice to use function for the same.

waiting for your reply,
sunita

Tom Kyte
December 07, 2010 - 9:30 am UTC

... but it is written in book that it is poor programming practice ...

If I write in another book that it is an OK programming practice - will that help?


And in some books it has been written is due to side effect not to use function
in case of returning more than one value.(but they have not mentioned details
about what type of side effects which confuses me and make me more anxious to
know deeply about function).


they are taking a purist approach to it. It is semantically unappealing to them to have a function which modifies its parameters (that is the side effects). They take the purist definition of a function which is loosely "a function is a thing that takes a set of inputs and returns an output". Whereas a procedure is "a thing that takes inputs and modifies them"


If you ask me, it is perfectly OK to have a function modify its inputs, it has never bothered me, there is no technical reason why it shouldn't.

It just needs to be documented and fortunately - it always is, the data dictionary tells you what are OUT parameters.

A reader, December 06, 2010 - 4:47 am UTC

Hi Tom,

one more question on this topic - I understand that deterministic functions will be called once to n times in a query for the same input.

How about multiple queries? I have a bit of a complicated calculation needed in a query, so I call a function:

create table foo(id number, price number, currency number);

create or replace function calculate_fx_rate(currency pls_integer)
is begin return 1.2345; end calculate_fx_rate;

select id, price*case when currency != 'USD' then calculate_fx_rate(currency)) else 1 end from foo;

using deterministic, I might speed up the query a bit. However, in reality, the function is not fully deterministic: the fx_rate may be different tomorrow. Without deterministic, everything works fine: The function is called for every row, and I KNOW it will return the same every time (the query is not that long-running that anything might change in the meantime). When I execute the query tomorrow, the function may return something else for the same input, which is the expected behaviour.

However, I'm a bit afraid that Oracle might think that since I now mark the function as deterministic, tomorrow's execution should return the same as yesterday.

So, to sum up, will a deterministic function also be called AT LEAST ONCE per query?
Tom Kyte
December 07, 2010 - 9:39 am UTC

... However, in reality, the
function is not fully deterministic: the fx_rate may be different tomorrow. ..


then do NOT claim it to be deterministic, you will be setting yourself up for complete failure.


However, in 11g DO mark it as "result_cache". As the data in the tables it queries change, we'll make sure everyone gets the current answer.


...So, to sum up, will a deterministic function also be called AT LEAST ONCE per
query? ...

No assurances on that - and it doesn't matter how many times you observe it being called at least once per query - it can change at anytime. Before 10g - deterministic had zero effect on reducing the number of calls - and then we 'optimized' that in 10g so that it did. We are free to further optimize it anytime we want.

Scalability with DETERMINISTIC

Vikram Rathour, October 06, 2011 - 11:18 pm UTC

Hi Tom,

In one of our application we have used SYS_CONTEXT to store some values that get used by all views to filter data.

e.g.
FUNCTION f_knowledge_time RETURN TIMESTAMP DETERMINISTIC IS





BEGIN

 RETURN SYS_CONTEXT ('APP_USR_CTX', 'KNOWLEDGE_TIME');

END f_knowledge_time;

CREATE OR REPLACE VIEW V_A_RD_DATA_ENTRY
AS
  SELECT 1 RN,
    V.RD_TYPE_CODE,
    V.RD_TYPE,
    V.RD_CODE,
    V.VERSION_VALID_FROM,
    V.CRE_USER,
    V.CRE_DATIM,
    V.IS_DELETING_VERSION,
    V.DELETES_VERSION_ID,
    V.CODE_1,
    V.CODE_2,
    VERSION_VALID_UNTIL AS version_valid_till,
    CAST ( MULTISET
    (SELECT RD_TYPE_CODE,
      VERSION_ID,
      LANGUAGE_CODE,
      SHORT_DESCRIPTION,
      LONG_DESCRIPTION
    FROM RD_DATA_ENTRY_T
    WHERE RD_DATA_ENTRY_T.RD_TYPE_CODE = V.RD_TYPE_CODE
    AND RD_DATA_ENTRY_T.VERSION_ID     = V.VERSION_ID
    ) AS RD_DATA_ENTRY_T_TAB_TYPE) RD_DATA_ENTRY_T
  FROM RD_DATA_ENTRY_V v
  JOIN RD_DATA_ENTRY_DT dt
  ON dt.RD_TYPE_CODE = v.RD_TYPE_CODE
  AND f_knowledge_time BETWEEN dt.CRE_DATIM AND dt.TERM_DATIM
  WHERE 1 = 1
  AND f_knowledge_time BETWEEN v.CRE_DATIM AND v.TERM_DATIM;

Suggested Approach
--------------------
CREATE OR REPLACE VIEW V_A_RD_DATA_ENTRY
AS
  SELECT 1 RN,
    V.RD_TYPE_CODE,
    V.RD_TYPE,
    V.RD_CODE,
    V.VERSION_VALID_FROM,
    V.CRE_USER,
    V.CRE_DATIM,
    V.IS_DELETING_VERSION,
    V.DELETES_VERSION_ID,
    V.CODE_1,
    V.CODE_2,
    VERSION_VALID_UNTIL AS version_valid_till,
    CAST ( MULTISET
    (SELECT RD_TYPE_CODE,
      VERSION_ID,
      LANGUAGE_CODE,
      SHORT_DESCRIPTION,
      LONG_DESCRIPTION
    FROM RD_DATA_ENTRY_T
    WHERE RD_DATA_ENTRY_T.RD_TYPE_CODE = V.RD_TYPE_CODE
    AND RD_DATA_ENTRY_T.VERSION_ID     = V.VERSION_ID
    ) AS RD_DATA_ENTRY_T_TAB_TYPE) RD_DATA_ENTRY_T
  FROM RD_DATA_ENTRY_V v
  JOIN RD_DATA_ENTRY_DT dt
  ON dt.RD_TYPE_CODE = v.RD_TYPE_CODE
AND SYS_CONTEXT ('APP_USR_CTX', 'KNOWLEDGE_TIME') BETWEEN dt.CRE_DATIM AND dt.TERM_DATIM
  WHERE 1 = 1
  AND SYS_CONTEXT ('APP_USR_CTX', 'KNOWLEDGE_TIME') BETWEEN v.CRE_DATIM AND v.TERM_DATIM;




However, there has been an issue raised:
"
The currently used deterministic function doesn't allow any scability with PQ-server.
Scalibility in this context can only be achieved with a direct usage of the SYS_CONTEXT-function.
The used deterministic function has the advantage that it is called in contrast to the SYS_CONTEXT-function only 1 x time per query. For mass-data volume (> 10 mio records) this is a advantage."

I am not able to understand how do I approach to test this scenario

Regards,
Vikram R

Tom Kyte
October 06, 2011 - 11:39 pm UTC

I fail to understand why this function:

FUNCTION f_knowledge_time RETURN TIMESTAMP DETERMINISTIC IS
BEGIN
RETURN SYS_CONTEXT ('APP_USR_CTX', 'KNOWLEDGE_TIME');
END f_knowledge_time;

exists at all?

It is just about the slowest way (deterministic or not) to do this.

And by the way - IT IS NOT DETERMINISTIC. Think about it. What is the definition of deterministic? that function isn't it.


Just reference sys_context and be done with it please.


why would you even do that? It can only make it much slower than just invoking sys_context and as defined would prevent parallel operations.

A deterministic function is NOT called once per query for sure. It can be called more than once - see:

http://www.oracle.com/technetwork/issue-archive/2011/11-sep/o51asktom-453438.html

However, since we treat sys_context like a bind variable pretty much - sys_context WOULD be called once per query.

Please just reference sys_context and don't create a function like that in plsql - when you can use things that are "pure sql" - without having to resort to plsql - do so.



and if:
BETWEEN v.CRE_DATIM AND
v.TERM_DATIM;

those columns are dates/timestamps - you better use to_date/to_timestamp with an explicit format to convert the sys_context string to a date/timestamp!!!! do no EVER rely on implicit conversions with default formats.

Scalability due to SYS_CONTEXT

Vikram Rathour, October 07, 2011 - 7:28 am UTC

We have another issue with this function and also the SYS_CONTEXT-function, they manuipulate the estimated CBO-statistics.
One of the table has arround 5 mio known records the estimation because of these functions is 12566 records (see below Execution-Plan). As result the wrong table is used in this execution-plan as driving-table (FI_NON_SPLITTING_CL_V instead of the much smaller KM_FI_INST_PKX table) which leads to a poorer performance.

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | CREATE TABLE STATEMENT | | 12566 | 1067K| 48502 (8)| 00:09:43 |
| 1 | LOAD AS SELECT | X109 | | | | |
| 2 | NESTED LOOPS | | | | | |
| 3 | NESTED LOOPS | | 12566 | 1067K| 48474 (8)| 00:09:42 |
|* 4 | TABLE ACCESS FULL | FI_NON_SPLITTING_CL_V | 12566 | 687K| 23303 (16)| 00:04:40 |
|* 5 | INDEX UNIQUE SCAN | KM_FI_INST_PKX | 1 | | 1 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID| KM_FI_INSTRUMENT | 1 | 31 | 2 (0)| 00:00:01 |


I remember reading one of your posts about functions impacting query cost. Can you please help me with that.

Regards,
Vikram
Tom Kyte
October 07, 2011 - 2:00 pm UTC

use the cardinality hint perhaps then to correct the issue, I would NOT be using a plsql function here at all.

Deterministic vs Functional Programming

Parthiban Nagarajan, May 28, 2012 - 2:11 pm UTC

Hi Tom

Are the PL/SQL deterministic functions - a sort of Functional Programming?
Please compare and contrast.

Thanks and regards

Tom Kyte
May 29, 2012 - 6:18 am UTC

no, not at all. In functional programming - things are immutable, there are no side effects. PL/SQL is just a plain old imperative procedural language.

http://en.wikipedia.org/wiki/Functional_programming

deterministic && scalar subquery cache

A reader, May 30, 2012 - 3:18 am UTC

Hi tom,

I am trying to understand the 'deterministic' and 'scalar subquery cache'.

Inside below test script, i have some question need your kindly help.


create table test as select rownum id from dual connect by level<=3;
insert into test select * from test;
create or replace function fun1 return number
as
begin
dbms_output.put_line('hello');
return 1;
end;
/
select id+fun1 from test;
-- 6 hello as expected
select id+(select fun1 from dual) from test;
-- 1 hello due to scalar subquery cache take effect

create or replace function fun2 return number
deterministic
as
begin
dbms_output.put_line('hello');
return 1;
end;
/
select id+fun2 from test;
-- 1 hello due to deterministic

create or replace function fun3(p_num in number) return number
deterministic
as
begin
dbms_output.put_line('hello');
return p_num;
end;
/

select id+fun3(id) from test;
-- why i got 4 hello here, should it be either 3 or 6?
-- i am thinking might be due to the data is 1,2,3,1,2,3 rather than 1,1,2,2,3,3
-- so i run below, it is 3 hello now, seems prove my assumption is right. If so, why above is 4? should it be 6?
select id+fun3(id) from test order by id;

select id+(select fun3(id) from dual) from test;
-- 3 as expected due to scalar subquery cache

create or replace function fun4 return number
as
begin
dbms_output.put_line('hello');
return dbms_flashback.GET_SYSTEM_CHANGE_NUMBER;
end;
/

select id||'_'||fun4 from test;
-- 6 hello as expected
select id ||'_'||(select fun4 from dual) from test;
-- 1 hello, so it means scalar subquery cache took effect
-- but my concern is fun4 is not deterministic, and does not always return the same value
-- suppose 'test' has many lines, anyway above query need to loop the row one by one
-- it might be possible that while in middle, the system SCN changed
-- so the question why scalar subquery cache works for this kind of function?
-- and even more deep, how is scalar subquery cache working? How can it know whether the value will change or not?
Tom Kyte
May 30, 2012 - 6:10 am UTC

-- why i got 4 hello here, should it be either 3 or 6?


neither of the caches is "perfect", nor do they claim to be. Deterministic does not mean "we'll call it once per set of inputs", but rather "we'll take that as a hint that we might be able to cache something as we go along"


-- 3 as expected due to scalar subquery cache


although it could have been six - due to scalar subquery caching as well! The cache is not perfect - if 1, 2 and 3 all hashed to the same place in the cache - then it would have been called six times.


-- but my concern is fun4 is not deterministic, and does not always return the
same value


why is that a concern? Anytime you call a function that is not statement level de4terministic (sysdate is a function, it is also a statement level deterministic function - it returns the same value in a select statement no matter how/how many times it is referenced) - you HAVE TO EXPECT THINGS like this.

We might call the function once
We might call the function twice
We might call the function once per row
We might do something else

A query plan can change everything - consider:

select * from t where col = fun4;

What if col is not indexed? We'll *probably* call fun4 once per row. Maybe. No promises

What if col is indexed AND we decide to use the index? We'l probably call fun4 once to get a start/stop key for an index range scan. *probably*



You cannot count on how many times a function will be called - or when it will be called or even if it will be called. SQL is not a procedural language.

Statement level non-deterministic functions are especially "troublesome", a simple plan change may change how often they are called

Re: Deterministic vs Functional Programming

Parthiban Nagarajan, June 21, 2012 - 2:14 pm UTC

Hi Tom

Yes. PL/SQL is imperative.
But at the same time, my view on the Deterministic Functions is that it resembles one particular property of the Functional Programming (Referential Transparency).

http://en.wikipedia.org/wiki/Functional_programming
says => ... the output value of a function depends only on the arguments that are input to the function ...

http://docs.oracle.com/cd/E11882_01/appdev.112/e17126/create_function.htm#LNPLS01370
says => Indicates that the function returns the same result value whenever it is called with the same values for its parameters.

Please correct me if my view is wrong.

By the way, the above query "deterministic && scalar subquery cache" and the comments are excellent ...

Thanks and regards
Tom Kyte
June 22, 2012 - 7:11 am UTC

A deterministic function in plsql can have side effects, it might "resemble" functional programming, but only in the sense that a wagon resembles a car. They both tend to have four wheels and a chassis



"Eliminating side effects can make it much easier to understand and predict the behavior of a program, which is one of the key motivations for the development of functional programming."

deterministic in oracle 11

Benjamin Sailer, March 09, 2017 - 9:14 am UTC

at least for oracle 11 the deterministic-keyword in fact can reduce the number of times the function is called within one statement (no fbi or mv involved):

begin
  dbms_output.enable();
end;
/

create or replace function f_det(arg varchar2) return varchar2 deterministic as
begin
dbms_output.put_line('called f_det with arg ' || arg);
return arg;
end;
/

select f_det('x') two_explicit_f_det from dual
union all
select f_det('x') two_explicit_f_det from dual
;

select f_det('x') one_explicit_f_det from (
select 1 from dual
union all
select 1 from dual)
;

create or replace function f_not_det(arg varchar2) return varchar2 as
begin
dbms_output.put_line('called f_not_det with arg ' || arg);
return arg;
end;
/

select f_not_det('x') two_explicit_f_not_det from dual
union all
select f_not_det('x') two_explicit_f_not_det from dual
;

select f_not_det('x') one_explicit_f_not_det from (
select 1 from dual
union all
select 1 from dual)
;


sql result:
anonymer Block abgeschlossen
FUNCTION F_DET kompiliert
TWO_EXPLICIT_F_DET
------------------
x
x

ONE_EXPLICIT_F_DET
------------------
x
x

FUNCTION F_NOT_DET kompiliert
TWO_EXPLICIT_F_NOT_DET
----------------------
x
x

ONE_EXPLICIT_F_NOT_DET
----------------------
x
x

(two rows for each query)


dbms_output:
called f_det with arg x
called f_det with arg x

called f_det with arg x <-- only one function call here

called f_not_det with arg x
called f_not_det with arg x

called f_not_det with arg x
called f_not_det with arg x



Chris Saxon
March 09, 2017 - 11:23 am UTC

Yep, deterministic function caching came in 10g. Tom discusses this (and other caching techniques) in more detail at:

http://www.oracle.com/technetwork/issue-archive/2011/11-sep/o51asktom-453438.html

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library