Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Manoj.

Asked: July 03, 2001 - 3:14 pm UTC

Last updated: February 24, 2008 - 11:38 am UTC

Version: 8.1.6

Viewed 10K+ times! This question is

You Asked

Hi Tom

We are using reference cursor to return result set for functions which are called from java program. In the function, we open a refernece cursor with some select statement. (And return that result set through that cursor.) Since there are 30 + procedure frequently called, We are getting low parse hit ratio for those statements, presuming statements are being reparsed due to use of ref cursor.

1) Is my assumption correct that use of ref cursor is expensixe ?
2) Do you suggest a better approach.

Appreciated as usual and thanks in advance.



and Tom said...

Ref cursors will cause AT LEAST a soft parse each time, yes. It is unavoidable -- normally PLSQL can "cache" cursors for us and avoid the soft parse but with ref cursors -- it must open them each time.

"expensive" is a relative term. If the alternative is putting the logic into the application, then this is not expensive.

If the system is to be the highest of highly scalable systems, this might be a performance hit that is not acceptable.

You can remove most all of this hit by using session_cached_cursors. Consider:

ops$tkyte@ORA817.US.ORACLE.COM> create table t ( runid varchar2(15), name varchar2(80), value int );
Table created.

ops$tkyte@ORA817.US.ORACLE.COM> create or replace view stats
2 as select 'STAT ' || a.name name, b.value
3 from v$statname a, v$mystat b
4 where a.statistic# = b.statistic#
5 union all
6 select 'LATCH ' || name, gets
7 from v$latch;

View created.


ops$tkyte@ORA817.US.ORACLE.COM> column name format a40

ops$tkyte@ORA817.US.ORACLE.COM> declare
2 type refcur is ref cursor;
3 rc refcur;
4 emp_rec emp%rowtype;
5 begin
6 execute immediate 'alter session set session_cached_cursors=0';
7
8 insert into t select 'before', stats.* from stats;
9
10 for i in 1 .. 100
11 loop
12 open rc for select * from emp where rownum = 1;
13 loop
14 fetch rc into emp_rec;
15 exit when rc%notfound;
16 end loop;
17 close rc;
18 end loop;
19
20 insert into t select 'after 1', stats.* from stats;
21
22 execute immediate 'alter session set session_cached_cursors=10';
23
24 for i in 1 .. 100
25 loop
26 open rc for select * from emp where rownum = 1;
27 loop
28 fetch rc into emp_rec;
29 exit when rc%notfound;
30 end loop;
31 close rc;
32 end loop;
33
34 insert into t select 'after 2', stats.* from stats;
35 end;
36 /

PL/SQL procedure successfully completed.

ops$tkyte@ORA817.US.ORACLE.COM>
ops$tkyte@ORA817.US.ORACLE.COM> select a.name, b.value-a.value run1, c.value-b.value run2,
2 abs( (c.value-b.value)-(b.value-a.value)) diff
3 from t a, t b, t c
4 where a.name = b.name
5 and b.name = c.name
6 and a.runid = 'before'
7 and b.runid = 'after 1'
8 and c.runid = 'after 2'
9 and (c.value-a.value) > 0
10 and (c.value-b.value) <> (b.value-a.value)
11 order by 4
12 /

NAME RUN1 RUN2 DIFF
---------------------------------------- ---------- ---------- ----------
LATCH checkpoint queue latch 5 6 1
LATCH redo writing 1 0 1
STAT calls to get snapshot scn: kcmgss 106 105 1
LATCH undo global data 1 0 1
STAT consistent gets 112 111 1
STAT execute count 105 106 1
STAT parse count (total) 105 106 1
STAT recursive calls 530 531 1
STAT opened cursors cumulative 105 106 1
STAT enqueue releases 3 2 1
STAT change write time 1 0 1
LATCH direct msg latch 2 0 2
LATCH session queue latch 2 0 2
LATCH done queue latch 2 0 2
STAT session cursor cache count 0 2 2
STAT free buffer requested 4 2 2
STAT enqueue requests 4 2 2
LATCH cache buffers lru chain 3 6 3
STAT redo entries 16 13 3
STAT recursive cpu usage 12 9 3
LATCH ksfv messages 4 0 4
LATCH messages 5 0 5
STAT db block changes 28 22 6
LATCH redo allocation 14 21 7
STAT db block gets 426 417 9
STAT session logical reads 538 528 10
LATCH cache buffers chains 1099 1117 18
LATCH shared pool 320 218 102
STAT session cursor cache hits 0 102 102
STAT redo size 21056 20940 116
LATCH library cache 1285 481 804


31 rows selected.

See how reduced the LATCHes to the library cache are, we wll -- the shared pool latch is much lower as well. Using this parameter, you can remove most of the overhead.

It'll still show up as a soft parse in tkprof and such but the overhead of the soft parse is mostly offset -- you would have to look at the cursor cache hits to see how well you are doing there.

followup to comment

Session_cached_cursors is settable in the init.ora file -- it can be set at the system level for all sessions (and should be!)


Rating

  (37 ratings)

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

Comments

Great response

Manoj, July 05, 2001 - 2:38 pm UTC

Nothing on the earth could have explained it better. Can you help me once more. Do I need to modify all the procedures to use session_cache_cursor or there is any system level parameter for it.

Most useful

Harrison, July 06, 2001 - 12:32 am UTC

Really good, if it is not in the book we can glue
the pages in (I don't know what that other reader
was talking about "Nobody could do it better...",
you might do really well on the next one, and then
start to expect "Nobody..." every time, and our lives
will be hell trying to deal with you. I am in favor
of some moderation on these compliments until we are
satisfied that you have answered all the questions we
can come up with.

REFCURSOR

Phil Williams, April 30, 2002 - 7:39 pm UTC

Is this statement still true for 9i or is there a new methodology availble now ?

Tom Kyte
April 30, 2002 - 8:26 pm UTC

Ref Cursors HAVE to parse each time.

The reason we cannot cache them (like plsql cursors) is due to the fact that you could have a procedure:

procedure p( x in out refcursor_type )
is
begin
open x for select * from dual;
end;


Now, if there was JUST one cached cursor and a client went:

p( cursor1 );
p( cursor2 );

we would be in a heap of trouble! They would both be the "same" cursor. It gets even more muddied when p looks like:

procedure p( x in out refcursor_type )
is
begin
if some_condition then
open x for select * from t;
else
open x for select * from t2;
end if;
end;


Here, the meaning of "x" changes from call to call.

to lessen the impact, one should use session_cached_cursors.

Not quite getting it....sorry

A reader, December 08, 2002 - 9:05 pm UTC

Tom,

Just got confused on these two sentences here.
"Ref cursors will cause AT LEAST a soft parse each time, yes."

"Ref Cursors HAVE to parse each time."

Soft parse indicates that we do not parse the query again. So, ref cursors cause hard parse each time. Do i make sense?

My second question is in Oracle reports application, would all the ref cursor queries be parsed every time it runs?

Thanks for your time

Tom Kyte
December 09, 2002 - 7:25 am UTC

read
</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:2588723819082 <code>

a soft parse does NOT mean we do not parse again -- if it did, it wouldn't have the word parse in it!!

A soft parse is a parse. A hard parse is a parse. They are both parsed, just different kinds and hopefully that writeup I point you to will clear that up.

when you use a REF CURSOR, by definition -- a parse will take place on each and every open. It might be a soft parse, it might be a hard parse -- but it'll be a parse.

Arun Gupta, December 09, 2002 - 10:05 am UTC

If I set session_cached_cursors to say 100 then are there any other implications that I need to aware of, like increase in memory requirements etc.?
Thanks


Tom Kyte
December 09, 2002 - 10:19 am UTC

marginal increase in UGA memory in the dedicated server or SGA depending on where your UGA is coming from.

Parsed cursor cache

Christophe Parmentier, December 09, 2002 - 11:58 am UTC

Hi, Tom
Reading this article I reminded something that bothered me some time ago...

In our DataWarehouse, we use dynamic tables, with dynamic names, we can't bind a table name, does that mean the cursor will always be hard parsed ?

Example:

'Select ... from ' || DYN_TABLE_NAME || ' WHERE Field = :1'

Is it useful to bind a such request even if it will be parsed each time, at least for the Table_name ?

I thought binding a such request was useless, was I right ?

Thanks for the help ;) I'll probably sleep better!

