2. Who am I
• Been with Oracle since
1993
• User of Oracle since 1987
• The “Tom” behind AskTom
in Oracle Magazine
www.oracle.com/oramag
• Expert Oracle Database
Architecture
• Effective Oracle by Design
• Expert One on One Oracle
• Beginning Oracle
3. Agenda
• What do you need to write “good” SQL
• The Schema Matters
• Knowing what is available
– Using rownum (yes, to 'tune')
– Scalar subqueries
– Analytics
– Some hints
• Don’t tune queries!
• Other things
– Materialized Views
– With subquery factoring
– Merge
– …
4. What do you need to know…
• Access Paths
– There are a lot of them
– There is no best one (else there would be, well, one)
• A little bit of physics
– Full scans are not evil
– Indexes are not all goodness
• How the data is managed by Oracle
– high water marks for example
– IOT’s, clusters, etc
• What your query needs to actually do
– Is that outer join really necessary or “just in case”
5. Structures
• How the data is accessed and organized
makes a difference
– Clustering
Select *
from orders o, line_items li
ORDERS ORDERS & LINE
where o.order# = li.order# LINE ITEMS
ITEMS
And o.order# = :order
6. Structures
• How the data is accessed and organized
makes a difference
– Clustering
– Index Organized Tables
Select avg(price) STOCKS
STOCKS
From stocks
Where symbol = ‘ORCL’
And stock_dt >= sysdate-5;
7. Structures
• How the data is accessed and organized
makes a difference
– Clustering
– Index Organized Tables ORDERS
ORDERS
ORDERS
– Partitioning Europe
USA
Jan
Jan Feb Feb
Composite Partition
Partition
Large Table
Higher and Manage
Divide Performance
Difficult to Conquer
More flexibility to match
Easier to Manage
business needs
Improve Performance
8. The Schema Matters
• A Lot!
• Tune this query:
Select DOCUMENT_NAME, META_DATA
from documents
where userid=:x;
• That is about as easy as it gets (the SQL)
• Not too much we can do to rewrite it…
• But we’d like to make it better.
Iot01.sql
Cf.sql
10. Organization Counts
ops$tkyte%ORA11GR2> begin
2 for i in 1 .. 100
3 loop
4 for x in ( select username from all_users )
5 loop
6 insert into heap
7 (username,document_name,other_data) values
8 ( x.username, x.username || '_' || i, 'x' );
9
10 insert into iot
11 (username,document_name,other_data) values
12 ( x.username, x.username || '_' || i, 'x' );
13 end loop;
14 end loop;
15 dbms_stats.gather_table_stats
16 ( user, 'IOT', cascade=>true );
17 dbms_stats.gather_table_stats
18 ( user, 'HEAP', method_opt=>'for all indexed columns',
cascade=>true );
19 commit;
20 end;
21 /
11. Organization Counts
ops$tkyte%ORA11GR2> declare
2 l_rec heap%rowtype;
3 cursor heap_cursor(p_username in varchar2) is
4 select * from heap single_row where username = p_username;
5 cursor iot_cursor(p_username in varchar2) is
6 select * from iot single_row where username = p_username;
7 begin
8 for i in 1 .. 10
9 loop
10 for x in (select username from all_users) loop
11 open heap_cursor(x.username);
12 loop
13 fetch heap_cursor into l_rec;
14 exit when heap_cursor%notfound;
15 end loop;
16 close heap_cursor;
17 open iot_cursor(x.username);
18 loop
19 fetch iot_cursor into l_rec;
20 exit when iot_cursor%notfound;
21 end loop;
22 close iot_cursor;
23 end loop;
24 end loop;
25 end;
26 /
12. Organization Counts
ops$tkyte%ORA11GR2> declare
2 type array is table of iot%rowtype;
3 l_data array;
4 begin
5 for i in 1 .. 10
6 loop
7 for x in (select username from all_users)
8 loop
9 select * bulk collect into l_data
10 from heap bulk_collect
11 where username = x.username;
12 select * bulk collect into l_data
13 from iot bulk_collect
14 where username = x.username;
15 end loop;
16 end loop;
17 end;
18 /
PL/SQL procedure successfully completed.
13. Organization Counts
SELECT * FROM HEAP SINGLE_ROW WHERE USERNAME = :B1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 410 0.02 0.02 0 0 0 0
Fetch 41410 0.25 0.27 0 82810 0 41000
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 41821 0.28 0.30 0 82810 0 41000
Row Source Operation
---------------------------------------------------
TABLE ACCESS BY INDEX ROWID HEAP (cr=202 pr=0 pw=0 time=41 us cost=102 ...)
INDEX RANGE SCAN HEAP_PK (cr=102 pr=0 pw=0 time=221 us cost=2 size=0 ca...)
14. Organization Counts
SELECT * FROM IOT SINGLE_ROW WHERE USERNAME = :B1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 410 0.02 0.02 0 0 0 0
Fetch 41410 0.16 0.18 0 42220 0 41000
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 41821 0.19 0.21 0 42220 0 41000
Row Source Operation
---------------------------------------------------
INDEX RANGE SCAN IOT_PK (cr=103 pr=0 pw=0 time=33 us cost=21 size=102000 ...)
15. Organization Counts
SELECT * FROM HEAP BULK_COLLECT WHERE USERNAME = :B1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 410 0.01 0.02 0 0 0 0
Fetch 410 0.11 0.12 0 42010 0 41000
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 821 0.13 0.14 0 42010 0 41000
Row Source Operation
---------------------------------------------------
TABLE ACCESS BY INDEX ROWID HEAP (cr=102 pr=0 pw=0 time=533 us cost=102 ...)
INDEX RANGE SCAN HEAP_PK (cr=2 pr=0 pw=0 time=23 us cost=2 size=0 ca...)
16. Organization Counts
SELECT * FROM IOT BULK_COLLECT WHERE USERNAME = :B1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 410 0.01 0.02 0 0 0 0
Fetch 410 0.06 0.06 0 9000 0 41000
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 821 0.08 0.08 0 9000 0 41000
Row Source Operation
---------------------------------------------------
INDEX RANGE SCAN IOT_PK (cr=22 pr=0 pw=0 time=142 us cost=21 size=102000...)
17. Knowing what is available
• There is a lot out there…
• I learn something new every day
• Skimming the docs works
– Oh, I remember something similar…
• Check out the “whats new in” at the head of the
docs
• Participate in the forums
• Things change… Some things must be
“discovered”
Ignulls.sql
18. You have to learn new things…
ops$tkyte%ORA11GR2> select dt, val
2 from t
3 order by dt;
DT VAL
--------- ----------
02-JAN-11 195
03-JAN-11
04-JAN-11
05-JAN-11
06-JAN-11 129
07-JAN-11
08-JAN-11
09-JAN-11
10-JAN-11 87
11-JAN-11
10 rows selected.
19. You have to learn new things…
ops$tkyte%ORA11GR2> select dt, val,
2 case when val is not null
3 then to_char(row_number() over (order by dt),'fm0000')||val
4 end max_val
5 from t
6 order by dt;
DT VAL MAX_VAL
--------- ---------- ---------------------------------------------
02-JAN-11 195 0001195
03-JAN-11
04-JAN-11
05-JAN-11
06-JAN-11 129 0005129
07-JAN-11
08-JAN-11
09-JAN-11
10-JAN-11 87 000987
11-JAN-11
10 rows selected.
20. You have to learn new things…
ops$tkyte%ORA11GR2> select dt, val,
2 max(max_val) over (order by dt) max_val_str
3 from ( select dt, val,
4 case when val is not null
5 then to_char(row_number() over (order by dt),'fm0000')||val
6 end max_val
7 from t ) order by dt
8 /
DT VAL MAX_VAL_STR
--------- ---------- ---------------------------------------------
02-JAN-11 195 0001195
03-JAN-11 0001195
04-JAN-11 0001195
05-JAN-11 0001195
06-JAN-11 129 0005129
07-JAN-11 0005129
08-JAN-11 0005129
09-JAN-11 0005129
10-JAN-11 87 000987
11-JAN-11 000987
10 rows selected.
21. You have to learn new things…
ops$tkyte%ORA11GR2> select dt, val,
2 to_number(substr(max(max_val) over (order by dt),5)) max_val
3 from ( select dt, val,
4 case when val is not null
5 then to_char(row_number() over (order by dt),'fm0000')||val
6 end max_val
7 from t ) order by dt
8 /
DT VAL MAX_VAL
--------- ---------- ----------
02-JAN-11 195 195
03-JAN-11 195
04-JAN-11 195
05-JAN-11 195
06-JAN-11 129 129
07-JAN-11 129
08-JAN-11 129
09-JAN-11 129
10-JAN-11 87 87
11-JAN-11 87
10 rows selected.
22. You have to learn new things…
ops$tkyte%ORA11GR2> select dt, val,
2 last_value(val ignore nulls) over (order by dt) val
3 from t
4 order by dt
5 /
DT VAL VAL
--------- ---------- ----------
02-JAN-11 195 195
03-JAN-11 195
04-JAN-11 195
05-JAN-11 195
06-JAN-11 129 129
07-JAN-11 129
08-JAN-11 129
09-JAN-11 129
10-JAN-11 87 87
11-JAN-11 87
10 rows selected.
23. Things Change
begin
for x in
( select *
from big_table.big_table
where rownum <= 10000 )
loop
null;
end loop;
end;
24. Things Change
declare
type array is table of big_table%rowtype;
l_data array;
cursor c is
select * from big_table where rownum <= 1000;
begin
open c;
loop
fetch c bulk collect into l_data limit 100;
for i in 1 .. l_data.count
loop
null;
end loop;
exit when c%notfound;
end loop;
close c;
end;
27. Using ROWNUM
• Psuedo Column – not a “real” column
• Assigned after the predicate (sort of during) but
before any sort/aggregation
Select x,y
from t where rownum < 10
order by x
Versus
Select * from
(select x,y from t order by x)
where rownum < 10
28. Using ROWNUM
• Incremented after a successful output
Select * from t where rownum = 2
Rownum = 1
For x in ( select * from t )
Loop
if ( rownum = 2 )
then
output record
rownum = rownum+1;
end if
End loop
29. Using ROWNUM
• Top-N queries
Select *
from (select * from t where … order by X )
where rownum <= 10;
• Does not have to sort the entire set
• Sets up an “array” conceptually
• Gets the first 10
• When we get the 11th, see if it is in the top 10
– If so, push out an existing array element, slide this in
– Else throw it out and get the next one.
• Do not attempt this in CODE! (well – what about 10g?)
rn02.sql
30. Top-N
ops$tkyte%ORA11GR2> explain plan for
2 select * from (select * from scott.emp order by sal desc) where rownum <= 10;
Explained.
ops$tkyte%ORA11GR2> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------
Plan hash value: 1744961472
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 870 | 4 (25)| 00:00:01 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | VIEW | | 14 | 1218 | 4 (25)| 00:00:01 |
|* 3 | SORT ORDER BY STOPKEY| | 14 | 532 | 4 (25)| 00:00:01 |
| 4 | TABLE ACCESS FULL | EMP | 14 | 532 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=10)
3 - filter(ROWNUM<=10)
17 rows selected.
31. Top-N
ops$tkyte%ORA11GR2> @mystat "sorts (disk)"
NAME VALUE
---------------------- ----------
sorts (disk) 0
ops$tkyte%ORA11GR2> select *
2 from (select *
3 from big_table.big_table
4 order by object_id) where rownum <= 10;
10 rows selected.
Statistics
----------------------------------------------------------
… 1 sorts (memory)
0 sorts (disk)
10 rows processed
ops$tkyte%ORA11GR2> @mystat2
NAME VALUE DIFF
---------------------- ---------- ------------------
sorts (disk) 0 0
32. Top-N
ops$tkyte%ORA11GR2> @mystat "sorts (disk)"
NAME VALUE
---------------------- ----------
sorts (disk) 0
ops$tkyte%ORA11GR2> declare
2 cursor c is
3 select * from big_table.big_table order by object_id;
4 l_rec big_table_v%rowtype;
5 begin
6 open c;
7 for i in 1 .. 10
8 loop
9 fetch c into l_rec;
10 end loop;
11 close c;
12 end;
13 /
PL/SQL procedure successfully completed.
ops$tkyte%ORA11GR2> @mystat2
NAME VALUE DIFF
---------------------- ---------- ------------------
sorts (disk) 1 1
34. Top-N
select *
from (select *
from big_table.big_table
order by object_id) where rownum <= 10
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 2 0.00 0.00 2 14 0 10
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.00 0.00 2 14 0 10
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 189
Number of plan statistics captured: 1
Row Source Operation
---------------------------------------------------
COUNT STOPKEY (cr=14 pr=2 pw=0 time=328 us)
VIEW (cr=14 pr=2 pw=0 time=321 us cost=13 size=1410 card=10)
TABLE ACCESS BY INDEX ROWID BIG_TABLE (cr=14 pr=2 pw=0 time=316 us …)
INDEX FULL SCAN BT_IDX (cr=4 pr=2 pw=0 time=322 us cost=3 size=0 …)
35. Top-N
SELECT * FROM BIG_TABLE.BIG_TABLE ORDER BY OBJECT_ID
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 2 0.00 0.00 0 0 0 0
Fetch 10 1.12 2.17 14703 14544 7 10
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 13 1.12 2.17 14703 14544 7 10
Row Source Operation
---------------------------------------------------
SORT ORDER BY (cr=14544 pr=14703 pw=14639 time=2173284 us cost=26523 size...)
TABLE ACCESS FULL BIG_TABLE (cr=14544 pr=14541 pw=0 time=694199 us cost=...)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
direct path write temp 476 0.00 1.23
direct path read temp 6 0.00 0.00
36. Using ROWNUM
• Pagination
Select *
From ( select a.*, ROWNUM rnum
From ( your_query_goes_here ) a
Where ROWNUM <= :MAX_ROW_TO_FETCH )
Where rnum >= :MIN_ROW_TO_FETCH;
• Everything from prior slide goes here…
• Never ever let them “count the rows”, never.
• Do not attempt this in CODE!
rn03.sql
37. Pagination
ops$tkyte%ORA11GR2> set autotrace traceonly statistics
ops$tkyte%ORA11GR2> variable max number
ops$tkyte%ORA11GR2> variable min number
ops$tkyte%ORA11GR2> exec :min := 100; :max := 115;
PL/SQL procedure successfully completed.
38. Pagination
select *
from (select a.*, rownum rnum
from (select /*+ FIRST_ROWS(15) */ *
from big_table.big_table order by object_id) a
where rownum <= :Max)
where rnum >= :min
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 3 0.00 0.00 12 119 0 16
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 5 0.00 0.00 12 119 0 16
Row Source Operation
---------------------------------------------------
VIEW (cr=119 pr=12 pw=0 time=939 us cost=18 size=2310 card=15)
COUNT STOPKEY (cr=119 pr=12 pw=0 time=2840 us)
VIEW (cr=119 pr=12 pw=0 time=2722 us cost=18 size=2115 card=15)
TABLE ACCESS BY INDEX ROWID BIG_TABLE (cr=119 pr=12 pw=0 time=2605 us cost...)
INDEX FULL SCAN BT_IDX (cr=4 pr=2 pw=0 time=258 us cost=3 size=0 card=...)
39. Pagination
ops$tkyte%ORA11GR2> declare
2 cursor c is
3 select * from big_table.big_table order by object_id;
4 l_rec big_table_v%rowtype;
5 begin
6 open c;
7 for i in 1 .. 115
8 loop
9 fetch c into l_rec;
10 if ( i < 100 )
11 then
12 null;
13 else
14 null; -- process it
15 end if;
16 end loop;
17 close c;
18 end;
19 /
PL/SQL procedure successfully completed.
40. Pagination
SELECT * FROM BIG_TABLE.BIG_TABLE ORDER BY OBJECT_ID
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 2 0.00 0.00 0 0 0 0
Fetch 115 1.20 2.32 14703 14544 7 115
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 118 1.21 2.32 14703 14544 7 115
Row Source Operation
---------------------------------------------------
SORT ORDER BY (cr=14544 pr=14703 pw=14639 time=2324724 us cost=26523 size=...)
TABLE ACCESS FULL BIG_TABLE (cr=14544 pr=14541 pw=0 time=682159 us cost=...)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
direct path write temp 571 0.00 1.34
direct path read temp 6 0.00 0.00
41. Scalar Subqueries
• The ability to use a single column, single row query
where you would normally use a “value”
Select dname, ‘Some Value’
From dept
• That example shows a possible use of scalar
subquerys – outer join removal
42. Scalar Subqueries
• The ability to use a single column, single row query
where you would normally use a “value”
Select dname, (select count(*)
from emp
where emp.deptno =
:dept.deptno ) cnt
From dept
• That example shows a possible use of scalar
subquerys – outer join removal
43. Scalar Subqueries
• Outer join removal for “fast return” queries
– That works great for a single column
– What about when you need more than one?
ss01.sql
44. Scalar Subqueries
ops$tkyte%ORA11GR2> select *
2 from (
3 select a.owner, count(b.owner)
4 from big_table.big_table_owners a left join big_table.big_table b
5 on (a.owner = b.owner and b.object_type = 'TABLE' )
6 group by a.owner
7 order by a.owner
8 )
9 where rownum <= 2
10 /
Statistics
----------------------------------------------------------
14613 consistent gets
14541 physical reads
7 sorts (memory)
0 sorts (disk)
2 rows processed
45. Scalar Subqueries
ops$tkyte%ORA11GR2> select a.*,
2 (select count(*)
3 from big_table.big_table b
4 where b.owner = a.owner and b.object_type = 'TABLE' ) cnt
5 from (
6 select a.owner
7 from big_table.big_table_owners a
8 order by a.owner
9 ) a
10 where rownum <= 2
11 /
Statistics
----------------------------------------------------------
590 consistent gets
1 sorts (memory)
0 sorts (disk)
2 rows processed
46. Scalar Subqueries
ops$tkyte%ORA11GR2> select a.*,
2 (select count(*)
3 from big_table.big_table b
4 where b.owner = a.owner and b.object_type = 'TABLE' ) cnt,
5 (select min(created)
6 from big_table.big_table b
7 where b.owner = a.owner and b.object_type = 'TABLE' ) min_created,
8 (select max(created)
9 from big_table.big_table b
10 where b.owner = a.owner and b.object_type = 'TABLE' ) max_created
11 from (
12 select a.owner
13 from big_table.big_table_owners a
14 order by a.owner
15 ) a
16 where rownum <= 2
17 /
Statistics
----------------------------------------------------------
1766 consistent gets
1 sorts (memory)
0 sorts (disk)
2 rows processed
47. Scalar Subqueries
ops$tkyte%ORA11GR2> select owner,
2 to_number(substr(data,1,10)) cnt,
3 to_date(substr(data,11,14),'yyyymmddhh24miss') min_created,
4 to_date(substr(data,25),'yyyymmddhh24miss') max_created
5 from (
6 select owner,
7 (select to_char( count(*), 'fm0000000000') ||
8 to_char( min(created),'yyyymmddhh24miss') ||
9 to_char( max(created),'yyyymmddhh24miss')
10 from big_table.big_table b
11 where b.owner = a.owner and b.object_type = 'TABLE' ) data
12 from (
13 select a.owner
14 from big_table.big_table_owners a
15 order by a.owner
16 ) a
17 where rownum <= 2
18 )
19 /
Statistics
----------------------------------------------------------
590 consistent gets
1 sorts (memory)
0 sorts (disk)
2 rows processed
49. Scalar Subqueries
ops$tkyte%ORA11GR2> select owner, a.data.cnt, a.data.min_created,
a.data.max_created
2 from (
3 select owner,
4 (select myType( count(*), min(created), max(created) )
5 from big_table.big_table b
6 where b.owner = a.owner and b.object_type = 'TABLE' ) data
7 from (
8 select a.owner
9 from big_table.big_table_owners a
10 order by a.owner
11 ) a
12 where rownum <= 2
13 ) a
14 /
Statistics
----------------------------------------------------------
590 consistent gets
1 sorts (memory)
0 sorts (disk)
2 rows processed
50. Scalar Subqueries
• Reducing PLSQL function calls via scalar subquery
caching
Select * from t where x = pkg.getval()
versus
Select * from t where x =
(select pkg.getval() from dual)
• How to call them (scalar subqueries) “as little as
possible”
ss02.sql
51. Scalar Subqueries
ops$tkyte%ORA11GR2> 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.
53. Scalar Subqueries
ops$tkyte%ORA11GR2> exec :cpu := dbms_utility.get_cpu_time;
dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
ops$tkyte%ORA11GR2> select owner, (select f(owner) from dual) f
from stage;
72841 rows selected.
ops$tkyte%ORA11GR2> select dbms_utility.get_cpu_time-:cpu cpu_hsecs,
userenv('client_info') from dual;
CPU_HSECS USERENV('CLIENT_INFO')
---------- --------------------------------------------
30 66
54. Scalar Subqueries
ops$tkyte%ORA11GR2> exec :cpu := dbms_utility.get_cpu_time;
dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
ops$tkyte%ORA11GR2> select owner, (select f(owner) from dual) f
2 from (select owner, rownum r from stage order by owner);
72841 rows selected.
ops$tkyte%ORA11GR2> select dbms_utility.get_cpu_time-:cpu cpu_hsecs,
userenv('client_info') from dual;
CPU_HSECS USERENV('CLIENT_INFO')
---------- --------------------------------------------------------------
32 32
55. Scalar Subqueries
ops$tkyte%ORA11GR2> create or replace function f( x in varchar2 ) return number
2 DETERMINISTIC
3 as
4 begin
5 dbms_application_info.set_client_info(userenv('client_info')+1 );
6 return length(x);
7 end;
8 /
Function created.
57. Scalar Subqueries
ops$tkyte%ORA11GR2> create or replace function f( x in varchar2 ) return number
2 RESULT_CACHE
3 as
4 begin
5 dbms_application_info.set_client_info(userenv('client_info')+1 );
6 return length(x);
7 end;
8 /
Function created.