I absolutely HATE your code, I know it is a demo, but things you do in demo's you probably do in real life:
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
END PRO_TEST;
http://asktom.oracle.com/pls/ask/search?p_string=%22i+hate+your+code%22 that is the least professional thing you can code, it makes you look really bad.
the number of times a function is called when embedded in a sql statement can vary widely - drastically - and dramatically. You have no control over how many times it will be called. You have no control over what order they will be called in.
The short answer to your question however is
select * from t where unindexed_column = f(x);
will
in general cause f(x) to be evaluated for each and every row in the table T. Why? because it needs to evaluate unindexed_column = f(x) for each row.
select * from t where indexed_column = f(x);
will
in general cause f(x) to be evaluated twice for the entire query. Why? Because we will use an index range scan and in order to do an index range scan we need a "start" and "stop" key - and that is all, we get the start/stop keys by running f(x).
The scalar subquery you used (select f(x) from dual) is ALWAYS a good idea when calling plsql from sql, there is a feature called scalar subquery caching
http://asktom.oracle.com/pls/ask/search?p_string=%22scalar+subquery+caching%22 that can dramatically reduce the number of times your function was called. Lets look at that in more detail. (in the following, stage is a copy of all_objects)
ops$tkyte%ORA11GR1> create or replace function f( x in varchar2 ) return number
2 as
3 begin
4 dbms_application_info.set_client_info(userenv('client_info')+1 );
5 return length(x);
6 end;
7 /
Function created.
that function counts how often it was called and stores the result in V$SESSION
ops$tkyte%ORA11GR1> exec :cpu := dbms_utility.get_cpu_time; dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
ops$tkyte%ORA11GR1> set autotrace traceonly statistics;
ops$tkyte%ORA11GR1> select owner, f(owner) from stage;
68464 rows selected.
Statistics
----------------------------------------------------------
316 recursive calls
0 db block gets
5599 consistent gets
1022 physical reads
0 redo size
1295486 bytes sent via SQL*Net to client
50624 bytes received via SQL*Net from client
4566 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
68464 rows processed
ops$tkyte%ORA11GR1> set autotrace off
ops$tkyte%ORA11GR1> select dbms_utility.get_cpu_time-:cpu cpu_hsecs, userenv('client_info') from dual;
CPU_HSECS USERENV('CLIENT_INFO')
---------- ----------------------------------------------------------------
205 68464
Ok, that took 2 cpu seconds and called our function ONCE per row in the table. If we simply wrap it in a scalar subquery:
ops$tkyte%ORA11GR1> exec :cpu := dbms_utility.get_cpu_time; dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
ops$tkyte%ORA11GR1> set autotrace traceonly statistics;
ops$tkyte%ORA11GR1> select owner, (select f(owner) from dual) f from stage;
68464 rows selected.
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
5511 consistent gets
1009 physical reads
0 redo size
1295479 bytes sent via SQL*Net to client
50624 bytes received via SQL*Net from client
4566 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
68464 rows processed
ops$tkyte%ORA11GR1> set autotrace off
ops$tkyte%ORA11GR1> select dbms_utility.get_cpu_time-:cpu cpu_hsecs, userenv('client_info') from dual;
CPU_HSECS USERENV('CLIENT_INFO')
---------- ----------------------------------------------------------------
29 73
It called it only 73 times (due to the scalar subquery cache!) Now, in my case - it (the function) was called more often than it needed to be - due to hash collisions in the subquery cache (we could not cache *every thing*). If we were to pre-sort the data and then invoke the function against the sorted data (sort by the inputs to the function) we can achieve the fewest invocations:
ops$tkyte%ORA11GR1> exec :cpu := dbms_utility.get_cpu_time; dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
ops$tkyte%ORA11GR1> set autotrace traceonly statistics;
ops$tkyte%ORA11GR1> select owner, (select f(owner) from dual) f
2 from (select owner, rownum r from stage order by owner);
68464 rows selected.
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
1011 consistent gets
1008 physical reads
0 redo size
922778 bytes sent via SQL*Net to client
50624 bytes received via SQL*Net from client
4566 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
68464 rows processed
ops$tkyte%ORA11GR1> set autotrace off
ops$tkyte%ORA11GR1> select dbms_utility.get_cpu_time-:cpu cpu_hsecs, userenv('client_info') from dual;
CPU_HSECS USERENV('CLIENT_INFO')
---------- ----------------------------------------------------------------
46 37
that would only make sense if the function was really expensive to invoke (eg: in this case, the time to sort the data was more than the amount of cpu saved by invoking the function fewer times - so it would not make sense in THIS case)
Now, starting in 10g, the keyword deterministic was picked up by the sql engine and the sql engine would transparently start caching things:
ops$tkyte%ORA11GR1> create or replace function f( x in varchar2 ) return number
2 <b>DETERMINISTIC</b>
3 as
4 begin
5 dbms_application_info.set_client_info(userenv('client_info')+1 );
6 return length(x);
7 end;
8 /
Function created.
ops$tkyte%ORA11GR1> exec :cpu := dbms_utility.get_cpu_time; dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
ops$tkyte%ORA11GR1> set autotrace traceonly statistics;
ops$tkyte%ORA11GR1> select owner, f(owner) from stage;
68464 rows selected.
Statistics
----------------------------------------------------------
17 recursive calls
0 db block gets
5535 consistent gets
1008 physical reads
0 redo size
1295486 bytes sent via SQL*Net to client
50624 bytes received via SQL*Net from client
4566 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
68464 rows processed
ops$tkyte%ORA11GR1> set autotrace off
ops$tkyte%ORA11GR1> select dbms_utility.get_cpu_time-:cpu cpu_hsecs, userenv('client_info') from dual;
CPU_HSECS USERENV('CLIENT_INFO')
---------- ----------------------------------------------------------------
119 7951
so, that shows that deterministic can be beneficial - especially if you cannot rewrite the SQL to use a scalar subquery - but you still want to use the scalar subquery cache!
In 11g, they added "function result caches" - whereby your plsql function would not be executed every time it was called - the value would be saved SERVER WIDE (across sql statements, across sessions, across everything). This will reduce the number of times your plsql function code RUNS:
ops$tkyte%ORA11GR1> create or replace function f( x in varchar2 ) return number
2 <b>RESULT_CACHE</b>
3 as
4 begin
5 dbms_application_info.set_client_info(userenv('client_info')+1 );
6 return length(x);
7 end;
8 /
Function created.
ops$tkyte%ORA11GR1> exec :cpu := dbms_utility.get_cpu_time; dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
ops$tkyte%ORA11GR1> set autotrace traceonly statistics;
ops$tkyte%ORA11GR1> select owner, f(owner) from stage;
68464 rows selected.
Statistics
----------------------------------------------------------
17 recursive calls
0 db block gets
5535 consistent gets
1008 physical reads
0 redo size
1295486 bytes sent via SQL*Net to client
50624 bytes received via SQL*Net from client
4566 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
68464 rows processed
ops$tkyte%ORA11GR1> set autotrace off
ops$tkyte%ORA11GR1> select dbms_utility.get_cpu_time-:cpu cpu_hsecs, userenv('client_info') from dual;
CPU_HSECS USERENV('CLIENT_INFO')
---------- ----------------------------------------------------------------
133 37
see - your code ran only 37 times, but - important but - note the CPU. It is not 0.2 as it was above when we used the scalar subquery cache - it is 1.33. That is the overhead of context switching from sql to plsql 68,464 times. So, even though the code did not run - we still had to flip flop between the sql engine and the plsql engine and that takes time
If we re-execute that query:
ops$tkyte%ORA11GR1> exec :cpu := dbms_utility.get_cpu_time; dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
ops$tkyte%ORA11GR1> set autotrace traceonly statistics;
ops$tkyte%ORA11GR1> select owner, f(owner) from stage;
68464 rows selected.
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
5511 consistent gets
1008 physical reads
0 redo size
1295486 bytes sent via SQL*Net to client
50624 bytes received via SQL*Net from client
4566 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
68464 rows processed
ops$tkyte%ORA11GR1> set autotrace off
ops$tkyte%ORA11GR1> select dbms_utility.get_cpu_time-:cpu cpu_hsecs, userenv('client_info') from dual;
CPU_HSECS USERENV('CLIENT_INFO')
---------- ----------------------------------------------------------------
103 0
we now ran the code ZERO times, but we spent 0.8 cpu seconds context switching between plsql and sql!
bottom line: if you call plsql from sql - use the scalar subquery technique.