Tom Kyte
December 09, 2002 - 12:56 pm UTC

if dyn_table_name is different each time, you might as well not bind.

Soft/Hard Parse

A reader, December 12, 2002 - 1:46 pm UTC


This is on my earlier question of ref cursor parsing. In that sense how is ref cursor different from regular cursors. THis is with reference only to the context of parsing.

Can you please illustrate with examples.

regards

Tom Kyte
December 12, 2002 - 2:17 pm UTC

Ok, in order to show that ref cursors cannot be cached, all I need to do is open a "single" ref cursor twice.  If we cached - there could only be ONE instance of it right?  Well, the semantics of ref cursors do not prevent them from being opened as many times as the caller wants.  So, consider:


ops$tkyte@ORA920> create or replace package demo_pkg
  2  as
  3      type rc is ref cursor;
  4
  5      procedure get_rc( p_cursor out rc );
  6
  7      procedure open_regular_cursor;
  8  end;
  9  /

Package created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> create or replace package body demo_pkg
  2  as
  3
  4  cursor C is select * from DUAL;
  5
  6  procedure get_rc( p_cursor out rc )
  7  is
  8  begin
  9      open p_cursor for select * from dual;
 10  end;
 11
 12  procedure open_regular_cursor
 13  is
 14  begin
 15      open C;
 16  end;
 17
 18
 19  end;
 20  /

Package body created.

ops$tkyte@ORA920> show errors
No errors.
ops$tkyte@ORA920> pause

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable x refcursor
ops$tkyte@ORA920> variable y refcursor
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec demo_pkg.get_rc( :x );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec demo_pkg.get_rc( :y );

PL/SQL procedure successfully completed.

<b>that "single" ref cursor is opened twice now -- but it isn't really that the ref cursor is opened, there are TWO separate cursor variables that have open result sets associated with them.  PLSQL doesn't have a cursor to cache -- the client OWNS this cursor so plsql cannot cache them.  We had to have 2 parses in the above -- one each for :x and :y</b>


ops$tkyte@ORA920>
ops$tkyte@ORA920> print x

D
-
X

ops$tkyte@ORA920> print y

D
-
X

ops$tkyte@ORA920>
ops$tkyte@ORA920> exec demo_pkg.open_regular_cursor

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec demo_pkg.open_regular_cursor
BEGIN demo_pkg.open_regular_cursor; END;

*
ERROR at line 1:
ORA-06511: PL/SQL: cursor already open
ORA-06512: at "OPS$TKYTE.DEMO_PKG", line 4
ORA-06512: at "OPS$TKYTE.DEMO_PKG", line 15
ORA-06512: at line 1


<b>that just shows why PLSQL can "cache" a cursor -- it owns them, there can only be one instance of it -- it is "well behaved"

Maybe this is even a better example:</b>

ps$tkyte@ORA920> create or replace package body demo_pkg
  2  as
  3
  4  cursor C is select * from DUAL;
  5
  6  cnt number := 1;
  7
  8  procedure get_rc( p_cursor out rc )
  9  is
 10  begin
 11      if ( cnt = 1 )
 12      then
 13          open p_cursor for select 'Hello', 55 from dual;
 14          cnt := 2;
 15      else
 16          open p_cursor for select 'World', sysdate from dual;
 17      end if;
 18  end;
 19
 20  procedure open_regular_cursor
 21  is
 22  begin
 23      open C;
 24  end;
 25
 26
 27  end;
 28  /

Package body created.

ops$tkyte@ORA920> show errors
No errors.
ops$tkyte@ORA920> pause

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable x refcursor
ops$tkyte@ORA920> variable y refcursor
ops$tkyte@ORA920>
ops$tkyte@ORA920> exec demo_pkg.get_rc( :x );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec demo_pkg.get_rc( :y );

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> print x

'HELL         55
----- ----------
Hello         55

ops$tkyte@ORA920> print y

'WORL SYSDATE
----- ---------
World 12-DEC-02

ops$tkyte@ORA920>


<b>how could PLSQL cache that?  it doesn't even know what the cursor will be until someone askes for it and the code runs...</b>

 

Thanks

A reader, December 12, 2002 - 3:15 pm UTC

That was a very clear explanation.

Summing it up regular cursors would be cached but Ref Cursors cannot be cached because its value would not be known until runtime.
But, we can get around this by setting session_cached_cursors. Correct?

My other question and the most important question to me is specific to my application - Oracle Reports. Would the same issue of caching be there on ref cursor queries on reports also? Or will a regular query and ref cursor query have the same performance when taken in Oracle Reports.

Really appreciate your time and effort...

Tom Kyte
December 12, 2002 - 4:02 pm UTC

You can "help it" by using session cached cursors which is a softer "soft parse".


In reports - if you enable sql_trace and look at the parse/execute counts on your queries, you would be able to tell if you would benefit from session cached cursors. If parse > 1, you would most likely benefit from it.

session_cursor_max and open_cursor

A reader, May 17, 2003 - 9:58 pm UTC

sorry for my confusion.

If I have value set as 500 for open_cursor, is not that the cursor will persist for the session. Then why should we set session_cursor_max again?

Another silly question...
If procedure open the ref cursor for select * from emp;, will there be 2 cursor, one for ref_cur and other for 'select * from emp'



Tom Kyte
May 18, 2003 - 10:23 am UTC

what is "session cursor max"?


open cursor is the maximum number of concurrent open cursors you have. we allocate them 64 at a time (so no, it is not the number the "persist" for a session, it is a threshold, a maximum boundary value).


a ref cursor is a cursor. if you open a ref cursor for select * from emp there is ONE open cursor -- the ref cursor. the select * from emp, that is just text.

Sorry, I meant session_cached_cursor

A reader, May 18, 2003 - 12:08 pm UTC


Tom Kyte
May 18, 2003 - 1:08 pm UTC

that caches cursors you closed, for performance.

so, open cursors = max cursors you can have.
session cached cursors = how many we try to keep open when you prematurely closed them, for performance.

they are apples and oranges when comparing.

Is not parsing happens for tetxt

A reader, May 18, 2003 - 12:10 pm UTC

Tom

Is it not true that parsing happens for SQL text. So how ref cursor will be parsed ?

Tom Kyte
May 18, 2003 - 1:09 pm UTC

exactly like a "non ref cursor" would be.

I wish I could transport your inteliigence........

A reader, May 18, 2003 - 3:24 pm UTC

Tom, you are expert and I am not. I know everything is clear in your mind, but not mine.

Let me rephrase it.

There is a statement called ( p_ref is predifned ref cursor)

open p_ref for select * from emp;

so here we have a cursor (p_ref) and a SQL statement.

Now which one of the 2 is getting parsed.

My understanding (confusion) is that

a) SQL statement is parsed ( syntax/semantics checking, optmization, parse tree etc).

b) Cursor is handle for statement execution and does not need parsing.

I know if I get answer from anyone, it's you.

I am sorry for killing your valuable time, but hope that I am not only person who will be benefited by the answer.




Tom Kyte
May 18, 2003 - 6:26 pm UTC

the cursor is getting opened.

the statement that will be parsed is represented by the SQL you have "select * from emp"


the SQL gets parsed, the cursor points to the parsed representation.



Query

Peter, August 14, 2003 - 9:23 am UTC

so...
CREATE OR REPLACE PROCEDURE T1
(
vCursor OUT XX_RECORDSET.OUT_CURSOR
)
BEGIN
OPEN vCursor FOR
SELECT * FROM EMP;
END;
/
-------------------
CREATE OR REPLACE PROCEDURE T2
(
vCursor OUT XX_RECORDSET.OUT_CURSOR
)AS
vStmt varchar2(2000);
BEGIN
vStmt :="Select * from EMP";
OPEN vCursor FOR
vStmt;
END;
/

Both the procedure are same??.
Because I am facing some problem here.
I have a procedure like this

CREATE OR REPLACE PROCEDURE Test1
(
vCursor OUT XX_RECORDSET.OUT_CURSOR
)
vStmt varchar2(2000);
BEGIN
vStmt :="Select * from EMP";
IF Some_Condition THEN
vStmt := vStmt ||"Where EMPID =v_id"
END IF;
IF Some_Condition THEN
vStmt := vStmt ||"AND EMP_NAME =v_Name"
END IF;
IF Some_Condition THEN
vStmt := vStmt ||"AND MGR=v_Mgr"
END IF;
OPEN vCursor FOR
vStmt;
END;

This is working fine.
But I don't like to use dynamic SQL.
So I have rewritten this procedure like this

CREATE OR REPLACE PROCEDURE Test2
(
vCursor ........
)
BEGIN
IF Some_Condition THEN
OPEN vCursor FOR
Select * from EMP Where EMPID =v_id;
ELSIF Some_Condition THEN
OPEN vCursor FOR
Select * from EMP Where EMPID =v_id
AND EMP_NAME =v_Name;
ELSIF Some_Condition THEN
OPEN vCursor FOR
Select * from EMP Where EMPID =v_id
AND EMP_NAME =v_Nam
AND MGR=v_Mgr;
END IF;
END;


logically both are giving same result.No problem. But I would like to know about the efficienty point of view.

Actually my question is in the procedure Test1, I have only one "Open vCursor For Selec..." statement. But
in the Test2 procedure I have 3 "Open vCursor FOR Select ..." statement. According to your previous
explanation, soft/hard parse will be done. So in the procedure Test2 it has to parse 3 sql statement.
But in the Test1 procedure the parsing will be done for one SQL statement. So which way will be usefull?. Here
I have just given these procedures for example. But in my real application I have used this same logic.
So I end up with 12 SQL statements.
So please suggest me, which approach I should follow?

Thanks in advance
Peter


Tom Kyte
August 14, 2003 - 10:12 am UTC

static sql is always preferred for

o performance
o maintanence
o realiability

in plsql. procedure test2 will only parse THE single statement you request.

Dynamic no impact?

Jon, August 28, 2003 - 12:44 am UTC

Since ref cursors are parsed each time, is there actually any difference between using static sql and dynamic sql when associate with a ref cursor (assuming no binding required). My testing seems to indiciate there is no significant difference:

declare
type refcursor is ref cursor;
rc refcursor;
r_client client%rowtype;
begin
execute immediate 'truncate table run_stats';
commit;
insert into run_stats select 'before', stats.* from stats;
for x in 1..100 loop
open rc for 'select * from client where client_id = 711345';
loop
fetch rc into r_client;
exit when rc%notfound;
end loop;
close rc;
end loop;
insert into run_stats select 'after 1', stats.* from stats;
for x in 1..100 loop
open rc for select * from client where client_id = 711345;
loop
fetch rc into r_client;
exit when rc%notfound;
end loop;
close rc;
end loop;
insert into run_stats select 'after 2', stats.* from stats;
commit;
end;
/

select * from report_stats;

Run 1 Run 2 Diff
------------------------------------------------------------
STAT redo size 20900 20984 84
LATCH cache buffers chains 562 586 24
LATCH shared pool 275 266 9
LATCH redo allocation 13 21 8
STAT db block gets 26 19 7
STAT session logical reads 437 430 7
LATCH library cache 458 464 6
STAT db block changes 27 22 5
LATCH checkpoint queue latch 2 6 4
STAT redo entries 16 13 3
STAT enqueue requests 5 3 2
STAT calls to get snapshot scn: kcmgss 106 105 1
STAT parse time cpu 3 2 1
STAT recursive cpu usage 16 17 1
STAT free buffer requested 4 3 1
STAT enqueue releases 4 3 1


Tom Kyte
August 28, 2003 - 7:46 am UTC

you lose the dependencies....

you lose the assurance that your SQL is correct....


if you can do it statically, do it.

session_cached_cursors

Charlie, May 09, 2004 - 12:08 pm UTC

If I set a value for this parameter, does oracle actually keep the cursors asssociated with my session SQL/PL SQL statements in the UGA not in the Shared pool? Could you explain the difference between setting this parameter to a value and zero? Thanks.

Tom Kyte
May 10, 2004 - 7:49 am UTC

it sets up for a softer soft parse. It keeps a soft link to the parsed stuff in the sga. it needs to still confirm that all is OK with the stuff in the sga (hence it is still a parse, still latches). It consumes some uga memory in order to accomplish this and manage the 'cache cursors' it has.


if you set this to 100, we'll attempt to keep a soft link to your 100 most frequently re-executed statements.

set to 0 and we won't.


it is a softer soft parse -- but remember, the best parse is NO parse at all.

ref cursor

A reader, June 23, 2004 - 5:25 pm UTC

Tom,
I have a questions about ref cursor. I have to write a procedure that will accept ref cursor as parameter and will create csv files with column names in it. This procedure can be exceuted from sevral different programs(means different ref cursors can be passed).

Could you Pl show me an example of how to do it?

Thanks Tom for your help and this great site for us.

Tom Kyte
June 24, 2004 - 8:30 am UTC

you cannot do it -- you have to use DBMS_SQL.

with dbms_sql you can "describe" a query and figure out the number of columns, their datatypes, their lengths and their names.

with a ref cursor you have to know in plsql at compile time how many columns, their datatypes, their lengths and so on.


</code> http://asktom.oracle.com/~tkyte/flat/index.html <code>
has a link to the plsql code to do this already. the caller just sends you a query in a string or you could change the API to take a dbms_sql cursor instead.

Ref Cursor With Bulk Collect

ARC, August 11, 2004 - 12:30 pm UTC

Hi,
Need your help in solving my problem.
I am using Oracle version
Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
PL/SQL Release 8.1.7.0.0 - Production
CORE 8.1.7.0.0 Production
TNS for 32-bit Windows: Version 8.1.7.0.0 - Production
NLSRTL Version 3.4.1.0.0 - Production.

I am writing procedure with a table name as a parameter.
In procedure I am bulding query string. Using this query string I am opening a cursor which is defined as ref cursor type.
Cursor is opening fine. I mean statement is parsing fine. The problem is at time of fetching data. I am using bulk collect to fectch data. It is giving below error.
GEFDWDEV>EXEC SP_COM401_ASSIGN_BUS_TIERS('TB_SDW023_STG_SALES',:PS,:PR)
BEGIN SP_COM401_ASSIGN_BUS_TIERS('TB_SDW023_STG_SALES',:PS,:PR); END;

*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at "DWADMIN.SP_COM401_ASSIGN_BUS_TIERS", line 55
ORA-06512: at line 1
But the same thing is worked fine with single record. Also I used the same above in another procedure working fine. The diff. is in the procedure which is working fine the statement is not building dynamically.
Also I teird with EXECUTE IMMEDIATE <QUERY STRING> BULK COLLECT INTO < >;
gETTING below error.
Warning: Procedure created with compilation errors.

Elapsed: 00:00:00.12
GEFDWDEV>SHO ERR
Errors for PROCEDURE SP_COM401_ASSIGN_BUS_TIERS:

LINE/COL ERROR
-------- -----------------------------------------------------------------
39/27 PLS-00103: Encountered the symbol "BULK" when expecting one of
the following:
. ( * @ % & = - + ; < / > at in mod not rem return returning
<an exponent (**)> <> or != or ~= >= <= <> and or like
between into using is null is not || is dangling.

Here I am giving my code.
CREATE OR REPLACE PROCEDURE SP_COM401_ASSIGN_BUS_TIERS (P_TABLE_NAME_I IN VARCHAR2, P_STATUS_O OUT NUMBER, P_RESULT_O OUT VARCHAR2)
AS
TYPE TYP_REFCURSOR IS REF CURSOR;
L_CURSOR1 TYP_REFCURSOR ;

/*******************************************************************************
* Declaration of object types for each field in the table to be updated
********************************************************************************/
TYPE TYP_BUS_TIER0 IS TABLE OF TB_SDW023_STG_SALES.BUS_TEAM_TIER0_DSC%TYPE INDEX BY BINARY_INTEGER;
TYPE TYP_BUS_TIER1 IS TABLE OF TB_SDW023_STG_SALES.BUS_TEAM_TIER1_DSC%TYPE INDEX BY BINARY_INTEGER;
TYPE TYP_BUS_TIER2 IS TABLE OF TB_SDW023_STG_SALES.BUS_TEAM_TIER2_DSC%TYPE INDEX BY BINARY_INTEGER;
TYPE TYP_BUS_TIER3 IS TABLE OF TB_SDW023_STG_SALES.BUS_TEAM_TIER3_DSC%TYPE INDEX BY BINARY_INTEGER;
TYPE TYP_BUS_DIM_KEY IS TABLE OF TB_SDW012_DIM_BUS_TIERS.BUS_DIM_KEY%TYPE INDEX BY BINARY_INTEGER;
TYPE TYP_PROD_PS_FLG IS TABLE OF TB_SDW012_DIM_BUS_TIERS.PRODUCT_SERVICE_PS_FLG%TYPE INDEX BY BINARY_INTEGER;
TYPE TYP_ROWID IS TABLE OF ROWID INDEX BY BINARY_INTEGER;
TYPE TYP_LOG IS TABLE OF TB_SDW023_STG_SALES.PROCESS_LOG_DSC%TYPE INDEX BY BINARY_INTEGER;
/*******************************************************************************
* Declaration of binary integer tables for each field in the table to be updated
********************************************************************************/
FLD_BUS_TIER0 TYP_BUS_TIER0;
FLD_BUS_TIER1 TYP_BUS_TIER1;
FLD_BUS_TIER2 TYP_BUS_TIER2;
FLD_BUS_TIER3 TYP_BUS_TIER2;
FLD_BUS_DIM_KEY TYP_BUS_DIM_KEY;
FLD_PROD_PS_FLG TYP_PROD_PS_FLG;
FLD_ROWID TYP_ROWID;
FLD_LOG TYP_LOG;

V_STMT VARCHAR2(2000);

BEGIN
DBMS_OUTPUT.ENABLE(100000);

V_STMT := 'SELECT A.BUS_DIM_KEY BUS_DIM_KEY, A.BUS_TEAM_TIER0_DSC BUS_TEAM_TIER0_DSC, '||CHR(10)||'A.BUS_TEAM_TIER1_DSC BUS_TEAM_TIER1_DSC, A.BUS_TEAM_TIER2_DSC BUS_TEAM_TIER2_DSC,'||CHR(10)||'A.BUS_TEAM_TIER3_DSC BUS_TEAM_TIER3_DSC, A.PRODUCT_SERVICE_PS_FLG PRODUCT_SERVICE_PS_FLG,'||CHR(10)||'C.ROWID ROW_ID'||CHR(10);
V_STMT := V_STMT || 'FROM '|| P_TABLE_NAME_I ||' C, TB_COM003_DIM_BUS_HIER A,'||CHR(10)||'TB_SDW005_MAP_SUBPRDLN_BUSTIER B'||CHR(10);
V_STMT := V_STMT || 'WHERE (C.SUB_PROD_LN_CD||NVL(C.DISTRIBUTION_CHANNEL_CD, '||'''#'''||')||NVL(C.CUST_GRP_CD, '||'''#'''||')) = (B.SUB_PROD_LN_CD||NVL(B.DISTRIBUTION_CHANNEL_CD, '||'''#'''||')||NVL(B.CUST_GRP_CD, '||'''#'''||'))'||CHR(10);
V_STMT := V_STMT || 'AND (B.BUS_TEAM_TIER1_DSC||B.BUS_TEAM_TIER2_DSC||B.BUS_TEAM_TIER3_DSC) = (A.BUS_TEAM_TIER1_DSC||A.BUS_TEAM_TIER2_DSC||A.BUS_TEAM_TIER3_DSC)';

-- DELETE T_DUMMY;

-- INSERT INTO T_DUMMY VALUES(V_STMT);
-- COMMIT;

--DBMS_OUTPUT.PUT_LINE('1');
-- OPEN L_CURSOR1 FOR V_STMT;
--DBMS_OUTPUT.PUT_LINE('2');

/* SELECT A.BUS_DIM_KEY BUS_DIM_KEY, A.BUS_TEAM_TIER0_DSC BUS_TEAM_TIER0_DSC, A.BUS_TEAM_TIER1_DSC BUS_TEAM_TIER1_DSC, A.BUS_TEAM_TIER2_DSC BUS_TEAM_TIER2_DSC, A.BUS_TEAM_TIER3_DSC BUS_TEAM_TIER3_DSC, A.PRODUCT_SERVICE_PS_FLG PRODUCT_SERVICE_PS_FLG, C.ROWID ROW_ID
FROM P_TABLE_NAME_I C,
TB_COM003_DIM_BUS_HIER A,
TB_SDW005_MAP_SUBPRDLN_BUSTIER B
WHERE (C.SUB_PROD_LN_CD||NVL(C.DISTRIBUTION_CHANNEL_CD,'#')||NVL(C.CUST_GRP_CD,'#')) = (B.SUB_PROD_LN_CD||NVL(B.DISTRIBUTION_CHANNEL_CD,'#')||NVL(B.CUST_GRP_CD,'#'))
AND (B.BUS_TEAM_TIER1_DSC||B.BUS_TEAM_TIER2_DSC||B.BUS_TEAM_TIER3_DSC) = (A.BUS_TEAM_TIER1_DSC||A.BUS_TEAM_TIER2_DSC||A.BUS_TEAM_TIER3_DSC);
*/
-- if l_cursor1%isopen then
-- DBMS_OUTPUT.PUT_LINE('3');
-- else
-- DBMS_OUTPUT.PUT_LINE('4');
-- end if;

/* LOOP
FETCH L_CURSOR1 BULK COLLECT INTO
FLD_BUS_DIM_KEY,
FLD_BUS_TIER0,
FLD_BUS_TIER1,
FLD_BUS_TIER2,
FLD_BUS_TIER3,
FLD_PROD_PS_FLG,
FLD_ROWID LIMIT 1000;
exit;
*/

EXECUTE IMMEDIATE V_STMT BULK COLLECT INTO
FLD_BUS_DIM_KEY,
FLD_BUS_TIER0,
FLD_BUS_TIER1,
FLD_BUS_TIER2,
FLD_BUS_TIER3,
FLD_PROD_PS_FLG,
FLD_ROWID LIMIT 1000;


FORALL I IN 1..FLD_ROWID.COUNT
UPDATE TB_SDW023_STG_SALES
SET BUS_DIM_KEY = FLD_BUS_DIM_KEY(I),
BUS_TEAM_TIER0_DSC = FLD_BUS_TIER0(I),
BUS_TEAM_TIER1_DSC = FLD_BUS_TIER1(I),
BUS_TEAM_TIER2_DSC = FLD_BUS_TIER2(I),
BUS_TEAM_TIER3_DSC = FLD_BUS_TIER3(I),
PRODUCT_SERVICE_PS_FLG = FLD_PROD_PS_FLG(I)
WHERE ROWID = FLD_ROWID(I);
COMMIT;

-- EXIT WHEN L_CURSOR1%NOTFOUND;
-- END LOOP;
-- CLOSE L_CURSOR1;

COMMIT;

P_STATUS_O := 0;
P_RESULT_O := 'SUCCESS';

/*EXCEPTION
WHEN OTHERS THEN
HANDLE_ERROR('SP_COM401_ASSIGN_BUS_TIERS'); */
END SP_COM401_ASSIGN_BUS_TIERS;
/

Please help me.
Thanking you in advance.
ARC



Tom Kyte
August 11, 2004 - 1:51 pm UTC

bulk collect from a dynamically opened ref cursor did not work in 8i, was not supported:

ops$tkyte@ORA817DEV> declare
  2          type rc is ref cursor;
  3          c rc;
  4          type array is table of number index by binary_integer;
  5          d array;
  6  begin
  7          open c for 'select 1 x from dual';
  8          fetch c bulk collect into d;
  9  end;
 10  /
declare
*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at line 8
 
 
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> declare
  2          type rc is ref cursor;
  3          c rc;
  4          type array is table of number index by binary_integer;
  5          d array;
  6  begin
  7          open c for select 1 x from dual;
  8          fetch c bulk collect into d;
  9  end;
 10  /
 
PL/SQL procedure successfully completed.


in 9i:

ops$tkyte@ORA920> declare
  2          type rc is ref cursor;
  3          c rc;
  4          type array is table of number index by binary_integer;
  5          d array;
  6  begin
  7          open c for 'select 1 x from dual';
  8          fetch c bulk collect into d;
  9  end;
 10  /
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          type rc is ref cursor;
  3          c rc;
  4          type array is table of number index by binary_integer;
  5          d array;
  6  begin
  7          open c for select 1 x from dual;
  8          fetch c bulk collect into d;
  9  end;
 10  /
 
PL/SQL procedure successfully completed.
 
 

Ref Cursor with Bulk Collect

ARC, August 12, 2004 - 6:58 am UTC

Tom,
Thanks for your feedback.
Could you please suggest me how can I code this.
Really I want to use bulk option in my process.

Thanks
ARC

Tom Kyte
August 12, 2004 - 9:24 am UTC

are you sure you even need to use procedural code.

when I see:


loop
fetch bulk collect;
forall i update


I'm thinking "should be a single sql statement" -- just update.

Ref Cursor with bulk collect

ARC, August 14, 2004 - 8:28 am UTC

Tom,
Really I did not get you.
Could you please explain me in detail.

Thank You.
ARC

Tom Kyte
August 14, 2004 - 1:36 pm UTC

update ( join the data here )
set x = y;


don't

open c for select * from t;
loop
fetch c into ....
update t2 set ....


just

update ( select t.x, t2.y
from t, t2
where ... )
set x = y;




Ref Cursor

ARC, August 16, 2004 - 5:45 am UTC

Tom,
Thanks for your feedback.

I am trying update as you mentioned. But I am getting following error.
GEFDWDEV>/
STG_BUS_DIM_KEY = DIM_BUS_DIM_KEY
*
ERROR at line 22:
ORA-01779: cannot modify a column which maps to a non key-preserved table

I below statement TABLE TB_SDW023_STG_SALES is detail table for other two tables in query.
The column TB_SDW023_STG_SALES.BUS_DIM_KEY is foreign key with reference to TB_COM003_DIM_BUS_HIER.BUS_DIM_KEY and also the combination of TB_SDW023_STG_SALES.SUB_PROD_LN_CD, TB_SDW023_STG_SALES.DISTRIBUTION_CHANNEL_CD, TB_SDW023_STG_SALES.CUST_GRP_CD is foreign key with combinational unique key on TB_SDW005_MAP_SUBPRDLN_BUSTIER with columns SUB_PROD_LN_CD, DISTRIBUTION_CHANNEL_CD, CUST_GRP_CD

Update Statement:
UPDATE
(
SELECT A.BUS_DIM_KEY DIM_BUS_DIM_KEY,
A.BUS_TEAM_TIER0_DSC DIM_BUS_TEAM_TIER0_DSC,
A.BUS_TEAM_TIER1_DSC DIM_BUS_TEAM_TIER1_DSC,
A.BUS_TEAM_TIER2_DSC DIM_BUS_TEAM_TIER2_DSC,
A.BUS_TEAM_TIER3_DSC DIM_BUS_TEAM_TIER3_DSC,
A.PRODUCT_SERVICE_PS_FLG DIM_PRODUCT_SERVICE_PS_FLG,
C.BUS_DIM_KEY STG_BUS_DIM_KEY,
C.BUS_TEAM_TIER0_DSC STG_BUS_TEAM_TIER0_DSC,
C.BUS_TEAM_TIER1_DSC STG_BUS_TEAM_TIER1_DSC,
C.BUS_TEAM_TIER2_DSC STG_BUS_TEAM_TIER2_DSC,
C.BUS_TEAM_TIER3_DSC STG_BUS_TEAM_TIER3_DSC,
C.PRODUCT_SERVICE_PS_FLG STG_PRODUCT_SERVICE_PS_FLG
FROM TB_SDW023_STG_SALES C,
TB_COM003_DIM_BUS_HIER A,
TB_SDW005_MAP_SUBPRDLN_BUSTIER B
WHERE (C.SUB_PROD_LN_CD||NVL(C.DISTRIBUTION_CHANNEL_CD,'#')||NVL(C.CUST_GRP_CD,'#')) = (B.SUB_PROD_LN_CD||NVL(B.DISTRIBUTION_CHANNEL_CD,'#')||NVL(B.CUST_GRP_CD,'#'))
AND (B.BUS_TEAM_TIER1_DSC||B.BUS_TEAM_TIER2_DSC||B.BUS_TEAM_TIER3_DSC) = (A.BUS_TEAM_TIER1_DSC||A.BUS_TEAM_TIER2_DSC||A.BUS_TEAM_TIER3_DSC)
)
SET
STG_BUS_DIM_KEY = DIM_BUS_DIM_KEY,
STG_BUS_TEAM_TIER0_DSC = DIM_BUS_TEAM_TIER0_DSC,
STG_BUS_TEAM_TIER1_DSC = DIM_BUS_TEAM_TIER1_DSC,
STG_BUS_TEAM_TIER2_DSC = DIM_BUS_TEAM_TIER2_DSC,
STG_BUS_TEAM_TIER3_DSC = DIM_BUS_TEAM_TIER3_DSC,
STG_PRODUCT_SERVICE_PS_FLG = DIM_PRODUCT_SERVICE_PS_FLG
/

Why I am getting the error as my staging table TB_SDW023_STG_SALES is a key-preserved table ?

Thanks
ARC

Tom Kyte
August 16, 2004 - 8:31 am UTC

you need primary keys (unique constraints) in place.

if you get this warning -- it is saying "hey, many rows could match here updating STG_BUS_DIM_KEY many times. that would result in ambigous non-reproducible updates.

it is all of your functions in there to join, the database has *no clue* that anything is even remotely "safe" here.



God, i was stupid :)

Toms, January 21, 2005 - 2:37 pm UTC

I was trying for about 1.5 hours to get ref cursor work with bulk collect in 8.1.7

Then i read this question, and when i came to place that "if i see fetch x bulk collect in y; and then forall" - i understood that that exactly was my case.

Not all variables bound

Kanth, May 09, 2005 - 4:23 pm UTC

Tom,
I am using the following code to fetch values from a ref cursor, by building a Dynamic sql (ofcourse binding the values).
But i'm getting Not all variables bound message
-- Here is the code
declare

v_sql varchar2(2000):='Select emp.empno,emp.sal,emp.deptno,dept.loc,dept.dname from emp ,dept ';
v_where varchar2(200):='Where ';
p_empno integer:=1234;
p_ref sys_refcursor;
p_deptno integer:=10;
v_column dbms_sql.varchar2_table;
v_values varchar2(100):='';

begin


v_where:=v_where||'emp.deptno=dept.deptno and emp.empno=:empno and dept.deptno=:dno ';
v_sql:=v_sql||v_where ;
dbms_output.put_line(v_sql);
v_column(1):=p_empno;
v_column(2):=p_deptno;
for i in 1..v_column.count
loop
v_values:=v_values||','||v_column(i);
end loop;
v_values:=ltrim(v_values,',');


open p_ref for v_sql using v_values ;


end;
--End of the code
I'm guessing v_values is the culprit here. Is there any way that i can append values from my array into a value and use it for using clause?

Thanks,

Tom Kyte
May 09, 2005 - 5:49 pm UTC

using v_values(1), v_values(2);

If you have a varying number of binds, you can use an application context
</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:1288401763279 <code>

Here are the tables and data

Kanth, May 09, 2005 - 4:56 pm UTC

CREATE TABLE DEPT
(
DEPTNO INTEGER,
DNAME VARCHAR2(20 BYTE),
LOC VARCHAR2(30 BYTE)
)
;


CREATE TABLE EMP
(
EMPNO INTEGER,
SAL INTEGER,
DEPTNO INTEGER
);

INSERT INTO EMP ( EMPNO, SAL, DEPTNO ) VALUES (
1234, 2345, 10);
INSERT INTO EMP ( EMPNO, SAL, DEPTNO ) VALUES (
2345, 4501, 10);
INSERT INTO EMP ( EMPNO, SAL, DEPTNO ) VALUES (
3456, 4501, 20);
INSERT INTO EMP ( EMPNO, SAL, DEPTNO ) VALUES (
4567, 3456, 20);


INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES (
10, 'Accounting', 'Frankfort');
INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES (
20, 'Human Resources', 'Frankfort');

COMMIT;

But..V_values is not an array

Kanth, May 10, 2005 - 11:27 am UTC

Tom, Thanks for your reply. V_values, i was using , is a varchar and i'm appending values to it from array.
I could use array directly in "Using array(1),
array(2)" ..but array elements are dynamically built and could change for each call to this proc.
So how do i use them in using clause if i don't know the no. of array elements?

Thanks for all your help

Tom Kyte
May 10, 2005 - 1:27 pm UTC

did you see the link I gave you? it was exactly in anticipation of this question.

Is there any other way

Kanth, May 11, 2005 - 11:39 am UTC

I have read your link talking about application context, sounds good, but to implement that i've to rewrite my entire logic and i was hoping not to take that route (atleast for now).
Do you think there is any other way to pass array values in using clause ?
when i append all the values to a string (comma seperated) and use that in my query like
open p_ref for vqry using v_using;

where v_using has values like 1,1,20,12

I think it doesn't work because v_values is like one big string rather than values. is my thinking right?
If so , how do i seperate them as values
Thanks


Tom Kyte
May 11, 2005 - 12:26 pm UTC

why would you have to rewrite the entire application logic? you are asking how to do a piece of code you haven't written yet.

Application Context issue..

Kanth, May 23, 2005 - 11:22 am UTC

Tom,
I have used Application Context approach as you showed earlier in this thread. It works great. I have to repeat this for different procedures . This is a search function, users will be using from front end, and we are expecting there will be atleast 20 or 30 more search functions that needs to be implemented in our application. Will this cause any performance issues if i create 30 contexts and use them seperately (one for each procedure)?

Thanks,

Tom Kyte
May 23, 2005 - 3:30 pm UTC

well, it'll be up to you whether you want 1 or 30 contexts, I've not observed any sort of scaling issues with a single or multiple namespaces.

A reader, May 24, 2005 - 9:48 am UTC

I am trying to use distinct clause in the select statement; but it is throwing error. Could you please give me the right approach? Here is my code:

CREATE OR REPLACE
TYPE MY_REC AS OBJECT
(
fam_name VARCHAR2(35),
given_name VARCHAR2(15),
status_code VARCHAR2(50)
);


CREATE OR REPLACE
TYPE MY_TAB
AS VARRAY (150) OF MY_REC;

CREATE OR REPLACE CONTEXT C1 USING TEST;

PROCEDURE TEST(I_FNAME IN VARCHAR2,
I_LNAME IN VARCHAR2,
O_REC OUT MY_TAB)
AS
L_QRY VARCHAR2(3000) DEFAULT
-----TRIED 'DISTINCT' BEFORE 'MY_REC' AS WELL AS
---FNAME
'SELECT MY_REC(FNAME,LNAME,STATUS) FROM T1,T2 WHERE ' ||
'T1.COL1=T2.COL2';

BEGIN
DBMS_SESSION.set_context (C1', 'lname', i_lname);
l_qry := l_qry || ' and fname = sys_context( ''C1'', ''lname'' ) ';

END;

Thanks.

Tom Kyte
May 24, 2005 - 1:06 pm UTC

unless your object is "orderable" -- has a map or order method -- there is no concept of ordering for them and distinct needs that:

*
ERROR at line 1:
ORA-22950: cannot ORDER objects without MAP or ORDER method


You need to teach us how to "sort" your object first.

but quick fix would be:

select my_rec(fname,lname,status)
from (select distinct fname,lname,status
from t1,t2
where ........ )



A reader, May 24, 2005 - 1:21 pm UTC

Thanks for your response Tom!
I have tried the quick fix you suggested, but my idea is to append the conditional clauses at the end (when user decides to filter by a particular column)

In the quick fix you suggested, how can i append the conditions dynamically using session context?

Hope I have explained it clearly.

Tom Kyte
May 24, 2005 - 1:52 pm UTC

not sure why it matters if you put them in the middle or at the end:


'select mytable(a,b,c)
from (select distinct a,b,c
from t ' || whatever || '
)'

why do you need to add to the end? it is just as easy to add your where clause and a closing ")"

A reader, May 24, 2005 - 2:45 pm UTC

Once again thanks for your time.
My concern is:

'select mytable(a,b,c)
from (select distinct a,b,c
from t ' || whatever || '
)'

in the above query we have closed the inner query with ')'.
so the session context will include it within that closing ')' or will it append outside the ')'?

for eg. will it become something like this, iin which case it would be wrong:

'select mytable(a,b,c)
from (select distinct a,b,c
from t ' || whatever || '
)
and lname = ...
and fname = ....'

(as there won't be a 'where' clause in the outer query, this will be wrong right?)



Tom Kyte
May 24, 2005 - 4:03 pm UTC

i'm saying you glue in your predicate AND THEN close the query with the ")"

you own the query don't you??


without a map or order method, you will not distinct an object type.

40 queries per second system -- SELECT, Ref Cursor or PipelineTable Function

Charlie Zhu, May 12, 2006 - 6:57 pm UTC

We got a 20 million rows single hash cluster table,
frequently queried by IN-List equal look-up.

30 concurrent users, totally run 40 times per second.

What do you recommend?

JDBC PrepareStatement SELECT, Ref Cursor or Pipeline Table Function ?

<Quote>
Followup:
I would use ref cursors and session cached cursors

OR (after benchmarking) letting the java programs:
select * from TABLE( your_plsql_pipelined_function );
<Quote End>

Ref Cursor is a little better than Table Function, 135:154 = 87% run time,
both slower than SELECT, 118 hsec.

== == ===
Here is the bench mark in single user mode

1) REF CURSOR vs. SELECT

Declare
l_string varchar2(4000);
c_sp ABELISTING.sp_sql_query.listidcurtyp;
--c_sp SYS_REFCURSOR;
l_i Number(20);

Begin
l_string := '700000173,680000143,680000221,700000135,180000118,680000265,720000215,700000112,700000151,680000251,600000120,700000142,720000158,700000262,660000202,600000115,600000123,600000126,600000139,600000141,600000145,600000148,600000152,600000159,600000188,600000244,600000253,660000246,680000200,700000136';

runStats_pkg.rs_start;

For i in 1 .. 2000 Loop
/* For c in( select column_value listingsid from table(cast(abelisting.tab_f(l_string) as nt_type)))
Loop
l_i := c.listingsid;
End loop;
*/
ABELISTING.sp_sql_query.list_df(c_sp,l_string );
while (True)
Loop
fetch c_sp into l_i;
exit when c_sp%notfound;
End loop;

End Loop;

runStats_pkg.rs_middle;

For i in 1 .. 2000 Loop
For c in(
WITH sq
as
(
SELECT substr(x, instr(x,',',1,level)+1, instr(x,',',1,level+1) - instr(x,',',1,level) -1) token
FROM (select ','||l_str||',' x,l_str from (select l_string l_str from dual))
CONNECT BY level <= length(l_str)-length(replace(l_str,',',''))+1
)
select a.listingsid
from abelisting.book_del_check a, sq
where a.listingsid = To_Number(sq.token)
)
Loop
l_i := c.listingsid;
End loop;
End Loop;

runStats_pkg.rs_stop;

End;
/
rollback;

Run1 ran in 135 hsecs
Run2 ran in 116 hsecs
run 1 ran in 116.38% of the run2 time

Name Run1 Run2 Diff
STAT...consistent gets 60,011 60,012 1
STAT...parse time cpu 1 0 -1
STAT...parse time elapsed 1 0 -1
STAT...Elapsed Time 137 118 -19
STAT...recursive cpu usage 133 113 -20
LATCH.cache buffers chains 120,278 120,256 -22
...
STAT...session cursor cache hi 1,999 0 -1,999
STAT...parse count (total) 2,001 2 -1,999
STAT...recursive calls 4,003 2,003 -2,000
LATCH.row cache objects 12,014 12 -12,002
STAT...session uga memory 65,464 0 -65,464
STAT...session pga memory 65,536 0 -65,536
STAT...session pga memory max 196,608 65,536 -131,072
STAT...session uga memory max 196,500 61,996 -134,504

Run1 latches total versus Run2 -- difference and pct
Run1 Run2 Diff Pct
142,417 130,485 -11,932 109.14%

PL/SQL procedure successfully completed.

2) PIPELINE TABLE FUNCTION vs. SELECT

Run1 ran in 154 hsecs
Run2 ran in 118 hsecs
run 1 ran in 130.51% of the run2 time

STAT...recursive calls 4,049 2,003 -2,046
LATCH.row cache objects 6,254 12 -6,242
LATCH.library cache pin 12,138 4,012 -8,126
LATCH.shared pool 12,125 2,005 -10,120
LATCH.library cache 14,273 4,018 -10,255
STAT...session pga memory max 196,608 131,072 -65,536
STAT...session uga memory max 196,500 127,460 -69,040
STAT...session uga memory 130,928 0 -130,928
STAT...session pga memory 131,072 0 -131,072

Run1 latches total versus Run2 -- difference and pct
Run1 Run2 Diff Pct
165,633 130,485 -35,148 126.94%

PL/SQL procedure successfully completed.


One more fact -- it's calling SELECT in JDBC

Charlie Zhu, May 12, 2006 - 7:07 pm UTC

import java.sql.*;
import oracle.jdbc.OracleTypes;

//Reture SELECT rowset/ResultSet by Oracle PL/SQL store procedure

//<quote>
//I firmly believe the best java programs are those that have ZERO "selects/inserts/updates/deletes" in them.
//Hence, using ref cursors is the way to go. Lets you tune without bothering those java programmers.
//</quote>

//there are many advantages of doing so apart from having interfaces and access controlled -- something really convincing "those java programmers"?

//Followup:
//tell them "you will not have to write sql" :)

//sorry, but the reason is one of encapsulation, dependency tracking (who uses what), tuning....

public class JDBC_SP_list_df {

private static Connection conn1 = null, conn2 = null;

public static void main (String[] args) throws Exception {

initInet();
//initOracle();
Statement sql_trace1 = conn1.createStatement();
sql_trace1.execute( "begin dbms_monitor.session_trace_enable( WAITS=>TRUE, binds=>true); end;");

JDBC_SP_list_df app = new JDBC_SP_list_df();

//app.sub_SELECT();
int i=0;
while ( i < 200 )
{
app.sub_SELECT();
app.sub_StoreProcedure();
app.sub_table_function();
i = i+1;
}
conn1.close();
//conn2.close();
}

private static void initInet() throws Exception {

Class.forName("com.inet.ora.OraDriver");
conn1 = DriverManager.getConnection("jdbc:inetora:testdb:1521:dev", "scott", "tiger");
}

private static void initOracle() throws Exception {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn1 = DriverManager.getConnection("jdbc:oracle:thin:@testdb:1521:dev", "scott", "tiger");
conn1.setAutoCommit(false);
}

//abe usage
private void sub_SELECT() throws Exception {
String list_ids = "700000173,680000143,680000221,700000135,180000118,680000265,720000215,700000112,700000151,680000251,600000120,700000142,720000158,700000262,660000202,600000115,600000123,600000126,600000139,600000141,600000145,600000148,600000152,600000159,600000188,600000244,600000253,660000246,680000200,700000136";
String ls_sql = "WITH sq as " +
" (" +
" SELECT substr(x, instr(x,',',1,level)+1, instr(x,',',1,level+1) - instr(x,',',1,level) -1) token" +
" FROM (select ','||l_str||',' x,l_str from (select ? l_str from dual))" +
" CONNECT BY level <= length(l_str)-length(replace(l_str,',',''))+1" +
" )" +
" select a1.listingsid listingsid" +
" from abelisting.book_del_check a1, sq" +
" where a1.listingsid = To_Number(sq.token)";

PreparedStatement pstat = conn1.prepareStatement(ls_sql);
pstat.setString(1, list_ids);
ResultSet rset = pstat.executeQuery();
int li_listingsid=0;

while ( rset.next() ) {
li_listingsid = rset.getInt("listingsid");
}
//System.out.println(li_listingsid + ":select: ");
rset.close();
//pstat.close();
}

//better usage
private void sub_StoreProcedure() throws Exception {

String list_ids = "700000173,680000143,680000221,700000135,180000118,680000265,720000215,700000112,700000151,680000251,600000120,700000142,720000158,700000262,660000202,600000115,600000123,600000126,600000139,600000141,600000145,600000148,600000152,600000159,600000188,600000244,600000253,660000246,680000200,700000136";

String query = "begin ABELISTING.sp_sql_query.list_df(?,?); end;";

CallableStatement stmt = conn1.prepareCall(query);

// register the type of the out param - an Oracle specific type
stmt.registerOutParameter(1, OracleTypes.CURSOR);

// set the in param
stmt.setString(2, list_ids);

// execute and retrieve the result set
stmt.execute();
ResultSet rset = (ResultSet)stmt.getObject(1);
int listingsid=0;

while ( rset.next() ) {
listingsid = rset.getInt("listingsid");
}
//System.out.println(listingsid + ":^_^: ");

stmt.close();
}

//Pipeline rows from PL/SQL Table Function
private void sub_table_function() throws Exception {

String list_ids = "700000173,680000143,680000221,700000135,180000118,680000265,720000215,700000112,700000151,680000251,600000120,700000142,720000158,700000262,660000202,600000115,600000123,600000126,600000139,600000141,600000145,600000148,600000152,600000159,600000188,600000244,600000253,660000246,680000200,700000136";

PreparedStatement pstat = conn1.prepareStatement("select * from table(cast(abelisting.tab_f(?) as nt_type))");
pstat.setString(1, list_ids);
ResultSet rset = pstat.executeQuery();
int listingsid=0;

while ( rset.next() ) {
listingsid = rset.getInt(1);
//System.out.println(listingsid + ":-:");
}
rset.close();
//pstat.close();
}

}


I got answer on another QA

Charlie Zhu, May 17, 2006 - 7:20 pm UTC

</code> http://asktom.oracle.com/pls/ask/f?p=4950:8:12185729567511667929 <code>
PL/SQL API for Java/VB - Return Data Arrays or Result Sets", version 10.1.0.3

Thanks,
Charlie

Looking for a diffrent approach..

A reader, September 13, 2006 - 2:50 pm UTC

Tom,

This is an interesting thread. Our environment is an oltp one. I am looking to minimized hits by cutting down on ref cursor. Here is one of them. Is there any way I could re-write this one so I don't have to parse so many times? Please advice.



PROCEDURE process_main( main_REC_in IN mon_rec_TYPE
,student_in IN VARCHAR2
,school_in IN VARCHAR2 DEFAULT NULL
)
IS
TYPE ref_cur IS REF CURSOR;
code_cur ref_cur;
code_REC code_REC_TYPE := NULL;

BEGIN

OPEN code_cur FOR 'SELECT * FROM main_sessions WHERE stud_code = :1'
USING mon_REC_in.main_code;

FETCH code_cur INTO code_REC;
CLOSE code_cur;

IF code_rec.Mon = 'Y'
THEN
write_mon_PROC(mon_REC_in,student_in);
END IF;

IF main_REC.Email = 'Y'
THEN
send_message_proc(main_REC_in,student_in,message_in);
END IF;


EXCEPTION
WHEN OTHERS THEN
IF code_cur%ISOPEN
THEN
CLOSE code_cur;
END IF;
RAISE_APPLICATION_ERROR(-20008,SQLERRM);

END process_main;

Tom Kyte
September 13, 2006 - 3:22 pm UTC

why are you using a ref cursor at all here?

that should be a select into, nothing else.



ugh, raise_application_error, why not just RAISE??? why hide the actuall error code from clients that might want to know.

Simple code vs. scalability

Boris, November 02, 2006 - 1:10 pm UTC

Hello Tom,

I really enjoy reading threads on asktom.oracle.com. I guess, I'm not the only one. Unfortunately, I did not find answer for one particular question. We have application that gets dozen of millon hits per day to one particular procedure. For the sake of code simplicity we open ref cursor for one or another SELECT based on incoming parameters. We experience great wait time on "Latch: library cache".

Here is a mock-up version of our procedure:

CREATE OR REPLACE PROCEDURE count_people
(is_loc IN dept.loc%TYPE,
is_flag IN VARCHAR2,
on_qnt OUT NUMBER)
IS
TYPE ref_cur IS REF CURSOR;
vc_cur ref_cur;
vn_deptno dept.deptno%TYPE;
vn_n PLS_INTEGER;

BEGIN

IF is_flag = 'ALL' THEN
OPEN vc_cur FOR SELECT deptno FROM dept;
ELSE
OPEN vc_cur FOR SELECT deptno FROM dept WHERE loc = is_loc;
END IF;

on_qnt := 0;
LOOP
FETCH vc_cur INTO vn_deptno;

SELECT COUNT(*) INTO vn_n FROM emp WHERE deptno = vn_deptno;

on_qnt := on_qnt + vn_n;
END LOOP;

CLOSE vc_cur;

END count_people;


Please note that instead of SELECT COUNT(*)... we open another ref cursor based on another parameter to go through details (eg., pick people with salary over some limit / or pick people with same manager) and do some additional checks. In short, our procedure serves 4 different purposes.

If this could help: we use Oracle 10.2.0.2

The actual question is:
1) For sake of performance / scalability should we rewrite this code by using 4 other procedures based on incoming parameters and use explicit cursors only?
2) Is there any good way to substiture REF CURSOR with explicit cursor without:
a) IF flag='ALL' THEN OPEN vc_ref_all; ELSE OPEN vc_ref; END IF; ....and same for FETCH and CLOSE

Your help is greatly appreciated.

Thank you very much,
Boris


Tom Kyte
November 02, 2006 - 7:31 pm UTC

1) likely would help, plsql would cache the open cursors - ref cursors are not "cachable"

2) you can use the fact that nvl or expansion works well, assuming loc is NOT NULL, then:

   select deptno from dept where loc = nvl( is_loc, loc );

will result in a plan like this:

ops$tkyte%ORA10GR2> create table t ( x int primary key, y int );

Table created.

ops$tkyte%ORA10GR2> variable x number
ops$tkyte%ORA10GR2> set autotrace traceonly explain
ops$tkyte%ORA10GR2> select * from t where x=nvl(:x,x);

Execution Plan
----------------------------------------------------------
Plan hash value: 3837537679

------------------------------------------------------------------------
| Id  | Operation                     | Name         | Rows  | Bytes | C
------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |              |     5 |   130 |
|   1 |  CONCATENATION                |              |       |       |
|*  2 |   FILTER                      |              |       |       |
|*  3 |    TABLE ACCESS FULL          | T            |     4 |   104 |
|*  4 |   FILTER                      |              |       |       |
|   5 |    TABLE ACCESS BY INDEX ROWID| T            |     1 |    26 |
|*  6 |     INDEX UNIQUE SCAN         | SYS_C0015661 |     1 |       |
------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter(:X IS NULL)
   3 - filter("X" IS NOT NULL)
   4 - filter(:X IS NOT NULL)
   6 - access("X"=:X)


see, it is two plans in one - one for the full table scan, one for an index access - and only one or the other will be used depending on the bind variable value at runtime (see the additional predicates the optimizer added) 

With Oracle you can learn something new evey single day!

Boris, November 03, 2006 - 8:49 am UTC

Hello Tom,

Thanks a lot for prompt followup! This advise is very interesting.

Looking on execution plan you just provided I would never assume that only part of the plan would be executed. I would suggest "the obvious" - Oracle will always do full table scan (step #3) and execute all 6 steps. Therefore I would avoid this statement at all costs (just imagine that this table has few million rows and majority of calls have :x as NOT NULL).


Thanks a lot for the advise,
Boris

Tom Kyte
November 03, 2006 - 10:46 am UTC

funny, now that you mentioned it - Jonathan Lewis actually just wrote about this:

</code> http://jonathanlewis.wordpress.com/2006/10/30/non-execution-plans/ <code>



Amit, February 23, 2008 - 3:33 am UTC

Hi Tom,

Another question on explain plan. What will oracle do if I write somethig like :

select * from
TableA MAIN_TAB
WHERE (1=some_var or exists(select 1 from chunk_tab B where B.id=MAIN_TAB.id)

If some_var=1 will the chunk_tab will never be touched in runtime.

Also, Is there any better way of implementing this. I want to join an additional table only on certain condition. dynamic code is not an option as the code is already in an unmanagable form.

Many Thanks
Amit
Tom Kyte
February 24, 2008 - 11:38 am UTC

.... dynamic code is not an option as ...

hahahahahaha, more untrue words could not be said - ever. Those words should never be uttered, unless you want people to laugh out loud. Give me a break "not an option". Of course it is - EVERYTHING is. EVERY-THING.

Your comment "...the code is already in an unmanagable form. ..." would lead me to believe that the query itself must be slightly more complex - in which case commenting on how oracle would do that simple query (it would short circuit) isn't very useful - since in a larger complex query with merging, predicate pushing, rewrites - it would be impossible to say.



In your SIMPLE case, it might not (probably won't) touch the table

create table t1 as select * from all_users;
create table t2 as select * from all_users;

variable x number

exec :x := 2;
select /*+ x=2 */ *
  from t1
 where (:x = 1 or exists (select null from t2 where t2.user_id = t1.user_id))
/
exec :x := 1;
select /*+ x=1 */ *
  from t1
 where (:x = 1 or exists (select null from t2 where t2.user_id = t1.user_id))
/

@trace
exec :x := 2;
select /*+ x=2 */ *
  from t1
 where (:x = 1 or exists (select null from t2 where t2.user_id = t1.user_id))
/
exec :x := 1;
select /*+ x=1 */ *
  from t1
 where (:x = 1 or exists (select null from t2 where t2.user_id = t1.user_id))
/


select /*+ x=2 */ *
  from t1
 where (:x = 1 or exists (select null from t2 where t2.user_id = t1.user_id))

Rows     Row Source Operation
-------  ---------------------------------------------------
     58  FILTER  (cr=181 pr=0 pw=0 time=112 us)
     58   TABLE ACCESS FULL T1 (cr=7 pr=0 pw=0 time=900 us)
     58   TABLE ACCESS FULL T2 (cr=174 pr=0 pw=0 time=1890 us)

select /*+ x=1 */ *
  from t1
 where (:x = 1 or exists (select null from t2 where t2.user_id = t1.user_id))

Rows     Row Source Operation
-------  ---------------------------------------------------
     58  FILTER  (cr=7 pr=0 pw=0 time=1132 us)
     58   TABLE ACCESS FULL T1 (cr=7 pr=0 pw=0 time=1865 us)
      0   TABLE ACCESS FULL T2 (cr=0 pr=0 pw=0 time=0 us)


but in general, you cannot say that will be true, it can move things around and come up with some pretty tricky plans - SQL is non-procedural, it does whatever it feels like to get the answer.

Amit, February 25, 2008 - 2:26 pm UTC

Many Thanks Tom,

and the learning continues... "EVERYTHING is. EVERY-THING"

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