Home>Question Details



PRATTY -- Thanks for the question regarding "CURSOR", version ORACLE 8i 8.1.5

Submitted on 17-Jan-2001 17:34 Central time zone
Last updated 10-Dec-2009 8:25

You Asked

Hi Tom,
    I want to declare multiple cursors based on the values 
passed through a procedure ie, only where conditions of the
cursors will change. The body of the procedure is
same for all the cursors.
  Pls. suggegst a solution.

Thanking you,

regards,
Pratty.
 

and we said...

Updated May 4th, 2009

See this link for a superior approach:

http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1669972300346534908





Sounds like a good use of REF CURSORS to me.  Lets say you wanted to build a generic 
routine that would look at the inputs passed to it and build a WHERE clause for each 
NON-NULL parameter passed.  This would result in a large number of statically defined 
cursors so we'll use a ref cursor instead.  

I'll demonstrate below.  I'll write a write that will print out some EMP data.  This 
routine will take upto 3 inputs to constrain the result set.  I want to have upto 8 
different cursors possible here

o 1 with NO where clause (all inputs null)
o 3 with a single predicate
o 3 with "pairs" of predicate conditions
o 1 with all three predicate conditions

Additionally, since the use of BIND VARIABLES is one of the MOST important things in 
programming Oracle -- I'll want to make sure I use them as well.  This will be trickly 
since I don't know if I'll have 0, 1, 2, or 3 of them until runtime.  I'll use an 
application context to solve that problem.

Here is a sample implementation:


ops$tkyte@ORA8I.WORLD> create or replace context MY_CTX using MY_PROCEDURE
  2  /

Context created.

That created our application context and bound it to our yet to be created procedure 
"MY_PROCEDURE".  Note that only MY_PROCEDURE will be able to set values in this context.  
See 
http://asktom.oracle.com/~tkyte/article2/index.html
for more info on application contexts and their use

ops$tkyte@ORA8I.WORLD> create or replace 
     procedure p ( p_str in varchar2 )
  2  is
  3     l_str   long := p_str || chr(10);
  4     l_piece long;
  5     n       number;
  6  begin
  7      loop
  8          exit when l_str is null;
  9          n := instr( l_str, chr(10) );
 10          l_piece := substr( l_str, 1, n-1 );
 11          l_str   := substr( l_str, n+1 );
 12             loop
 13                exit when l_piece is null;
 14                dbms_output.put_line( substr( l_piece, 1, 
                                                        250 ) );
 15                l_piece := substr( l_piece, 251 );
 16          end loop;
 17     end loop;
 18  end;
 19  /

Procedure created.


P is just a little procedure I use to print things out nicer then dbms_output would.  I 
use it below to dump the dynamically generated query so we can see what was built for 
each execution.  It is not really relevant to the example, just part of the 
demonstration...

ops$tkyte@ORA8I.WORLD> create or replace
  2  procedure my_procedure( p_ename   in varchar2 default NULL,
  3                          p_hiredate  in date default NULL,
  4                          p_sal       in number default NULL)
  5  as
  6      type rc is REF CURSOR;
  7  
  8      l_cursor rc;
  9      l_query  varchar2(512)
 10               default 'select * from emp where 1 = 1 ';
 11  
 12      cursor l_template is select * from emp;
 13      l_rec  l_template%rowtype;
 14  

Here I use what I call a "TEMPLATE" cursor.  I like to use these with my ref cursors.  
I use them to define a record to fetch into.  Here, in this simple example, I could have 
skipped it and just defined l_rec as EMP%rowtype -- but I wanted to show how this would 
work if you didn't select * from a single table but had many columns from many tables.  
This just helps me create a nice record.  The template query ONLY has a SELECT and a 
FROM.  I never put a WHERE clause on it (even when joining) since I never use it any 
where.  I just use it to get the default datatypes, names and so on for a record 
definition right below it.

 15  begin
 16  
 17      if ( p_ename is NOT NULL ) then
 18          dbms_session.set_context( 'MY_CTX', 'ENAME',
 19                                    '%'||upper(p_ename)||'%');
 20          l_query := l_query ||
 21               ' and ename like
 22                 sys_context( ''MY_CTX'', ''ENAME'' ) ';
 23      end if;
 24  

for each input -- i'm inspecting it to see if it is non-null.  If it is, I add to the 
where clause and set the value in the context.  Notice how in the where clause -- I 
always use the SYS_CONTEXT function.  I NEVER put the literal value in to the query (that 
would be very bad and would trash the shared pool -- very extremely important to use bind 
variables).  Note also the use of '' to get a single ' into the where clause!

 25      if ( p_hiredate is NOT NULL ) then
 26          dbms_session.set_context( 'MY_CTX', 'HIREDATE',
 27                    to_char(p_hiredate,'yyyymmddhh24miss'));
 28          l_query := l_query ||
 29                ' and hiredate >
 30                    to_date(
 31                       sys_context( ''MY_CTX'',
 32                                    ''HIREDATE'' ),
 33                       ''yyyymmddhh24miss'') ';
 34      end if;

Note here how I am careful to preserve the date and time component -- if necessary!  
Always wrap the sys_context in a TO_DATE call if you are comparing to a DATE to avoid 
implicit conversions in the query at runtime!


 35  
 36      if ( p_sal is NOT NULL ) then
 37          dbms_session.set_context( 'MY_CTX', 'SAL', p_sal);
 38          l_query := l_query ||
 39                ' and sal >
 40                   to_number(
 41                       sys_context( ''MY_CTX'',
 42                                    ''SAL'' )
 43                             ) ';
 44      end if;
 45  

Same caveat for the NUMBER here.  Use TO_NUMBER to avoid IMPLICIT conversions

 46      p( l_query );
 47  
 48      open l_cursor for l_query;
 49  
 50      loop
 51          fetch l_cursor into l_rec;
 52          exit when l_cursor%notfound;
 53  
 54          dbms_output.put_line( l_rec.ename || ',' ||
 55                                l_rec.hiredate || ',' ||
 56                                l_rec.sal );
 57      end loop;
 58  
 59      close l_cursor;
 60  end;
 61  /

Procedure created.


 and that is it.  I now have a routine that will open 1 of 8 possible different 
cursors.  Here is a small test run just to see how it works

ops$tkyte@ORA8I.WORLD> 
ops$tkyte@ORA8I.WORLD> exec my_procedure
select * from emp where 1 = 1
SMITH,17-dec-1980 00:00:00,800
ALLEN,20-feb-1981 00:00:00,1600
WARD,22-feb-1981 00:00:00,1250
JONES,02-apr-1981 00:00:00,2975
MARTIN,28-sep-1981 00:00:00,1250
BLAKE,01-may-1981 00:00:00,2850
CLARK,09-jun-1981 00:00:00,2450
SCOTT,09-dec-1982 00:00:00,3000
KING,17-nov-1981 00:00:00,5000
TURNER,08-sep-1981 00:00:00,1500
ADAMS,12-jan-1983 00:00:00,1100
JAMES,03-dec-1981 00:00:00,950
FORD,03-dec-1981 00:00:00,3000
MILLER,23-jan-1982 00:00:00,1300
KING,,5

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> exec my_procedure( p_ename => 'a' )
select * from emp where 1 = 1  and ename like
sys_context( 'MY_CTX', 'ENAME' )
ALLEN,20-feb-1981 00:00:00,1600
WARD,22-feb-1981 00:00:00,1250
MARTIN,28-sep-1981 00:00:00,1250
BLAKE,01-may-1981 00:00:00,2850
CLARK,09-jun-1981 00:00:00,2450
ADAMS,12-jan-1983 00:00:00,1100
JAMES,03-dec-1981 00:00:00,950

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> exec my_procedure( p_sal => 1000 )
select * from emp where 1 = 1  and sal >
to_number(
sys_context( 'MY_CTX',
'SAL' )
)
ALLEN,20-feb-1981 00:00:00,1600
WARD,22-feb-1981 00:00:00,1250
JONES,02-apr-1981 00:00:00,2975
MARTIN,28-sep-1981 00:00:00,1250
BLAKE,01-may-1981 00:00:00,2850
CLARK,09-jun-1981 00:00:00,2450
SCOTT,09-dec-1982 00:00:00,3000
KING,17-nov-1981 00:00:00,5000
TURNER,08-sep-1981 00:00:00,1500
ADAMS,12-jan-1983 00:00:00,1100
FORD,03-dec-1981 00:00:00,3000
MILLER,23-jan-1982 00:00:00,1300

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> exec my_procedure( p_hiredate => add_months(sysdate,-240) )
select * from emp where 1 = 1  and hiredate >
to_date(
sys_context( 'MY_CTX',
'HIREDATE' ),
'yyyymmddhh24miss')
ALLEN,20-feb-1981 00:00:00,1600
WARD,22-feb-1981 00:00:00,1250
JONES,02-apr-1981 00:00:00,2975
MARTIN,28-sep-1981 00:00:00,1250
BLAKE,01-may-1981 00:00:00,2850
CLARK,09-jun-1981 00:00:00,2450
SCOTT,09-dec-1982 00:00:00,3000
KING,17-nov-1981 00:00:00,5000
TURNER,08-sep-1981 00:00:00,1500
ADAMS,12-jan-1983 00:00:00,1100
JAMES,03-dec-1981 00:00:00,950
FORD,03-dec-1981 00:00:00,3000
MILLER,23-jan-1982 00:00:00,1300

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> exec my_procedure( p_ename => 'a', p_sal => 2000 )
select * from emp where 1 = 1  and ename like
sys_context( 'MY_CTX', 'ENAME' )  and sal >
to_number(
sys_context( 'MY_CTX',
'SAL' )
)
BLAKE,01-may-1981 00:00:00,2850
CLARK,09-jun-1981 00:00:00,2450

PL/SQL procedure successfully completed.

ops$tkyte@ORA8I.WORLD> 

 

Reviews    
5 stars Lots of help, I was adrift on reference cursors   August 12, 2001 - 10am Central time zone
Reviewer: Harrison from Haymarket, VA USA
Thanks for the  help.  The example, as often happens with
your answers, taught me more than one thing (three or
four, in fact).  I started reading another response, followed a link to this and picked up several 
things that
will help (the page linked here was a '1=1' question, which
was also interesting). 


5 stars   August 20, 2001 - 5am Central time zone
Reviewer: Helena Markova from Bratislava, Slovakia


4 stars Most advanced   August 28, 2001 - 12pm Central time zone
Reviewer: andrew from CA, USA
Interesting, I never knew about sys_context() in this context :) It certainly is a common scenario 
and this approach overcomes having a separate query for each permutation. 


5 stars   August 30, 2001 - 5am Central time zone
Reviewer: Srinivas Atreya from Hyderabad, India
Simply Brilliant, never knew about the context stuff...--:) 


5 stars Is there a way to avoid giving the select multiple number of times   November 6, 2001 - 2pm Central time zone
Reviewer: Nag 
function test ( params )  is 

rc refcursor;

lv_rc rc;

begin


open rc for  

Select column list

from 

(select same set of columns
from same set of from same tables
where different conditions
union

select   same set of columns
from same set of from same tables
where different conditions
union

select same set of columns
from same set of from same tables
where different conditions
union
select
same set of columns
from same set of from same tables
where different conditions)

return rc;


end test;

Tom, as you see iam having to give the same select with different where clause again and again, 
what is the best way  to handle this.

The objective is to reduce the code 


Followup   November 6, 2001 - 3pm Central time zone:

It is called an OR

select same set of columns
from same set of tables
where (condition1) or (condition2) or (condition3)


It could be somewhat more efficient then a UNION as well as:

A union B union C 

is really

distinct( sort( a + b + c ) )

you'll skip a non-necessary sort/distinct step.

(please ask only relevant followups in this space.  I only answered this one cause its sort of 
related and I really like the example -- by touching it, it goes back onto the front page.  I'm not 
answering the other one about counting commas -- it has nothing to do with the original question at 
all)

 

5 stars Dynamic cursor   November 7, 2001 - 9am Central time zone
Reviewer: William from Ontario Canada
This is really helpful and handy too. But I would like to know the advantage of using context over 
building the predicate dynamically eg.

l_query := l_query || ' and empname like '''||p_ename||'''';

Thanks

 


Followup   November 7, 2001 - 6pm Central time zone:

Get my book -- its all about bind variables.

Bind variables are SO crucial, so important -- you MUST use them.

If you do something like:

  for i in 1 .. 1000
  loop
     execute immediate 'insert into t values ( ' || i || ')';
  end loop;

90%, yes 90%, of your time will be spent parsing.  If you on the other hand:

execute immediate 'insert into t values ( :x )' using I;

you will spend a significantly less amount of time parsing.  And if you just:

insert into t values ( i );

you'll spend almost NO time.


Parsing (soft and hard) latches the library cache.  A latch is a lock by any other name.  You are 
serializing operations by over parsing.  it will limit your scalability, decrease your performance 
and you will FAIL if you do not use bind variables!

 

5 stars A non dynamic alternative   November 8, 2001 - 10pm Central time zone
Reviewer: Jeroen from Singapore
Hi Tom,

I always appreciate your answers, and I really appreciate the techniques you are demonstrating.
But looking at this specific example, where the input parameters and table are hardcoded, I was 
wondering whether the construct underneath would not solve the problem as well.


create or replace
procedure my_procedure( p_ename     in varchar2 default NULL,
                        p_hiredate  in date default NULL,
                        p_sal       in number default NULL)
as

cursor c_emp
       (b_ename      varchar2
       ,b_hiredate   date
       ,b_sal        number
       )
is
select  *
from    emp    e
where   e.ename    = decode( b_ename
                            ,null,       e.ename
                            ,            b_ename
                            )
and     e.hiredate = decode( b_hiredate
                            ,null,       e.hiredate
                            ,            b_hiredate
                            )
and     e.sal     = decode( b_sal
                            ,null,       e.sal
                            ,            b_sal
                            );

begin
        for l_recin c_emp
           (b_ename      => p_ename
           ,b_hiredate   => p_hiredate
           ,b_sal        => p_sal
           )
        loop
          dbms_output.put_line( l_rec.ename || ',' ||
                                l_rec.hiredate || ',' ||
                                l_rec.sal );
       end loop;
end;
/ 


Followup   November 9, 2001 - 10am Central time zone:

Yes it would HOWEVER, in the real world it would not perform very well at all.

You see -- your approach would result in a query that could only be optimized by using a FULL TABLE 
SCAN in all cases -- indexes and other techniques would be ruled out!

The dynamic approach has the advantage that the optimizer has a good query to work with -- it'll 
get queries like:

select * from emp where ename = :bv;

select * from emp where hiredate = :bv1 and sal = :bv2;

It can optimize each one (and it'll only do that once and then stash the plan away for reuse 
later).  Each one can and probably will have a different plan.  One query can use an index on 
ENAME, the other on SAL or HIREDATE or SAL & HIREDATE. 

Your query obviates the use of indexes in 100% of the cases (even with an function based index -- 
there would be no chance).


I would definitely 100% go for dynamic SQL in this case.
 

5 stars How to reset complete context   November 9, 2001 - 8am Central time zone
Reviewer: Frank from NL
Great example. 
Is it possible to reset the entire context any other way then looping through list_context output?
 


Followup   November 9, 2001 - 10am Central time zone:

Well, you could iterate over the rows in SESSION_CONTEXT as well (it is a table with your current 
context values).

But yes, you'll be iterating over something to reset the entire context -- the API only does entry 
by entry. 

4 stars CURSOR   December 10, 2001 - 6pm Central time zone
Reviewer: Niloufar from Austin, TX
Tom,

Thank you for your complete and helpful answer.

In another article I had asked you for your help in writing a procedure that can handle a list of 
elements as the parameter to the procedure to be used in a "where clause".  Could that same 
technique (creating a type in SQL and using the in_list function, etc...) be used in "my_procedure" 
to create the "where clause"?

Here is what I am trying to do:

- create procedure
  my_procedure(clause1_par IN varchar2(30),  
               clause2_par IN varchar2(10))

  where clause1_par and clause2_par can be a list

- In the body of my_procedure I'd like to be able to create the sql statement:

  select * from table_name 
  where field1 in (clause1_par) 
  and field2 in (clause2_par)

Couple of questions:

1- Can I still use the in_list function in "my_procedure" to receive a list of elements as 
parameter?

2- If I have two parameters and both are lists, how can I figure out where the first parameter's 
elements end and the next parameter's list start?  Is this doable?

Thanks again for all your help.  Please let me know if my question is not clear.

  


5 stars Can this be applied to Execute Immediate?   March 14, 2002 - 5am Central time zone
Reviewer: Tim Glasgow from Belfast
I expect that this can be applied to execute immediate also and it does not necessarily only apply 
to ref_cursors. 


Followup   March 14, 2002 - 8am Central time zone:

Yes, you can use this as a method to "bind" any SQL insert/update/delete/select (and in 9i MERGE) 
command. 

5 stars Problems with dbms_session   March 14, 2002 - 9am Central time zone
Reviewer: Tim Glasgow from Belfast
I am getting the following error when I use dbms_session.set_context. The user has been granted 
execute privileges from sys and still this doesn't make any difference. A contributor from 
RevealNet said:

"...they can run DBMS_SESSION OK (the error message indicates that they are in it), but they're not 
allowed to do whatever it is that they are asking to do via DBMS_SESSION"

ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 58
ORA-06512: at "PROP.AGENTPROPERTYREPORTS", line 269
ORA-06512: at line 2
 


Followup   March 14, 2002 - 10am Central time zone:

The contributor from revealnet is dead on.

A context is bound to a plsql procedure/function or package.  You cannot call dbms_session 
directly.  It works like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace context my_ctx USING my_procedure
  2  /

Context created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace procedure my_procedure( p_name in varchar2, 
p_value in varchar2 )
  2  as
  3  begin
  4          dbms_session.set_context( 'my_ctx', p_name, p_value );
  5  end;
  6  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_session.set_context( 'my_ctx', 'hello', 'world' );
BEGIN dbms_session.set_context( 'my_ctx', 'hello', 'world' ); END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 62
ORA-06512: at line 1


ops$tkyte@ORA817DEV.US.ORACLE.COM> select sys_context( 'my_ctx', 'hello' ) sc from dual;

SC
--------------------


ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec my_procedure( 'hello', 'world' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sys_context( 'my_ctx', 'hello' ) sc from dual;

SC
--------------------
world



So, I would have to assume that when you created the context, you did not specify USING 
PROP.AGENTPROPERTYREPORTS -- hence that piece of code cannot set the context.

 

5 stars At last   March 14, 2002 - 10am Central time zone
Reviewer: Tim Glasgow from Belfast
Tom, thanks very much that was the problem. Well done!

..obviously creating the context in a sqlplus session under the same user doesn't work as I had 
done this before I attempted to execute the package.procedure
(i.e. SQL> create or replace context MY_CTX using prop.AgentPropertyReports;

Operation 177 succeeded.)

I also had the create context in my procedure a built it using exec immediate, but as you spotted 
didn't have the user prefix before the package.

Thanks very much for your help.
Tim



 


Followup   March 14, 2002 - 10am Central time zone:

Suggestion -- upgrade your SQLPlus version if you want to use new features.  Funky things can 
happen if you use an older sqlplus against a newer db -- for example, sqlplus isn't recognizing 
"CREATE OR REPLACE CONTEXT" as a SQL command properly -- that tells me you are using the v8.0 or 
before sqlplus against 8i.  Some commands might not work AT ALL. 

5 stars Followup   March 15, 2002 - 9am Central time zone
Reviewer: Tim Glasgow from Belfast
Hi Tom,
I found application context to be absolutely superb, but I've run into a problem with using 'IN' in 
my dynamic query.

----------------------------------------------------
dbms_session.set_context( 'MY_CTX', 'estateagentid', pc_estate_agent_id_in);

 sql_stmt := sql_stmt || ' and ea.estateagentid IN sys_context( ''MY_CTX'', ''estateagentid'')';

----------------------------------------------------

If I do pc_estate_agent_id_in => '139' -- its OK
If I do pc_estate_agent_id_in => '139,44' -- error:

SQL>  @unit_tests/properties/test_all_nulls
Input truncated to 1 characters
139,44
hi1
BEGIN
*
ERROR at line 1:
ORA-01722: invalid number
ORA-06512: at "PROP.AGENTPROPERTYREPORTS", line 343
ORA-06512: at line 2

I know it's something to do with the quotes or the comma and have tried every combination of adding 
quotes to the context attribute and the parameter.

 


Followup   March 15, 2002 - 2pm Central time zone:

read
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:139812348065
... 

4 stars   March 28, 2002 - 6am Central time zone
Reviewer: Tanel Hiir from Estonia
Tom,

Oracle8i Application Developer's Guide - Fundamentals Chapter 11:

<blockquote>
Fine-grained access control with application context was designed to handle static applications; 
that is, those for which the security attributes contained within application contexts are static 
within the user sessions. In other words, the feature is best used for applications in which the 
user logs in, his application context is set for the session, and the context does not change until 
the user logs off. This design principle enables application context to be highly scalable, because 
many users can share the same fully-parsed, optimized statement.
</blockquote>

Does it apply only to DBMS_RLS?
Or does it influence dynamic SQL with sys_contex use also?
 


Followup   March 28, 2002 - 5pm Central time zone:

They are only taking about DBMS_RLS there. 

5 stars Excellent technique -- now I have a ton of tuning work to do!!!   April 3, 2002 - 5pm Central time zone
Reviewer: Keith Miller from Boston, MA
This works great.

However, I notice that the number of parse_calls for these are equal the number of executions for 
each distinct query in v$sqlarea.  There are no invalidations for any of the distinct queries.

I have my dynamic call wrapped in dbms_utility.get_time statements for timing.  The response time I 
get looks as if the statements are not being reparsed.  The initial time the query is run the 
response is 7 hsecs, subsequent excutions respond in 2 hsecs.

Is there anything I can do to reduce the parse_calls for these dynamic queries? 


Followup   April 3, 2002 - 6pm Central time zone:

REF CURSORS must be parsed each time -- by definition.  They cannot be cached (they are potentially 
DIFFERENT with each execute!)

search this site for session_cached_cursors, that'll help you.

They are being SOFT PARSED, much better then a hard parse -- much worse then no parse or a 
session_cached_cursor hit.
 

5 stars More on cursors...   April 4, 2002 - 10am Central time zone
Reviewer: Andre Whittick Nasser from Brazil
I have 2 questions on cursors. Maybe you are able to clarify them, both in 9i:

1) CURSOR_SHARING = SIMILAR -> The Oracle on-line books and the Oracle University books (the only 
ones on 9i I really trust) are rather reticent on this feature. They only say things like "the CBO 
examines the statement for safe (?) literals" or "the CBO uses statistics on available structures, 
like histograms". I know bind variable values are assigned only after execution plan generation, so 
the CBO is not aware of these values. Also, I know how histograms are used to find out about the 
selectivity of column values. Can you please explain briefly how "similar" statements are treated ?

2) Could you elaborate a bit on "cached" execution plans ?    

Thanks! 


Followup   April 4, 2002 - 1pm Central time zone:

SIMILAR
Causes statements that may differ in some literals, but are otherwise identical,
to share a cursor, unless the literals affect either the meaning of the statement or the degree to 
which the plan is optimized.


What this means is that we will take a query such as:


select * from emp where sal > 100


and turn that into "select * from emp where sal > :x".  We will decide IF that bind variable is 
"safe" -- that is, if changing the literal value could affect the plan.  In many cases -- it could 
(eg: full tablescan for sal > 100, index range scan for sal > 10000).  Hence, similar queries will 
not share the plan.

On the other hand, a query like:

select * from emp where empno = 5555;

that would be deemed "safe", that is, regardless of the literal value used, the query plan will be 
the same (index unique scan on the empno index).  Hence the queries 

select * from emp where empno = 1
select * from emp where empno = 2

will be "similar" and will use shared sql.  The determination of whether a bind variable would be 
"safe" or not is made before the optimizer kicks in.

Also, in 9i, the optimizer will "peek" at binds before generating the optimized plan, this is new.  
So -- the optimizer does have access to the binds *the first time it opimizes a statement* (but 
obviously not so on subsequent parses since they just reuse the optimized plan that already exists)


a cached execution plan is what is stored in the shared pool.  it is the "shared sql". 

5 stars Cached execution plans and V$SQL_PLAN   April 4, 2002 - 2pm Central time zone
Reviewer: Andre Whittick Nasser from Brazil
Thanks for the explanation on CURSOR_SHARING=SIMILAR. The quirk I was missing was the "peek" at 
binds by the CBO in 9i. Interesting.

Sorry for asking you about cached execution plans. It's a misnomer, in fact. It's being described 
as being some new feature of 9i. Of course it's not -- in essence at least. It's the name Oracle is 
giving to the possibility of viewing the ACTUAL, "stored" execution plan, as opposed to that 
generated by EXPLAIN PLAN. EXPLAIN PLAN is an ESTIMATE of what will be executed. On the other hand, 
the new V$_SQL_PLAN shows the real, previously executed plan.

From the Performance Guide:

"
V$SQL_PLAN

This view provides a way of examining the execution plan for cursors that were recently executed. 

The information in this view is very similar to the output of an EXPLAIN PLAN statement. However, 
EXPLAIN PLAN shows a theoretical plan that can be used if this statement were to be executed, 
whereas V$SQL_PLAN contains the actual plan used. The execution plan obtained by the EXPLAIN PLAN 
statement can be different from the execution plan used to execute the cursor, because the cursor 
might have been compiled with different values of session parameters (for example, HASH_AREA_SIZE). 

" 
   
Thanks ! 


Followup   April 4, 2002 - 3pm Central time zone:

Ahh -- but a$sql_plan is just showing you the cached plan from the shared pool -- it's not really 
new (the cached plan), the ability to "see it" is all that is new here. 

5 stars That's what I meant...   April 4, 2002 - 4pm Central time zone
Reviewer: Andre Whittick Nasser from Brazil


5 stars Cached execution plans...   April 4, 2002 - 5pm Central time zone
Reviewer: Andre Whittick Nasser from Brazil
That's what I meant ! I have an Oracle document titled "Cached Execution Plans". At first, I didn't 
know what was new about it, then I asked you. Later I saw that what was new wasn't the cached stuff 
-- of course. The view was new. 

You know what's interesting ? 

V$SQL has a new column, PLAN_HASH_VALUE. Like with HASH_VALUE, which you can use to compare 2 sql 
texts, you can use PLAN_HASH_VALUE to know if the plan has changed for a specific cursor. In fact, 
you can do both to compare cursors plans:

Either...

- compare V$SQL_PLAN rows or

- compare V$SQL.PLAN_HASH_VALUE column values    

If you do:

column id          format 999 newline
column operation   format a20
column operation   format a20
column options     format a15
column object_name format a22 trunc
column optimizer   format a3  trunc

SELECT id
     , lpad (' ', depth) || operation operation
     , options
     , object_name
     , optimizer
     , cost
  FROM V$SQL_PLAN
 WHERE hash_value = 2446703096
   AND address    = '80E3A1D8'
 START WITH id = 0
 CONNECT BY 
       (     prior id           = parent_id
         AND prior hash_value   = hash_value
         AND prior child_number = child_number
       )
 ORDER SIBLINGS BY id, position;

  ID OPERATION            OPTIONS         OBJECT_NAME            OPT       COST
---- -------------------- --------------- ---------------------- --- ----------
   0 SELECT STATEMENT                                            CHO
   1  SORT                ORDER BY                                            6
   2   NESTED LOOPS                                                           4
   3    HASH JOIN                                                             3
   8    TABLE ACCESS      BY INDEX ROWID  ORDERS                              1
   9     INDEX            RANGE SCAN      ORDERS_PK                           2
   4     TABLE ACCESS     BY INDEX ROWID  ORDERS_LINE_ITEM                    1
   5      INDEX           RANGE SCAN      ORDERS_LINE_ITEM_PK                 2
   6     TABLE ACCESS     BY INDEX ROWID  ORDERS_STATUS                       1
   7      INDEX           RANGE SCAN      ORDERS_STATUS_PK                    2

10 rows selected.

Then you have the plan for that specific child cursor. Here, we have only one child cursor. 

Thanks ! 


4 stars Another approach using package variables   April 5, 2002 - 9am Central time zone
Reviewer: James Turner from NY, NY USA
Seems like you could achieve the same results using package variables.  What advantage is there to 
the application context approach over this alternative? 


Followup   April 5, 2002 - 9am Central time zone:

You cannot do that in a dynamically opened ref cursor.

open cursor for 'select * from t where x = pkg.variable';

won't work.  you could code:

open cursor for 'select * from t where x = pkg.function_returning_variable';

but that can be highly "non-performant".  sys_context is treated just like a bind variable -- using 
sys_context the code:

open cursor for 'select * from t where x = sys_context( ''n'', ''v'' )';

is treated as:

open cursor for 'select * from t where x = :bind_variable';

 

5 stars Dyanmic Attribute?   July 25, 2002 - 10am Central time zone
Reviewer: Robert Boyle 
Hi Tom,
Thanks for this most useful piece of coding!  I was wondering is there anyway of creating the 
attribute in dbms_session.set_context dynamically?  Reason I am asking is that we are getting a 
string containing various words to search a field for and I am building the sql using this 
procedure (along with checkin a dozen other parameters) and using multiple likes.

eg:  l_query := l_query || ' and description like sys_context(''MY_CTX'', ''DESCRIPTION'')';

can DESCRIPTION be made dynamically to be DESCRIPTION1, DESCRIPTION2 etc...or am I going about this 
the wrong way?  Should I use intermedia, or should I limit the number of key words available for a 
search thus I would know the limit required in the query?

Cheers
Robert 


Followup   July 25, 2002 - 10pm Central time zone:

Not sure what you are asking here? 

5 stars Dynamic Attribute   July 26, 2002 - 5am Central time zone
Reviewer: Robert 
Sorry, never was the best at explaining myself!

First of all let me explain the parameter I receive.  It will contain several keywords to search a 
description field for, seperated by a space.  

'myword yourword ourword anotherword'

I use the code you supplied to check whether it is null or not and whether I need to append it to 
my dynamic query to run against the table.  (there are other parameters too that are checked to see 
if they are null).

if ( p_keywords is NOT NULL ) then
 
This is where I am unsure of the best way to do things.  I need to get these keywords and do a like 
comparison on each of them against the description field.  So I create a loop and get the keyword 
into a local variable (l_keyword) and then set the context:

dbms_session.set_context( 'MY_CTX', 'DESCRIPTION', '%'||l_keyword||'%');

My problem stems here.  I will not know how many keywords there might be and my understanding is 
that if I assign l_keyword to the 'DESCRIPTION' attribute, I will over write each l_keyword until I 
get to the final one in my loop.  So that my l_query would have:
' and description like sys_context(''MY_CTX'', ''DESCRIPTION'') 

several times in the query and therefore only check a like for the same word n amount of times.

Therefore, I was wondering if 

a) we should limit the amount of keywords (this I do at the moment) so that I can loop and assign 
to attribute 'DESCRIPTION1', 'DESCRIPTION2' etc

b) we can create the attribute 'DESCRIPTION' || l_keyword_count on the fly (ie have 'DESCRIPTON1', 
'DESCRIPTION2' created dynamically in the code)

or 

c)should we abandon using this and go for Intermedia, the desciption field is a varchar2(2000) and 
so not that large.

Thanks for your help.
Robert 


Followup   July 26, 2002 - 7am Central time zone:

Ok, you'll have to use the approach you are currently using -- description1, description2 and so on 
-- making a unique name for each.

You can do this dynamically on the fly -- the variable names in the context are just strings.  You 
make them up as you go along.


Intermedia won't do "%word%" searches -- the leading % is a bad thing, hard problem to solve.  
cannot be solved with an index. 

5 stars   July 26, 2002 - 11am Central time zone
Reviewer: Robert 
Hi Tom,

I tried creating the attribute dynamically...but maybe I am going about it wrong?  Below is my code 
that loops and appends to the query:

WHILE l_description IS NOT NULL LOOP

  l_delimiter := INSTR(l_description, ' ');
  l_length    := LENGTH(l_description);
  IF l_delimiter = 0 THEN
     l_delimiter := l_length;
  ELSE
     NULL;
  END IF;
              
  l_desc_part := SUBSTR(l_description, 1, l_delimiter);
  l_desc_count := l_desc_count + 1;
  l_desc_context := 'DESCRIPTION' || l_desc_count;
              
  dbms_session.set_context('MY_CTX', l_desc_context, '%'||l_desc_part||'%');
  l_query := l_query || ' and description like sys_context(''BUGTRACK_CTX'', l_desc_context)';
END LOOP;

When I run this I get :

ERROR at line 1:
ORA-00904: invalid column name
ORA-06512: at "ROBERT.MY_PKG", line 216
ORA-06512: at line 43

Any ideas?

Thanks
Robert 


Followup   July 28, 2002 - 3pm Central time zone:

when you build this query -- it'll be:

  select .... and description like 
     sys_context('BUGTRACK_CTX', l_desc_context)


well, l_desc_context isn't known in the query! 

what you need is:

l_query := l_query || 
        ' and description like sys_context(''BUGTRACK_CTX'', ''' ||
           l_desc_context || ''' )';


so your where clause reads:

  select .... and description like 
     sys_context('BUGTRACK_CTX','DESCRIPTION1')

you need to concatenate in the VALUE of l_desc_context, not the word l_desc_context!
 

5 stars Dynamic Attribute   July 29, 2002 - 4am Central time zone
Reviewer: Robert 
Brilliant, thanks Tom.  It all seems so obvious now!   


5 stars question on the followup for -- a non dynamic alternative   August 12, 2002 - 12pm Central time zone
Reviewer: john 
Hi Tom,

i just would like to know that why the index is not used in the following query. because we have 
not used any function on on ename or sal or hiredate(which are on left hand side)

select  *
from    emp    e
where   e.ename    = decode( b_ename
                            ,null,       e.ename
                            ,            b_ename
                            )
and     e.hiredate = decode( b_hiredate
                            ,null,       e.hiredate
                            ,            b_hiredate
                            )
and     e.sal     = decode( b_sal
                            ,null,       e.sal
                            ,            b_sal
                            );

i have used a query that get all the records for which either start date falls in 2002 or end date 
falls in like below:

select * from my_table where strt_date = to_date(to_char(strt_dt,'DDMM')||'2002','DDMMYYYY')
OR end_date = to_date(to_char(strt_dt,'DDMM')||'2002','DDMMYYYY')

now my question is will the above query not use the index on strt_date and end_date.

if no, can this below query use the index:

select * from my_table where strt_date >= to_date('01012002','DDMMYYYY')
OR end_date <= to_date('31122002','DDMMYYYY') 
 


Followup   August 12, 2002 - 1pm Central time zone:

you most certainly did use a function on ename, sal and hiredate!!!!


where e.ename = function_of( ...., e.name, .... )
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

right there!

How could one use an index on:

  where x = decode( :bv, null, x, :bv );


the decode can return a different value for each and every row!!!!  if :bv is NULL that decode is 
NOT a constant and you can only use an index when we have a constant!

You want to use a technique like:

(well, I was going to cut and paste the url for this question here but then I noticed you already 
found the answer!!!!)

use the technique above.


you want to query like this:

select * 
  from t
 where strt_date between to_date('01-jan-2002') 
                     and to_date('01-jan-2003')-1/24/60/60
    or end_date between to_date('01-jan-2002') 
                     and to_date('01-jan-2003')-1/24/60/60


your approach *applies a function* to the column -- 
to_date(to_char(strt_dt,'DDMM')||'2002','DDMMYYYY') is potentially different for each and every row 
-- no way to use an index with that (well, you could use a function based index but that would be 
the wrong solution here, just use between like I did)
 

4 stars Another way?   August 13, 2002 - 7am Central time zone
Reviewer: Darko Egersdorfer from South Africa
Can't you achieve the same (without using sys_context), creating dummy WHERE condition if passed 
parameter has null value. So, you will always use the same number of bind variables in USING 
clause. 
      if ( p_ename is NOT NULL ) then
           l_query := l_query ||
                ' and ename like  ''%''||:p_ename||''%''';
       else
           l_query := l_query ||
                ' and 1 = DECODE(:p_ename,NULL,1,1)';
       end if;

 ...

   open l_cursor for l_query USING  p_ename,p_hiredate,p_sal;

(You can always TO_CHAR and TO_DATE date parameters using the same format)
 


Followup   August 13, 2002 - 5pm Central time zone:

sure, but if you've gone to all of that work -- you might just as well not include the predicate 
anyway.  Why add an extra decode if you need not?  Also, sys context lets you do it when you have 
no clue how many binds you might have.

Yes, you can do it like this.
I would myself use sys_context.
 

5 stars create context privillege   August 13, 2002 - 11am Central time zone
Reviewer: john 
tom,
when i try creating the context, i get an error 

create or replace CONTEXT my_ctx using my_procedure

ORA-01031: insufficient privileges

is that enough if i have create or replace context privellege or do i also need to have execute 
privilege on dbms_session.set_context as well.

please suggest 


Followup   August 13, 2002 - 5pm Central time zone:

ops$tkyte@ORA817DEV.US.ORACLE.COM> grant create any context to scott;

Grant succeeded.

is what you are missing. 

4 stars contexts vs. predicates using short-circuited OR   August 13, 2002 - 2pm Central time zone
Reviewer: Bill Coulam from CO, USA
I've never seen that context stuff used before. Very cool. I can't wait to dig into it more.

Up until this point, whenever I've had a query that made use of bind variables that were possibly 
empty, I've used the following approach, depending on the short-circuiting nature of "OR" in 
PL/SQL:

   CURSOR c_mytab (<parmlist>) IS
   SELECT m.* FROM mytab m
   WHERE  (ln_id IS NULL
           OR
           m.myid = ln_id)
   AND    (ls_msg IS NULL
           OR
           m.myval = ls_msg)
   ...
   ;
BEGIN
   OPEN CURSOR c_mytab(<parmlist>);
   ...etc.

It looks and feels a little "lumpy", but it seems like less code and hassle than the context stuff.

So far, the only drawback we've experienced is aesthetic. When adapted to Dynamic SQL, there are 
two slots for each variable/parameter, e.g.

EXECUTE IMMEDIATE 'SELECT ...
' USING in_id, in_id, is_msg, is_msg;

That's not so hot.

Can you think of anything wrong with using the OR in this way?

bill coulam 


Followup   August 20, 2002 - 4pm Central time zone:

the optimized query plan from this OR stuff where you say

where ( bind_variable IS NULL or table.column = bind_variable )

will be really bad compared to the approach above which results in a finely tuned query.

The optimizer looks at that OR and says -- hmm, maybe we will do the compare, maybe no -- no index 
for you (it would not be able to use an index on table.column effectively whereas my approach will) 

5 stars short circuited or   September 3, 2002 - 1pm Central time zone
Reviewer: A reader 
but tom,

when i used the OR approach, i see optimizer using the index when the bind variable is null. i gave 
a sample query like this

select * from emp where (null is null or empno = 1)

then i saw full table scan on emp

now when i modify the query:

select * from emp where (1 is null or empno = 1)

then i saw the index is being used on that..

do you still suggest that this or approach result bad optimizer plan. i don't understand why? 
 


Followup   September 3, 2002 - 2pm Central time zone:

You did NOT use any bind variables.  That is why.  consider:

ops$tkyte@ORA920.US.ORACLE.COM> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA920.US.ORACLE.COM> alter table emp add constraint emp_pk primary key(empno);

Table altered.

ops$tkyte@ORA920.US.ORACLE.COM> 
ops$tkyte@ORA920.US.ORACLE.COM> variable x number
ops$tkyte@ORA920.US.ORACLE.COM> 
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA920.US.ORACLE.COM> 
ops$tkyte@ORA920.US.ORACLE.COM> exec :x := null

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> select * from emp where ( :x is null or empno = :x );

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'EMP'



ops$tkyte@ORA920.US.ORACLE.COM> exec :x := 1

PL/SQL procedure successfully completed.

ops$tkyte@ORA920.US.ORACLE.COM> select * from emp where ( :x is null or empno = :x );

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'EMP'



ops$tkyte@ORA920.US.ORACLE.COM> 
ops$tkyte@ORA920.US.ORACLE.COM> select * from emp where (null is null or empno = 1);

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'EMP'



ops$tkyte@ORA920.US.ORACLE.COM> select * from emp where (1 is null or empno = 1);

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP'
   2    1     INDEX (UNIQUE SCAN) OF 'EMP_PK' (UNIQUE)



ops$tkyte@ORA920.US.ORACLE.COM> 


Bind variable = FULL SCAN.
Hard coded literals = new plan for every query = Maybe full scan, maybe not. 

5 stars short-circuited or   September 3, 2002 - 3pm Central time zone
Reviewer: A reader 
thanks so much tom 


4 stars Privilege problem when calling DBMS_SESSION   September 4, 2002 - 7am Central time zone
Reviewer: Mike Stacey-Gee from Llandeilo - UK
An elegant solution but I'm receiving an error whenever I access DBMS_SESSION despite running as a 
user with DBA privilege (8i v8.1.7)

variable c REFCURSOR;
exec MSCR_PAYABLEEMPTIES( :c, 'O');

ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 58
ORA-06512: at "MIS.MSCR_PAYABLEEMPTIES", line 16
ORA-06512: at line 1

Attached is a subset of the procedure that triggers the error:

CREATE OR REPLACE  PACKAGE "MIS"."P_REF_CURSOR"   is
   type ret_ref_cursor is ref cursor;
end p_ref_cursor;

CREATE OR REPLACE CONTEXT MSCR_CTX_PAYABLEEMPTIES USING MSCR_PAYABLEEMPTIES;

CREATE OR REPLACE  PROCEDURE "MIS"."MSCR_PAYABLEEMPTIES"                (
Result_Cursor IN OUT p_ref_cursor.ret_ref_cursor,
Direction IN VARCHAR2 DEFAULT NULL,
EndDate IN VARCHAR2 DEFAULT NULL
)
AS
  l_query varchar2(9999);
BEGIN

  l_query := 'SELECT DIRECTION,TRADE,VESSELCODE,VOYAGENUMBER,LOADPORTCODE,' ||
       'DISCHPORTCODE,EQUIPMENTTYPE,NOOFCONTAINERS,TEUS,CONTRTEUS ' ||
       'FROM MISFLATMVOYAGE WHERE COMMODITYCODE = ''RE00''';

  -- Check whether a direction parameter has been passed
  -- N.B. If passed from the web reports module it will contain '' instead of NULL to
  --      indicate a blank parameter
  
  IF ( Direction IS NOT NULL ) AND ( Direction <> '''' ) THEN
    dbms_session.set_context('MSCR_CTX_PAYABLEEMPTIES', 'DIRECTION', Upper(Direction));
    l_query := l_query ||
               ' AND DIRECTION = sys_context(''MSCR_CTX_PAYABLEEMPTIES'', ''DIRECTION'' )';
  END IF;
  
  open Result_Cursor FOR l_query;

END MSCR_PAYABLEEMPTIES; 


Followup   September 4, 2002 - 8am Central time zone:

Well, it doesn't have anything to do with DBA/not DBA.

You can execute dbms_session -- the 1031 is coming from the fact that "MIS"."MSCR_PAYABLEEMPTIES" 
is not able to set the context MSCR_CTX_PAYABLEEMPTIES.

I notice your procedure and package have "MIS".  on them.  But the context does not.  If you ran 
this script as a user OTHER THEN "MIS", then the context can only be set by that 
SCHEMA.MSCR_PAYABLEEMPTIES -- not by MIS.MSCR_PAYABLEEMPTIES;

Perhaps that is it.  Do a select * from dba_context and make sure the SCHEMA is in fact MIS.

Here is a small test case showing the "issue"

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop context MSCR_CTX_PAYABLEEMPTIES;

Context dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop user a cascade;

User dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> create user a identified by a;

User created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> grant create session to a
  2  /

Grant succeeded.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE OR REPLACE CONTEXT MSCR_CTX_PAYABLEEMPTIES USING 
MSCR_PAYABLEEMPTIES;

Context created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE OR REPLACE  PROCEDURE "A"."MSCR_PAYABLEEMPTIES"
  2  AS
  3  BEGIN
  4      dbms_session.set_context('MSCR_CTX_PAYABLEEMPTIES', 'DIRECTION', 'x' );
  5  END MSCR_PAYABLEEMPTIES;
  6  /

Procedure created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect a/a
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
a@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
a@ORA817DEV.US.ORACLE.COM> set termout on
a@ORA817DEV.US.ORACLE.COM> 
a@ORA817DEV.US.ORACLE.COM> 
a@ORA817DEV.US.ORACLE.COM> exec MSCR_PAYABLEEMPTIES
BEGIN MSCR_PAYABLEEMPTIES; END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 62
ORA-06512: at "A.MSCR_PAYABLEEMPTIES", line 4
ORA-06512: at line 1


a@ORA817DEV.US.ORACLE.COM> 
a@ORA817DEV.US.ORACLE.COM> @connect /
a@ORA817DEV.US.ORACLE.COM> set termout off
ops$tkyte@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout on
ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE OR REPLACE CONTEXT MSCR_CTX_PAYABLEEMPTIES USING 
A.MSCR_PAYABLEEMPTIES;

Context created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> @connect a/a
ops$tkyte@ORA817DEV.US.ORACLE.COM> set termout off
a@ORA817DEV.US.ORACLE.COM> REM GET afiedt.buf NOLIST
a@ORA817DEV.US.ORACLE.COM> set termout on
a@ORA817DEV.US.ORACLE.COM> 
a@ORA817DEV.US.ORACLE.COM> 
a@ORA817DEV.US.ORACLE.COM> exec MSCR_PAYABLEEMPTIES

PL/SQL procedure successfully completed.

a@ORA817DEV.US.ORACLE.COM> 


See, until the context is bound to the "A" schema - the procedure owned by "A" cannot set it.
 

4 stars using the same context field value in two queries   September 9, 2002 - 6pm Central time zone
Reviewer: A reader 
can we use the same context field in different queries

like 

dbms_session.set_context('my_ctx','ename','john');
execute immediate
'select count(1) from emp where ename = sys_context('my_ctx','ename')';

open p_ref_cur for 'select * from emp where ename = sys_context('my_ctx','ename')';

is the above possible? i am getting an error ' invalid column name 


Followup   September 9, 2002 - 8pm Central time zone:

sure you can but you have to write the code right!


to get a single quote in a string you have to use two quotes.

'select count(1) from emp where ename = sys_context(''my_ctx'',''ename'')'; 

4 stars assigning context to a procedure   September 30, 2002 - 2pm Central time zone
Reviewer: ramu 
can i assign the same context to more than one procedure or it should always be used in assigning 
to a single proc?

can i do like :

create or replace context MY_CTX using MY_PROCEDURE1;

create or replace context MY_CTX using MY_PROCEDURE2; 


Followup   October 1, 2002 - 9am Central time zone:

you can do that but only the last one COUNTS.

Only one procedure or PACKAGE (<<<=== thats how to allow more then one proceudre access) at a time. 

5 stars ref cursor   December 24, 2002 - 3pm Central time zone
Reviewer: Mo 
Tom:

In the example above you build the WHERE clause dynamically.  How about if I want to build the 
columns in the SQL itself.  The situation I have is:

I have a table for organization and a  table for warehouse.
One org may have one or more warehouse.  I want to run a inventory report to do total count for 
items for org and break it also by warehouse.  I have a function that cimputes total quantity. But 
now after figuring out which org user belongs to, I want to build my SQL statment based on how many 
warehouses he has. let us say he has one warehouse only then it will be:

FOR x in (SELECT item,compute_qty(org,item) qty1 from stock_items)
LOOP
htp.p(item);
htp.p(qty1);

If there are two warehouses then I want:
FOR x in (SELECT item,compute_qty(org,item) qty1, compute_qty(warehouse1,item) qty2,
compute_qty(warehouse2,item) qty3
 from stock_items)
LOOP
htp.p(item);
htp.p(qty1);
htp.p(qty2);
htp.p(qty3);
etc....

IS THIS SOMETHING TO BE DONE USING REF CURSORS in PL/SQL?

Thank you,
 


Followup   December 24, 2002 - 4pm Central time zone:

not with ref cursors - they fetch into a known set of plsql variables or a fixed record (eg: the 
OUTPUT of a ref cursor is known at compile in if plsql is fetching)

using dbms_sql -- trivial.  Just look at the listprint code, or the cellsprint code in owa_util. 

5 stars cursor   December 24, 2002 - 4pm Central time zone
Reviewer: mo 
Tom:

Thanks. I will look into dbms_sql even though I remember you used to always say use "Execute 
immediate instead".

1. getcode owa_util.listprint does not work.  Do i have to login as SYS.

THank you, 


Followup   December 24, 2002 - 5pm Central time zone:

Nope -- I never said that.  Read my chapter on dynamic sql in "Expert one on one" -- there I show 
you when you will use native dynamic sql and when you will use dbms_sql.

This is one of the cases that clearly falls into dbms_sql.

getcode uses USER_SOURCE. You would have to be logged in as the owner of the package in order to 
use it or

select text from dba_source where name = 'OWA_UTIL' and type = 'PACKAGE BODY' order by line 

4 stars I do it the following way when all the predicates are varchars   December 26, 2002 - 4am Central time zone
Reviewer: Bala from KL, Malaysia
This one is regarding the original posting.
define a cursor for select * from tbl where col_v1 like nvl(v1,'%'||v1||'%') and col_v2 like 
nvl(v2,'%'||v2||'%') and ..... so on.
Any 'Danger Will Robinson's  here? 


Followup   December 26, 2002 - 11am Central time zone:

If you always put % on the front and back -- you can do that since indexes would be pretty much 
useless where and the only plan will be a full scan (eg: the plan will not change from query to 
query). 

This solution above is good when you want:


select * from t where x = 5;

to be optimized DIFFERENTLY then 


select * form t where y like 'hello%';

In this case -- we might make use of an index on X for one and Y on another.  That will NOT work 
with you do something like:


where x = nvl(bind_var_x,x)
  and y like nvl(bind_var_y,y)

 

5 stars ref cursor   January 13, 2003 - 3pm Central time zone
Reviewer: mo 
Tom:

Would this also work instead of using ref cursor. let us say you have three inputs and you want to 
build AND SQL statement.

FOR x in (SELECT * from table where country=p_country
          intersect
          SELECT * from table where state=p_state 
          intersect
          SELECT * from table where city=p_city)
LOOP
...
END LOOP; 


Followup   January 13, 2003 - 4pm Central time zone:

time it -- tell me what happens when you specify:

USA
CA
San Francisco


USA -- returns lots of records (lots of people in that country)
CA  -- returns a good percentage of those (lots of people in CA)
San Francisco -- returns the least number, but still sizable

Then, we put them together -- and figure out what is in all three and sort distinct it (which may 
materially change the answer - sort/distinct)...


Or, we can just

select * from table where .....;


 

5 stars context   January 13, 2003 - 4pm Central time zone
Reviewer: mo 
Tom:

You are right. The interesect can be slow for large tables.

However, when I implement the context solution: I get

SQL> create or replace context proc_ctx using proc_list;
create or replace context pi_ctx using pi_list
*
ERROR at line 1:
ORA-01031: insufficient privileges

I asked DBA to grant context for dev account and he says it is not simple. Is it difficult? is in 
it a simple grant?

Thank you, 


Followup   January 13, 2003 - 7pm Central time zone:

They should create the context for you.  It is that simple.


If they won't do it, ctl-f and search for

Another way? 

on this page.

 

5 stars set context   January 15, 2003 - 5pm Central time zone
Reviewer: mo 
Tom:

To build a clause as :

where col1 between p_from_col1 and p_to_col1 

Can you do in one if statement?

or you have to implement two ifs and two set_context.

Thanks, 


Followup   January 16, 2003 - 8am Central time zone:

think about it...  this is just writing code mo, just writing code.  Seems like it is one if, two 
sets doesn't it?

if ( p_from_col1 is not null and p_to_col1 is not null )
then
    dbms_session.set_ctx( 'my_ctx', 'p_from_col1', p_from_col1 );
    dbms_session.set_ctx( 'my_ctx', 'p_from_col2', p_from_col2 );
    l_query := l_query || ' and col1 between sys_context .......';
end if;


doesn't make any sense to have two ifs - if either of to or from is null - it would not make sense 
to query on them.  You obviously need two context values -- hence you need two calls to set 
context. 

5 stars context   January 16, 2003 - 9am Central time zone
Reviewer: mo 
Tom

Thanks. What I meant is that whether I can assign two values in one statement of 
dbms_session.set_ctx but obviously I can not.

THanks a lot, 


5 stars count   January 22, 2003 - 10am Central time zone
Reviewer: mo 
Tom:

For the above solution ,let us say you want to count records before you open ref cursor. I used 
execute immediate and it does not work.

execute immediate 'select count(*) from '||l_query||' into l_cnt';

Is not this valid? 


Followup   January 22, 2003 - 10am Central time zone:

why -- you do know the answer is immediately different - that count is obsolete right away....

I hate that idea more then anything -- well, maybe not more then not using bind variables....  but 
you get my point.


reread:

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:3489618933902
and read:

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:4456921593765
to get my opinion on that very bad, wasteful, slow, wrong practice.
 

5 stars count   January 22, 2003 - 10am Central time zone
Reviewer: mo 
Tom:

I see you point which makes sense.  However, the count is accurate when that user started his 
search.

Are you also against the idea of doing a count for a the record subset (10 records) (if pagesize is 
10) after a page is created to determine whether to plae a NEXT button or not?

Are you always in favor of displaying a blank page?

Thanks, 


Followup   January 22, 2003 - 11am Central time zone:

no it isn't -- the instant after you start that query that count can change.


do it the way you want.  what I am in favor of isn't really relevant here is it?

Don't do a count, just overfetch by one record to see if there is a next page. 

5 stars variable output parameters   January 27, 2003 - 4pm Central time zone
Reviewer: Sujata from Germany
building a procedure for advanced search... 
This procedure will accept parameters from a web page...and returns a recordset to the web page. 
The output parameters will be what ever user wants..and user will also build the where clause...for 
example "Employee Name like 'To%'.
The no of tables invloved are known...but the joins are built dynamically....
This is how I approached:

PROCEDURE EMP_ADVANCED_SEARCH (pCOL LONG := null, 
                      pRELATION LONG := null,
                      pVALUE LONG := null,
                      pSELECT_COL LONG := null,
                      C_OUT_CUR OUT T_REFCUR) 



AS

vSQL LONG;
vSELECT LONG;
vFROM LONG;
vWHERE LONG;


BEGIN

vSELECT := pSELECT_COL;



VSQL := 'SELECT ' || vSELECT || CHR(10) ||
        ' FROM ' || vFROM || CHR(10) ||
        ' WHERE ' || vWHERE;    


WRITE_A_STRING (VSQL);
OPEN C_OUT_CUR FOR VSQL;

END EMP_ADVANCED_SEARCH;




Here is a sample of values for parameters:
pCOL = 'ENAME,DEPT_NAME'
pRELATION = 'LIKE,='
pVALUE = '%TO%,10'
pSELECT_COL = 'EMPLID, DEPT, ENAME, SALARY' 

pCOL, pRELATION, pVALUE ARE THE PARAMETER TO BUILD WHERE CLAUSE..BASED ON THE USER INPUT...
pSELECT_COL DESCRIBES THE COLUMNS THAT THE USER WANTS IN THE RECORDSET

vFROM, vWHERE CLAUSE WILL BE BUILT BASED ON pSELECT_COL,pCOL, pRELATION AND pVALUE 
PARAMETERS. 




QUESTION:
I am lost on building FROM and WHERE clause dynamically part...
Here is what I am imagining:
I will run pCOL and pSELECT_COL through loops..and will check the column_name 
against SOME_TABLE to find the table name the column is in
and will keep adding 
to the VFROM and VWHERE clause...and I will check before I add a join relationship to VWHERE 
or a table to VFROM...to make sure that it hasn't been added before (instr function).

SOME_TABLE will have table_names, column_names
as two columns...and I will check the column names in pCOL and pSELECT_COL parameters against
this table. 
In scott schema for example DEPTNO is in both tables EMP and DEPT.
but I think this custom table should have DEPTID reference only one time...so that
we will hit one or the other when we are looking for DEPTID column..to see what table does 
it relate to..
We won't mention DEPT table in the from clause or where clause...unless DEPTNAME is mentioned
either in pCOL parameter or pSELECT_COL parameter.


Is there a better way to accomplish this?

Thanks.


 


3 stars sys_context not using index???   March 11, 2003 - 8am Central time zone
Reviewer: Paul from UK
Tom,

Can you explain the following, please? I'm very puzzled!

Oracle 8.1.7 - Customers has 4 million odd rows, indexed on postcode.
As per your book p716,  I have used sys_context when building up dynamic sql.
So why won't Oracle use the index when using sys_context when I'm using both OR and LIKE, yet it 
does when hard-coding the values (see below)?  It also works fine if I just use LIKE (no OR) with 
the sys_context statement.


SQL> select
  2  UPPER(sys_context('mycontext','postcode1'))||'%' postcode1,
  3  UPPER(sys_context('mycontext','postcode2'))||'%' postcode2
  4  from dual;

postcode1                       postcode2
------------------------------- ---------------------------------------
NT6 7EF%                        NT6 8AP%

SQL> set autotrace on
SQL> select cus_number from customers cus
  2  where cus.postcode like 'NT6 7EF%'
  3  or
  4  cus.postcode like 'NT6 8AP%';

CUS_NUMBER  
------------- 
3860001833844 
3860005094542 
3860005094543 
3860005094544 
3860002229221 
3860002229878 
3860002386195 
3860002386196 
3860002436754 
3860002436844 
0273000769335 
0273000638189 
0273000952026 
0273002199257 
0273002239570 
0273002446665 
0273000769334 

17 rows selected.


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=16 Card=16 Bytes=2064)
   1    0   CONCATENATION
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=4 Card=1 Bytes=129)
   3    2       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=3 Card=1)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=4 Card=1 Bytes=129)
   5    4       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=3 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         25  consistent gets
          0  physical reads
          0  redo size
       4239  bytes sent via SQL*Net to client
        529  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         17  rows processed

SQL>  select * from customers cus
  2  where cus.postcode like UPPER(sys_context('mycontext','postcode1'))||'%'
  3  or
  4  cus.postcode like UPPER(sys_context('mycontext','postcode2'))||'%';

CUS_NUMBER  
------------- 
3860001833844 
3860005094542 
3860005094543 
3860005094544 
3860002229221 
3860002229878 
3860002386195 
3860002386196 
3860002436754 
3860002436844 
0273000769335 
0273000638189 
0273000952026 
0273002199257 
0273002239570 
0273002446665 
0273000769334 

17 rows selected.


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=28972 Card=545239 Bytes=70335831)
   1    0   TABLE ACCESS (FULL) OF 'CUSTOMERS' (Cost=28972 Card=545239 Bytes=70335831)


Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
     190893  consistent gets
     189129  physical reads
          0  redo size
       4242  bytes sent via SQL*Net to client
        617  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
         17  rows processed
 


Followup   March 11, 2003 - 8am Central time zone:

what happens with:


variable x varchar2(20);
exec :x := 'NT6 7EF%';
variable y varchar2(20);
exec :y := 'NT6 8AP%';

select cus_number from customers cus
 where cus.postcode like :x
 or
 cus.postcode like :y;

(just to show it is not sys_context to get excited about first...)

You see -- with the binds, the optimizer says, you know, it could be:

where cus.postcode like '%'

you know, that would get quite a few rows.....


tell me -- what is the distribution of the postcode.  how do you analyze the table.  and what does 
that other query do.

Also, what is the frequency by which you run this query with different values? 

4 stars More feedback   March 11, 2003 - 10am Central time zone
Reviewer: Paul 
Right again, Tom!

Using binds from SQL*Plus instead of the sys_context code, got the same plan!

1) Distribution of postcodes is like this:
SQL> select count(*),count(postcode),count(distinct postcode) from customers;

 COUNT(*) COUNT(POSTCODE) COUNT(DISTINCTPOSTCODE)
--------- --------------- -----------------------
  5592187         3981897                  852500

There are only 9 postcodes (excluding NULL) that occur more than 100 times.  The most common 
postcode has 1855 occurrences.

2) Analyze done this way:
execute DBMS_STATS.GATHER_SCHEMA_STATS('${ORACLE_USER}',25,FALSE,'FOR ALL INDEXED 
COLUMNS',NULL,'DEFAULT',TRUE);


3) The other query (i.e. using just 1 sys_context clause) is like this:
SQL>  select cus_number from customers cus
  2  where cus.postcode like UPPER(sys_context('mycontext','postcode1'))||'%';

CUS_NUMBER  
------------- 
3860002229221 
3860002229878 
0273000769335 
0273002239570 
0273000769334 
3860001833844 
3860005094542 
3860005094543 
3860005094544 

9 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=22760 Card=279610 Bytes=5312590)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=22760 Card=279610 Bytes=5312590)
   2    1     INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=138 Card=279610)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         13  consistent gets
          0  physical reads
          0  redo size
        374  bytes sent via SQL*Net to client
        275  bytes received via SQL*Net from client
          5  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          9  rows processed

4) This query is part of a more complex query for searching the customers table by anything the 
user wants - surname, date of birth, town, postcode etc.  It had been working "acceptably" until 
they requested being able to search on multiple postcodes (which they provide in a comma-separated 
string and I cut up to build up the Dynamic SQL).  I would guess it's used hundreds of times a day.


 


Followup   March 11, 2003 - 10am Central time zone:

what happens if you build a union all query instead?


  select * 
    from customers cus
   where cus.postcode like UPPER(sys_context('mycontext','postcode1'))||'%'
   UNION ALL
  select * 
    from customers cus
   where cus.postcode like UPPER(sys_context('mycontext','postcode2'))||'%';

 

3 stars That works fine   March 11, 2003 - 12pm Central time zone
Reviewer: Paul 
The only problem is that my actual query can be way, way more complicated than this.  If the users 
enter lots of different criteria the SQL generated gets large with several WHERE EXISTS clauses 
(which are causing me other performance problems!)

If the user enters 5 postcodes, then the amount of SQL will be generated fivefold.

Is it just the the optimizer isn't clever enough? ;-)  It seems odd that running 2 queries is 
quicker than 1! 


Followup   March 11, 2003 - 1pm Central time zone:

what is your optimizer_index_cost_adj/index_caching set to. 

3 stars More feedback   March 12, 2003 - 4am Central time zone
Reviewer: Paul from UK
SQL> select * from v$parameter
  2  where name like 'optimizer_ind%';

      NUM NAME                                TYPE VALUE        
--------- ------------------------------ --------- -----------
      392 optimizer_index_cost_adj               3 100          
      393 optimizer_index_caching                3 0            
 


Followup   March 12, 2003 - 7am Central time zone:

so, in your session, see what 

alter session set optimizer_index_cost_adj = 25;
alter session set optimizer_index_caching = 80;

does for you -- in SQLPLUS using binds -- just test it out. 

4 stars Interesting - Different plan, but still poor performance   March 12, 2003 - 8am Central time zone
Reviewer: Paul 
SQL> alter session set optimizer_index_cost_adj = 25;

Session altered.

SQL> alter session set optimizer_index_caching = 80;

Session altered.

SQL>
SQL> variable x varchar2(20);
SQL> exec :x := 'NT6 7EF%';

PL/SQL procedure successfully completed.

SQL> variable y varchar2(20);
SQL> exec :y := 'NT6 8AP%';

PL/SQL procedure successfully completed.

SQL>
SQL> select cus_number from customers cus
  2   where cus.postcode like :x
  3   or cus.postcode like :y;

CUS_NUMBER  
------------- 
3860001833844 
3860005094542 
3860005094543 
3860005094544 
3860002229221 
3860002229878 
3860002386195 
3860002386196 
3860002436754 
3860002436844 
0273000769335 
0273000638189 
0273000952026 
0273002199257 
0273002239570 
0273002446665 
0273000769334 

17 rows selected.


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6008 Card=222592 Bytes=4229248)
   1    0   CONCATENATION
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=318 Card=10600 Bytes=201400)
   3    2       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=138 Card=10600)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=318 Card=10600 Bytes=201400)
   5    4       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=138 Card=10600)


Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
     190893  consistent gets
     190874  physical reads
          0  redo size
        583  bytes sent via SQL*Net to client
        305  bytes received via SQL*Net from client
          7  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         17  rows processed
 


Followup   March 12, 2003 - 8am Central time zone:

but that is the same plan -- I think autotrace is lying, use tkprof and see what that says...

SQL> select cus_number from customers cus
  2  where cus.postcode like 'NT6 7EF%'
  3  or
  4  cus.postcode like 'NT6 8AP%';

CUS_NUMBER  
------------- 
3860001833844 
3860005094542 
3860005094543 
3860005094544 
3860002229221 
3860002229878 
3860002386195 
3860002386196 
3860002436754 
3860002436844 
0273000769335 
0273000638189 
0273000952026 
0273002199257 
0273002239570 
0273002446665 
0273000769334 

17 rows selected.


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=16 Card=16 Bytes=2064)
   1    0   CONCATENATION
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=4 Card=1 
Bytes=129)
   3    2       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=3 Card=1)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=4 Card=1 
Bytes=129)
   5    4       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=3 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         25  consistent gets
          0  physical reads
          0  redo size
       4239  bytes sent via SQL*Net to client
        529  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         17  rows processed 

3 stars Whilst I'm waiting ...   March 13, 2003 - 9am Central time zone
Reviewer: Paul 
... for my DBA to give me the ability to set sql_trace=true and give me access to the 
user_dump_dest directory, can you explain what you mean by "I think autotrace is lying"?

Does this happen very often, and more to the point, why? 


Followup   March 14, 2003 - 5pm Central time zone:

it doesn't happen often -- it is because autotrace uses explain plan and that is close, not not 
always 101%.

I think you'll find, based on the numbes, it was a full scan. 

4 stars Problem appears to have gone away!!   March 19, 2003 - 9am Central time zone
Reviewer: Paul 
Tom,

This is very odd.  I have only just got back from a few days away.  I ran the identical query as 
above, before and after setting the optimizer_index parameters.  Before - as slow as ever.  After, 
and all of a sudden it's working like lightning!  I've included the autotrace output below (I've 
got the TKPROF output too, but I don't suppose there's much point now that it's "working").  I 
suppose I should be happy that it's working nicely now, but I wish I knew why, and I'm a bit uneasy 
in case it happens again in production.

SQL> alter session set optimizer_index_cost_adj = 25;

Session altered.

SQL> alter session set optimizer_index_caching = 80;

Session altered.

SQL>
SQL> variable x varchar2(20);
SQL> exec :x := 'NT6 7EF%';

PL/SQL procedure successfully completed.

SQL> variable y varchar2(20);
SQL> exec :y := 'NT6 8AP%';

PL/SQL procedure successfully completed.

SQL>
SQL> select cus_number from customers cus
  2   where cus.postcode like :x
  3   or cus.postcode like :y;

CUS_NUMBER  
------------- 
3860001833844 
3860005094542 
3860005094543 
3860005094544 
3860002229221 
3860002229878 
3860002386195 
3860002386196 
3860002436754 
3860002436844 
0273000769335 
0273000638189 
0273000952026 
0273002199257 
0273002239570 
0273002446665 
0273000769334 

17 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6008 Card=222592 Bytes=4229248)
   1    0   CONCATENATION
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=318 Card=10600 Bytes=201400)
   3    2       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=138 Card=10600)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (Cost=318 Card=10600 Bytes=201400)
   5    4       INDEX (RANGE SCAN) OF 'CUS_PC_I' (NON-UNIQUE) (Cost=138 Card=10600)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         25  consistent gets
          0  physical reads
          0  redo size
        544  bytes sent via SQL*Net to client
        309  bytes received via SQL*Net from client
          7  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         17  rows processed



 


Followup   March 19, 2003 - 10am Central time zone:

the only thing I can think of is a cached cursor -- autotrace explained the query (getting the 
index plan) but plus ran a cached cursor -- that was associated with a full scan. 

5 stars character limit on dbms_session.set_context is 256?   April 23, 2003 - 4pm Central time zone
Reviewer: Sachin from Germany
Database: 8.1.7.4

Tom,
I have a varchar2(2000) column in a table...that I am trying to update through a procedure that 
uses dbms_session.set_context.

The procedure has a statement like:

.........
ELSIF (pOPTION = 8) THEN
   dbms_session.set_context( 'CTX_CB_STRTG_SURVEY_PKG', 'C_OUTDOOR', pOUTDOOR);
   vSET := vSET || ' OUTDOOR = sys_context(''CTX_CB_STRTG_SURVEY_PKG'', ''C_OUTDOOR''),';   

END IF;

vSET := RTRIM (vSET,',');

--dbms_output.put_line (vSET);

vWHERE := ' USERID = :XUSERID ';

vSQL := 'UPDATE ' || vUPDATE || CHR(10) ||
    ' SET ' || vSET || CHR(10) ||
    ' WHERE ' || NVL (vWHERE, '1=1');

--write_a_string (vSQL);

EXECUTE IMMEDIATE VSQL USING pUSERID;
......
.................

I am not being able to update OUTDOOR column to anything 
more than 256 characters.
I can do it through an update statement outside the procedure...but not through procedure...
So, does dbms_session.set_context has a limit on characters..
pOUTDOOR parameter does pass with the correct length...
If dbms_session.set_context does have a limit...how else would we bind parameter in this case

Thanks. 


Followup   April 23, 2003 - 8pm Central time zone:

sys_context is 255

you need to use a bind variable here.  DBMS_SQL will be more appropriate as you can do it 
procedurally. 

5 stars dbms_session.set_context limit...   April 23, 2003 - 5pm Central time zone
Reviewer: Sachin from Germany
Ok...I saw it in the docs...
So, is there another way to bind variables varchar2(something more than 256):

Syntax
DBMS_SESSION.SET_CONTEXT (
   namespace VARCHAR2, 
   attribute VARCHAR2, 
   value     VARCHAR2);

Parameters
Table 46-12 SET_CONTEXT Procedure Parameters 
Parameter  Description  
namespace

  Name of the namespace to use for the application context (limited to 30 bytes). 
 
attribute

  Name of the attribute to be set (limited to 30 bytes). 
 
value

  Value to be set (limited to 256 bytes). 
 
 


Followup   April 23, 2003 - 8pm Central time zone:

dbms_sql 

5 stars SET_CONTEXT.VALUE LIMIT   April 24, 2003 - 4pm Central time zone
Reviewer: Sachin from Germany
DBMS_SQL or go to 9i :)
SET_CONTEXT Procedure
This procedure sets or resets the value of a context attribute.

Syntax
DBMS_SESSION.SET_CONTEXT (
   namespace VARCHAR2, 
   attribute VARCHAR2, 
   value     VARCHAR2,
   username  VARCHAR2,
   client_id VARCHAR2);

Parameters
Table 65-18 SET_CONTEXT Procedure Parameters
Parameter Description 
namespace
 Name of the namespace to use for the application context (limited to 30 bytes).
 
attribute
 Name of the attribute to be set (limited to 30 bytes).
 
value
 Value to be set (limited to 4 kilobytes).
 
username
 The username attribute of the application context
 
client_id
 The application-specific identifier of the current database session.
 

 


5 stars cursor with like operator   May 8, 2003 - 4pm Central time zone
Reviewer: mo 
Tom:

I have the following implicit cursor

  FOR x in (SELECT stock_number,description from stock
      where description like '%p_keyword%')
    LOOP

I am not getting any result?  i_keyword is the word or sentence that a user passes from a web page 
to serach for records matching his criteria.

2.  If I want to make it case insensitive can I use upper function between the % .

Thank you, 


Followup   May 9, 2003 - 1pm Central time zone:

do you have anything with the string p_keyword in it?


think you might mean to code:

for x in ( sel.... where description like '%' || p_keyword || '%' ) 
loop


but you probably really really want to look at intermedia text (simply TEXT in 9i) and use that 
instead.  what you have now is and will be a full scan which will not scale very well over time.

you want to be able to query

select .... where contains( description, p_keyword ) > 0


and use an index. 

5 stars keyword   May 9, 2003 - 6pm Central time zone
Reviewer: mo 
TOm:

1.  you mean this is done using intermedia.
<select .... where contains( description, p_keyword ) > 0>

DO i only ask the DBA to install internedia and use it?

2. Table is a lookup with 900 descriptions. Each description is about 30 characters. Would you 
still be using intermedia?

3. You were right about piping the parameter. However let us say the p_keyword is "Key" . Woold not 
that find all phrases that have KeyWest in them too.  DO i change it to the take the p_keyword and 
look for a space after it in order to do an exact search"?

Thank you

 


Followup   May 9, 2003 - 7pm Central time zone:

1) yes.  it should be installed by default, although they can choose to not install it, do you have 
a schema ctxsys in all-users?

2) to avoid a full scan, sure.  

3) you would use intermedia, it does words.


lets say you have this data:

key to power -- is knowledge
Having knowledge is key


if you used the "space" trick you might find the first, but not the last.   

4 stars DBMS_SESSION.SET_CONTEXT and ORA-01031   May 12, 2003 - 1pm Central time zone
Reviewer: darren from London
Good quick fix, easy to locate and implement.

I'd created a new package containing DBMS_SESSION.SET_CONTEXT calls and was getting ora-01031 
errors.

simply executed:

SQL> create or replace context SEARCHNEW_CONTEXT USING SPSEARCHNEW;

and fixed!
(though the java side did let me down again
but hey! thats life! (and thats java!) 


3 stars Optimizer Settings Earlier in Post   May 14, 2003 - 1am Central time zone
Reviewer: Matt from Australia
Earlier you suggested changed some optimizer parameters. I understand thst this affect the 
weightings that the CBO applies to accessing tables via an index or via a full scan. I am just 
curious why you chose these particular values. Obviously you have seen these settings work well in 
the past, but was there any other criteria that caused you to choose these particular values? 

alter session set optimizer_index_cost_adj = 25;
alter session set optimizer_index_caching = 80;

I am working on converting queries from the RBO to the CBO and have been going through a tuning 
exercise to alter these values. However, I have taken the approach of only altering the 
optimizer_index_cost_adj parameter. I am seeing improvements in CBO timings as I move 
optimizer_index_cost_adj from 100 down to 70 compared to the RBO but nothing significant. I am 
definately not seeing any benefits over the CBO so far (I know that these will come when I find the 
right magic values).

Surely,

alter session set optimizer_index_cost_adj = 25;
alter session set optimizer_index_caching = 80;

is not a rule of thumb? ( :o) )

Best Regards. 


Followup   May 14, 2003 - 7am Central time zone:

they are two sides of the pendulum.

You want to see a big swing?  swing hard to the other side.  I just started "low" 

3 stars Confirmation.   May 14, 2003 - 8pm Central time zone
Reviewer: Matt from Australia
I see.

So you are saying that:

optimizer_index_cost_adj = 25;
optimizer_index_caching = 80;

is a slight push to the optimizer to alter it's behaviour. A bigger push would then be:

optimizer_index_cost_adj = 75;
optimizer_index_caching = 20;

Is it always necessary to alter these parameters as a pair?

If so, is it as simple as geometrically defining a straight line between these two points above to 
find the sweet spot? 

Matt 


Followup   May 14, 2003 - 11pm Central time zone:

no, it takes it HARD to port, not soft.

the defaults:

optimizer_index_caching              integer     0
optimizer_index_cost_adj             integer     100


the other end of the spectrum:
                                                 Hard     Really Hard
                                                 to port  to port
optimizer_index_caching              integer     80       90
optimizer_index_cost_adj             integer     25       10


cost_adj -> zero implies indexes are cheap, really cheap
index_caching -> 100 implies indexes incurr no PIO 

5 stars coun of cursor   May 19, 2003 - 6pm Central time zone
Reviewer: mo 
Tom:

I remember you had an excellent article on explaining how to count records inside/outside a cursor 
but I tried all kind of searches to find it and I can not. Can you point me to it?

Thank you 


Followup   May 20, 2003 - 7am Central time zone:

you must have been dreaming?  I don't know what you are refering to.

the only way to know the number of records in a "cursor" is to actually fetch them all -- at which 
point the cursor is no longer a cursor, it is empty.


of course, you could add "count(*) over()" as a column but boy would that be a huge mistake and 
kill your performance totally. 

5 stars cursor   May 19, 2003 - 6pm Central time zone
Reviewer: mo 
Tom:

if i have 

for x in ( select * from table)
LOOP
---HERE can i get how many records were retrieved
...
....

END LOOP;

Thank you,
 


Followup   May 20, 2003 - 7am Central time zone:

add rownum to the query or count them. 

5 stars Great Information   May 23, 2003 - 6am Central time zone
Reviewer: Deanna from SF
This listing was very helpful in understanding how to use bind variable.  It is one of the most 
valuable pieces of information out on the site!  Thank you 


Do you know how to use this with Intermedia.  When applying the code I get "Invalid Relational 
Operator"
I tried modifying the quotes but had no luck

Thanks

PROCEDURE REPORT_SEARCH (p_kw1 IN VARCHAR2 DEFAULT NULL)

AS
        TYPE RC IS REF CURSOR;
        v_query VARCHAR2(3000);
       l_cursor RC;
       l_query  VARCHAR2(3000)
               DEFAULT ('SELECT a.url 
                                         
                       FROM test_table a
                       WHERE 1=1');
  
          cursor l_template IS
                        SELECT a.url 
                        FROM  test_table a;
                       
          l_rec l_template%ROWTYPE;
 
BEGIN
 IF ( p_kw1 IS NOT NULL ) THEN
           dbms_session.set_context( 'test_ctx', 'KW1',
                      'contains( url, p_kw1 ) > 0 ' );     
       
    l_query := l_query ||
               ' and sys_context( ''test_ctx'', ''KW1'' ) ';
      END IF;
      OPEN l_cursor for l_query;
       LOOP
           FETCH l_cursor into l_rec;
           EXIT WHEN l_cursor%NOTFOUND;
 
            dbms_output.put_line( l_rec.url  );
       END LOOP;
  
      CLOSE l_cursor;
      END; 


3 stars   May 23, 2003 - 5pm Central time zone
Reviewer: Deanna from SF,CA
Hi Tom

Thanks for the reply
I read the information in the following link, and I still get "Invalid relational Operator" at run 
time

I matched this syntax:
if ( p_ename is NOT NULL ) then
           l_query := l_query ||
                ' and contains( field, :p_ename ) > 0 '
with this syntax 
      
 IF ( p_kw1 IS NOT NULL ) THEN
      dbms_session.set_context( 'report_search_ctx', 'KW1',
           'contains( url, :p_kw1 ) > 0' );                            
     l_query := l_query ||
        'and sys_context( ''report_search_ctx'', ''KW1'' ) ';
      END IF;

Is the problem with the sys_contex ?
 


Followup   May 24, 2003 - 10am Central time zone:

look at the query you would be generating.  you are basically saying:


select * from t where f(x,y);


f being a function.  that does not make sense.  

you do not put the PREDICATE into sys_context, you put values to be passed to a predicate in there.

You cannot do this:


   set_context( ...., ' empno = 1234 ' )

and expect "select * from emp where sys_context( ... )"  to work -- same with contains 

5 stars   May 25, 2003 - 7am Central time zone
Reviewer: A reader 
tom, just to get it 100% right: the SYS_CONTEXT issue does NOT apply to code like that?

open ref_cursor for
  select <some columns> from <some tables>
  where  <some joins>
    and  col1 = p_MyParam;

(but only to GENERATED SQL statements?) 


Followup   May 25, 2003 - 10am Central time zone:

it applies to ANY sql statement, sys_context is just a function that returns a string -- use it 
anywhere you would put a string (character string literal) 

3 stars   May 25, 2003 - 7am Central time zone
Reviewer: Deanna from SF,CA
Hi Tom

Thanks again for the information... especially on a 3 day weekend :)

I tried this  

IF ( p_kw1 IS NOT NULL ) THEN
           dbms_session.set_context( 'report_search_ctx', 'KW1',p_kw1 );  
       
    l_query := l_query ||
            ' and contains (url, (sys_contex(''report_search_ctx'', ''KW1'' ))) >0 ' ;  

Thinking that (sys_contex(''report_search_ctx'', ''KW1'' )) contained my value therefore if I 
placed it as the value in the contains clause that would work.

Instead I get now Invalid column name 

How would you resolve this problem?  Ill be working on it further to try to "get the clues" in the 
last response 


Followup   May 25, 2003 - 10am Central time zone:

just read:

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:5225986313862#5261789008763
it shows exactly what you need to do. 

4 stars   May 25, 2003 - 11am Central time zone
Reviewer: A reader 
sorry for that, tom, but to me it 's still not 102% clear if one should use SYS_CONTEXT rather than 
"normal" parameters (such as 'p_MyParam' in my sample above):

is there any (major) difference between:

open ref_cursor for
  select <some columns> from <some tables>
  where  <some joins>
    and  col1 = p_MyParam;

and:

open ref_cursor for
  select <some columns> from <some tables>
  where  <some joins>
    and  col1 = SYS_CONTEXT('MyCtx', 'p_MyParam');

I've never thought of using SYS_CONTEXT that way but just only parameters: their values get BOUND 
to the query - not their values "copied" into it, right?

so, can one summarize: use SYS_CONTEXT wherever string literals occur AND CHANGE between several 
executions of the statement (otherwise there 's no advantage too). 


Followup   May 25, 2003 - 11am Central time zone:

I would use 


open ref_cursor for
  select <some columns> from <some tables>
  where  <some joins>
    and  col1 = p_MyParam;



rather then 

open ref_cursor for
  select <some columns> from <some tables>
  where  <some joins>
    and  col1 = SYS_CONTEXT('MyCtx', 'p_MyParam');

because it takes less code to do so.  sys_context() works just like a bind. 

5 stars cursor   May 25, 2003 - 9pm Central time zone
Reviewer: mo 
Tom:

when you do:

l_query  varchars(2000)  default 'select * from table where user_id = p_user_id'

open l_cursor from l_query;

I got an error.  it does not seem to recognize that p_user_id is a parameter and substitute the 
value.  I also tried this:

l_query  varchars(2000)  default 'select * from table where user_id ='||p_user_id;

and it did not work.  are you stuck with sys_context here?


 


Followup   May 26, 2003 - 9am Central time zone:

no, you are stuck reading the documentation ;)


l_query := 'select * from table where user_id = :p_user_id';
open l_cursor from l_query USING p_user_id;


 

5 stars U tell me dude   May 26, 2003 - 12am Central time zone
Reviewer: A reader 
your code :
l_query  varchars(2000)  default 'select * from table where user_id = p_user_id'

open l_cursor from l_query;
correct :
l_query  varchars(2000)  default 'select * from table where user_id = :p_user_id'

open l_cursor for l_query using p_user_id;
 


5 stars dbms_session.set_context in a loop   May 27, 2003 - 5pm Central time zone
Reviewer: Sachin from Germany
(Version 8.1.7.4.0)

Tom,

I have a procedure "A" that builds a string and returns to procedure "B"
that calls it.

--CREATING cONTEXT
CREATE OR REPLACE CONTEXT CTX_WHERE USING A;

--CREATING PROCEDURE "A"
CREATE OR REPLACE PROCEDURE A (pCOL_ARRAY TYPES_PKG.ARRAY DEFAULT TYPES_PKG.ARRAY(), 
                               pRELATION_ARRAY TYPES_PKG.ARRAY DEFAULT TYPES_PKG.ARRAY(), 
                               pVALUE_ARRAY TYPES_PKG.ARRAY DEFAULT TYPES_PKG.ARRAY(),
                               pQUERY OUT LONG) AS

V_SEP VARCHAR2(4000) := ' AND ';
vQUERY LONG := NULL;

v_REC_COL VARCHAR2(4000);
v_REC_RELATION VARCHAR2(4000);
v_REC_VALUE VARCHAR2(4000); 

BEGIN

FOR I IN 1 .. pCOL_ARRAY.COUNT LOOP

 
  v_REC_COL := pCOL_ARRAY(I);

  v_REC_RELATION := pRELATION_ARRAY(I);
  
  
  dbms_session.set_context( 'CTX_WHERE', 'CTX_CATCH_ALL', pVALUE_ARRAY(I));
  v_REC_VALUE := 'Sys_context( ''CTX_WHERE'', ''CTX_CATCH_ALL'' )';  
  --v_REC_VALUE :=  pVALUE_ARRAY(I);
  

   IF vQUERY IS NULL THEN
          vQUERY := v_REC_COL || ' ' || v_REC_RELATION || ' ' || v_REC_VALUE;
      
   ELSE
          vQUERY := vQUERY || V_SEP || v_REC_COL || ' ' || v_REC_RELATION || ' ' || v_REC_VALUE;
  
   END IF;   
   
   
END LOOP;

pQUERY := vQUERY;

END A;



So, For Example...if we pass procedure A values as follows:
pCOL_ARRAY as TOOLS_PKG.MAKE_AN_ARRAY('EMPNO','DEPTNO')
pRELATION_ARRAY AS TOOLS_PKG.MAKE_AN_ARRAY('=','=')
pVALUE_ARRAY AS TOOLS_PKG.'2812','10')

We should get...
EMPNO = 2812 AND DEPTNO = 10

and we do get that...if we don't use contexts...i.e. uncommenting
the statement "v_REC_VALUE :=  pVALUE_ARRAY(I)"...and commenting out "dbms_session.set_context..."
but when do use contexts...I do net get the desired results..I can not see the values
stored in context..but my query does not return any results.

What am I doing wrong here?
Should the context be created using procedure B..since the procedure B calls procedure A, Could
that be the mistake? 


Followup   May 27, 2003 - 6pm Central time zone:

hows about this -- a simple, concise, yet complete example that demonstrates the issue in a manner 
I too can reproduce on my system?

eg: a test case....


but actually -- the issue seems to be one of logic here....

You have one context variable 'CTX_CATCH_ALL'

it can have but one value at a time.  Your query ends up being:

.... empno = sys_context( 'ctx_where', 'ctx_catch_all' ) and deptno = sys_context( 'ctx_where', 
'ctx_catch_all' ) ....


well, ctx-catch-all will just have whatever LAST value you stuffed in it -- doubtful you have an 
empno = deptno and deptno = sys_context() -- but that is what you coded in effect....

maybe

FOR I IN 1 .. pCOL_ARRAY.COUNT LOOP

  dbms_session.set_context
  ( 'CTX_WHERE', 'bv_'||i, pVALUE_ARRAY(I));

   IF vQUERY IS NULL THEN
          vQUERY := pCol_array(i) || ' ' || pRelation_array(i) || 
                   ' sys_context( ''ctx_where'', ''bv_' || i || ''' );
   ELSE
          vQUERY := vQUERY || V_SEP || pCol_array(i) || ' ' ||
                     pRelation_array(i) || 
                   ' sys_context( ''ctx_where'', ''bv_' || i || ''' );
   END IF;      
END LOOP;
 

5 stars Multiple Contexts or single   May 27, 2003 - 6pm Central time zone
Reviewer: Sachin from Germany
Thanks Tom,
Another Question...
In the Procedure A, I also evaluate the value of v_REC_RELATION (i.e. pRELATION_ARRAY(I))....as 
follows
   IF (v_REC_RELATION = 'BEGINS_WITH') THEN
 
      v_REC_RELATION := ' LIKE ';      
      dbms_session.set_context( 'CTX_WHERE', 'CTX_BEGIN', pVALUE_ARRAY(I) || '%');
      v_REC_VALUE := 'Sys_context( ''CTX_WHERE'', ''CTX_BEGIN'' )';
      
   ELSIF (v_REC_RELATION = 'ENDS_WITH') THEN
   
      v_REC_RELATION := ' LIKE ';      
      dbms_session.set_context( 'CTX_WHERE', 'CTX_END', '%' || pVALUE_ARRAY(I));
      v_REC_VALUE := 'Sys_context( ''CTX_WHERE'', ''CTX_END'' )';      
      
   ELSIF (v_REC_RELATION = 'PHRASE') THEN
   
      v_REC_RELATION := ' LIKE ';
      dbms_session.set_context( 'CTX_WHERE', 'CTX_PHRASE', '%' || pVALUE_ARRAY(I) || '%');
      v_REC_VALUE := 'Sys_context( ''CTX_WHERE'', ''CTX_PHRASE'' )';
      
   ELSIF (v_REC_RELATION = 'SOUNDS_LIKE') THEN
   
   
      v_REC_COL := 'SOUNDEX(' || pCOL_ARRAY(I) || ')';
      
      v_REC_RELATION := '=';
      dbms_session.set_context( 'CTX_WHERE', 'CTX_SOUND', 'SOUNDEX(' || pVALUE_ARRAY(I) || ')');
      v_REC_VALUE := 'Sys_context( ''CTX_WHERE'', ''CTX_SOUND'' )';

end if;


Question:
Is it better to set context just once: 
as
"  dbms_session.set_context( 'CTX_WHERE', 'BV_' || I, pVALUE_ARRAY(I));"

then keep changing the value for v_REC_VALUE (inside an if condition) as
v_REC_VALUE := '%' || 'Sys_context( ''CTX_WHERE'', ''BV_' || I || ''' )';

v_REC_VALUE := 'Sys_context( ''CTX_WHERE'', ''BV_' || I || ''' )' || '%';

....
....






 


Followup   May 27, 2003 - 6pm Central time zone:

up to you -- whatever makes more sense codewise to you 

4 stars Cursor in 8.1.7   May 31, 2003 - 7pm Central time zone
Reviewer: Randy from Warren, MI
Hi Tom,
I have a cursor for loop which runs on a varchar2(4000) column.  In 9i the cursor runs no problem.  
However, in 8.1.7, the column returns a blank value with > 1,000 characters.  I thought maybe this 
was the 32K PL/SQL limit, but then why does this work in 9i?  I have been looking for days for the 
answer on this one.  Any help you can provide will be greatly appreciated! 


Followup   May 31, 2003 - 9pm Central time zone:

you will have to provide a test case.

I've never observed what you say -- never. 

4 stars   May 31, 2003 - 9pm Central time zone
Reviewer: Randy from Warren
Thanks for your respose...
  I have the following PL/SQL statement which outputs data to MS word using Oracle COM automation 
feature...

declare 
    
i binary_integer;

filename varchar2(100);




begin

filename:= 'c:\test\report_'||sysdate;

i:=ORDWord.CreateWordObject('');

i:=ORDWord.FileNew();
i:=ORDWord.FormatFilePageSetup(1, 1, 54, 54, 54, 54, 0, 15840, 11520);
i:=ORDWord.FormatFontSize(12, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1, 0);
i:=ORDWord.FormatFontParagraph(0, 0, 0, 0, 0, 0, 1);
i:=ORDWord.InsertText('*** FOR OFFICIAL USE ONLY ***');
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertText('PM Combat Systems Weekly Significant Activity Report');
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertNewLine();
i:=ORDWord.FormatFontSize(11, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1, 0);
i:=ORDWord.InsertText('Period Ending: '||sysdate);
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertNewLine();

For c1_rec IN (SELECT ROWNUM, SUBJECT, ISSUE, RESPONSIBLE_POC, PHONE_NUMBER FROM one_liner2 where 
status = 'Approved')
LOOP

i:=ORDWord.FormatFontSize(10, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1, 0);
i:=ORDWord.FormatFontParagraph(0, 0, 0, 0, 0, 0, 0);
i:=ORDWord.InsertText(c1_rec.rownum||'. '||c1_rec.SUBJECT||':');
i:=ORDWord.FormatFontSize(10, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0);
i:=ORDWord.FormatFontParagraph(0, 0, 0, 0, 0, 0, 0);
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertText(c1_rec.ISSUE);
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertText(c1_rec.RESPONSIBLE_POC||'/'||c1_rec.phone_number);
i:=ORDWord.InsertNewLine();
i:=ORDWord.InsertNewLine();

END LOOP;

i:=ORDWord.FileSaveAs(filename);

i:=ORDWord.FileClose();

end;

When I run the statement in 9i it works exactly the way it should.  However, when I run in our 
current production environment any data stored in the issues varchar2(4000) column that is > 1000 
characters the cursor returns nothing.  Everything else returns fine.  


Followup   June 1, 2003 - 9am Central time zone:

so, develop a small test case that shows where the issue is.  I mean -- I'll betcha that the cursor 
is returning the data but the ORDWord thing is having issues with it.

eg: either keep the cursor but remove the ordword calls and just dbms_output substrs of the data.  
If you see data -- then you've narrowed the problem down.

Once you do that, remove the sql and just make ordword calls.  You know, declare a local variable 
of varchar2(4000) -- fill it up and use it instead of SQL (so you have a reproducible test case 
that standsalone that you can give to support and they too can run).

After that -- try the logical workaround -- writing out the string in 512 byte chunks instead of in 
a single call.  Sort of like a procedure "P" I keep around for dbms_output which only does 255 
characters:

create or replace procedure p ( p_str in varchar2 )
is
   l_str   long := p_str;
begin
   loop
      exit when l_str is null;
      dbms_output.put_line( substr( l_str, 1, 250 ) );
      l_str := substr( l_str, 251 );
   end loop;
end;
/

and see if that doesn't work around your issue.



It is funny, if you go back to your text above:

...
I have a cursor for loop which runs on a varchar2(4000) column.  In 9i the 
cursor runs no problem.  However, in 8.1.7, the column returns a blank value 
with > 1,000 characters.  I thought maybe this was the 32K PL/SQL limit, but 
then why does this work in 9i?  I have been looking for days for the answer on 
this one.  Any help you can provide will be greatly appreciated! 
......

you make no mention of ORDWord.  You never tested the "cursor" (it undoubtably is NOT the issue 
here -- not a chance, it is not part of the problem at all).


You really need a TEST system that mirrors PRODUCTION -- developing in 9i and deploying to 8i just 
is a recipe for disaster -- cannot be done.  You need a system whereby you can isolate these issues 
(production isn't that environment).  I certainly hope you have such a system -- you might have to 
run 5 or 6 variants of the above code to isolate the exact issue and then you need to develop a 
workaround for it (i won't be able to -- you see, my computers don't do "com" -- open systems, 
sure, not com.  My solution would be UTL_FILE, write some HTML.  Pretty much every editor reads 
HTML.  "open", "easy", "portable" -- that is what it is all about) 

4 stars   June 1, 2003 - 9am Central time zone
Reviewer: Randy 
Hi Tom,
  Thanks for the response on Sunday:-).  I figured this was a cursor problem related to 8.1.7 
because it works in 9.  But from what you're telling me this isn't the case.  I will try some of 
your suggestions and see where it gets me.  Again, thanks for taking the time to read my question!
 


5 stars   June 2, 2003 - 12pm Central time zone
Reviewer: Randy 
Hi Tom,
 You were right, it was the ORDWord causing the problem.  I tried your suggestions and I think I 
figured out a possible work around. If I pull the data using the ORDWord.inserttext function 1000 
characters at a time it works fine.  However, if the data is < 1000 characters I get a fetched 
column value is NULL for substr > 1000 characters?  Can I write an exception clause if 
ORDWord.Inserttext(v_issue2) –- v_issue2 is the variable that holds > 1000 characters-- is null 
then do nothing?  


Thanks,
Randy 


Followup   June 2, 2003 - 12pm Central time zone:

just use a function like this:

create or replace procedure p ( p_str in varchar2 )
is
   l_str   long := p_str;
begin
   loop
      exit when l_str is null;
      dbms_output.put_line( substr( l_str, 1, 250 ) );
      l_str := substr( l_str, 251 );
   end loop;
end;
/

using ordword instead of dbms_output -- that should not through any exceptions 

5 stars   June 2, 2003 - 1pm Central time zone
Reviewer: Randy 
Tom,
  Thanks for all your help. Your example was exactly what I was looking for!! 

Randy  


 


5 stars Something Global inside triggers   June 5, 2003 - 11am Central time zone
Reviewer: Sachin from Germany
8.1.7.4.0
Tom,
We have some Auditing triggers on our tables..
USER is accessible to triggers automatically..
I don't know any way to pass parameters to triggers.
is there a way to have a variable that we can put in some kind of global context..that will be 
accessible to the triggers...for a session.

Thanks. 


Followup   June 5, 2003 - 1pm Central time zone:

set a package variable.

create or replace package my_pkg
as
   some_variable varchar2(25);
end;

anything can access my_pkg.some_variable now. 

5 stars context for triggers   June 5, 2003 - 4pm Central time zone
Reviewer: Sachin from Germany
Database Version: 8.1.7.4.0
Sorry for not being clear...
Actually, I am trying to catch the persons identity who is logged in (to use it for MODIFIEDBY 
column).
Database user who is logged into the app is always the same..so does not help us for auditing.
When a user logs in the app catches their NT login name...
but it is not a USER parameter (Select user from dual) that is visible to the trigger..so I was 
wondering if their is a way to catch that for a session..and use it in a trigger..
instead of moving some of the auditing to procedures...
Front end is Active Server Pages
 


Followup   June 5, 2003 - 6pm Central time zone:

not without the application itself telling us, we cannot reach out and just grab this information.

You can use dbms_application_info.set_client_info (then userenv('client_info') will have it)

You can create an application context and set it.

You can create a package with a "set user", "get user" function and use that. 

5 stars orword   June 6, 2003 - 5pm Central time zone
Reviewer: mo 
Tom:

what is ordword or oracle com software? can i write a pl/sql procedure that will dump my table into 
a MS Word file using that?

 


Followup   June 6, 2003 - 7pm Central time zone:

you can, but I think that would be wrong.

generate open solutions -- make it so anything can use your output.

HTML -- open
XML -- open
COM -- not open (or any of its other names like dcom, activex, .nyet, whatever)

 

5 stars COM   June 7, 2003 - 1pm Central time zone
Reviewer: mo 
Tom:

Thanks, but is COM software part of oracle database? or it is some extra tool that you have to get? 


Followup   June 7, 2003 - 2pm Central time zone:

COM would be a MS protocol.

On Windows, the Oracle server comes with a package that lets you make COM callouts to COM enabled 
things.

On Unix and other platforms, it doesn't since that protocol is pretty much all about -- well -- 
windows. 

5 stars SYS_CONTEXT   June 12, 2003 - 11am Central time zone
Reviewer: Sachin from Germany
8.1.7.4.0
Tom,

What is the difference between these two:
SELECT USERENV( 'client_info' ) INTO VUSER from dual;
SELECT SYS_CONTEXT('USERENV','CLIENT_INFO') from dual;

Thanks.

 


Followup   June 12, 2003 - 11am Central time zone:

SELECT SYS_CONTEXT('USERENV','CLIENT_INFO') from dual;
is the new prefered way where as

SELECT USERENV( 'client_info' ) INTO VUSER from dual;
is the old, deprecated way.

since sys_context was added and lots of new functionality surrounding it is avaiable, it is 
prefered that you use sys_context but userenv() will remain for quite a while as there is lots of 
code out there that uses it (including the oracle data dictionary views themselves) 

5 stars GREAT!!!   June 12, 2003 - 11am Central time zone
Reviewer: lou from NEW YORK
The information was great!!! however, how can I put an 
index to speed things up in this kind of situation. I am very
interested on how you andwer this because you are using LIKE thus preventing it from using an 
index.


Thanks,

LNL 


Followup   June 12, 2003 - 1pm Central time zone:

like doesn't prevent an index?  

not sure what you are asking for here? 

4 stars related to original question   June 17, 2003 - 11am Central time zone
Reviewer: sam from NY ,USA
Hi tom 

I am not sure if I posted this question earlier. I don't see it after 1 hour. 

can we have dynamic cursors with 'for update / current of '
clauses ??

 TYPE reference_cur IS REF CURSOR;
      c1 reference_cur;

     open c1 for 'select cust_num from ' || table_name || ' for update'; -- table_name is procedure 
input parameter
     loop
        fetch c1 into custnum;
        exit when c1%notfound;
     -- some calculations here
      execute immediate 'update ' || table_name || ' set 
      deleted_ind=' || '''N''' || ' where current of c1';

     end loop;

thanks 



 


Followup   June 17, 2003 - 12pm Central time zone:

look on the other page where you put this

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96521/tspaces.htm#19171
 

4 stars I did not put this earlier   June 17, 2003 - 1pm Central time zone
Reviewer: sam from NY , USA
Tom

I searched but this is the only thread I have put my question in.

Also The link you provided as a answer is for tablespace link.

Please answer my question  


Followup   June 17, 2003 - 4pm Central time zone:

sorry but you did,

I pasted the wrong link, see

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:584023239495#10489907277766
which is the other page you asked this on.
 

5 stars Can this be bounded   June 25, 2003 - 3pm Central time zone
Reviewer: Sachin from Germany
8.1.7.4

Tom,
I wrote a procedure to build the where clause..
For example

procedure build_where (pCOL ARRAY,
                       pRELATION ARRAY,
                       pVALUE ARRAY,
                       pQUERY OUT LONG);

Then I should be able to pass array params to it like
pCOL := 'emplid','empname','dept'
pRELATION := '=','=','is'
pVALUE := '2030','SMITH','NULL'

and it should return:
emplid = '2030' and empname = 'SMITH'
AND dept is null

I am using dbms_context to bind values for pVALUE.
It all works fine for all the cases..except for 
the third value:
dept is null.

The procedure does produce the string the way it supposed to..and I check the values with in 
sys_context..it is 'NULL'

but when I open the dynamic query..I get this error:
ORA-00908: missing NULL keyword

Is it because it's a key word and only values can be bounded...Please help..

Thanks
 


Followup   June 25, 2003 - 8pm Central time zone:

you'll loop over p_col right


  for i in 1 ... p_col.count
  loop
     l_where := l_where || ' and ' || pCol(i) || ' ' || pRelation(i);
     if ( pValue(i) is null )
     then
         l_where := l_where || ' null';
     else
         dbms_session.set_context( .... );
         l_where := l_where || '  sys_context( ''ctx'', .....
     end if;
  end loop


and set:

   pValue := '2030', 'SMITH', null


(or check for the STRING null).... Additionally - you need a 4th array -- DATATYPE of S, N, D 
(string number date) so you know when to wrap the sys_context call by to_number and to_date! 

5 stars How to use session context in mod_plsql   July 1, 2003 - 5pm Central time zone
Reviewer: A reader from CA, USA
Tom,

If I use mod_pl/sql and session is stateless, how does the session context work? since session is 
changing from page to page. 


Followup   July 1, 2003 - 7pm Central time zone:

well, in this example, it didn't matter that is was stateless.  we used the context to "bind" with.

see
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:5669294472933
for "global application contexts" which can persist over connections. 

5 stars Maharaj   July 1, 2003 - 11pm Central time zone
Reviewer: Maharaj ki Jai Ho 
CREATE OR REPLACE FUNCTION test(loan NUMBER,loantype VARCHAR2)
 RETURN VARCHAR2
 IS
 CURSOR loanc(Cloan NUMBER) IS
 SELECT loan_total,loan_interest,loan_category
 FROM loans
 WHERE loan_KEY=cloan;

 fun_return VARCHAR2(1) := 'Y';
 BEGIN
 IF loantype='interim'  THEN
    FOR REC IN loanc(loan) LOOP
           IF REC.loan_total=0 OR (REC.loan_interest=0 AND loan_category <> 'I')
        THEN fun_return:='N';
        EXIT;
        END IF;
     
    END LOOP;
  END IF;
   
 
  RETURN fun_return
  END;

Tom, my requirment is that , just like that above function, I will have to do 6 more functions 
doing the same validation
but against different loan categories , and I will have to hit their respective tables. 

One straight forward way is to declare 6 cursors, and repeat the above for all the 6 scenarios i.e. 
for all the 6 loan categories.

But I want to know if I can write a generic routine , which can account for a single function 
taking care of all the 6 scenarios.

The function obvious should accept 6 different table names, one at a given time, and also different 
column names.

The number of columns might also differ as in two cases we will be selecting only the first two 
columns, in other cases there will be 3.

Can you suggest the most correct method to write this dynamic sql , which also uses bind variables.


Thanks

Maharaj 


Followup   July 2, 2003 - 8am Central time zone:

You "could", but you "shouldn't"

static sql is better then dynamic sql -- more performant, easier to debug/maintain.  6 is a small 
number.

code should just be a single SELECT INTO, no procedural code needed or desired:

ops$tkyte@ORA920> create table loans ( loan_key int, loan_total int, loan_interest int, 
loan_category char(1) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> variable loan number
ops$tkyte@ORA920> exec :loan := 55;

PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select decode( count(*), 1, 'N', 'Y' )
  2    from dual
  3   where exists ( select null
  4                    from loans
  5                   where loan_key = :loan
  6                     and ( loan_total = 0
  7                           or
  8                           (loan_interest = 0 and loan_category <> 'I')
  9                         )
 10                )
 11  /

D
-
Y

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into loans values ( 55, 0, 0, 'x' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select decode( count(*), 1, 'N', 'Y' )
  2    from dual
  3   where exists ( select null
  4                    from loans
  5                   where loan_key = :loan
  6                     and ( loan_total = 0
  7                           or
  8                           (loan_interest = 0 and loan_category <> 'I')
  9                         )
 10                )
 11  /

D
-
N


 

4 stars dynamic columns   July 29, 2003 - 9am Central time zone
Reviewer: ramesh from Sweden
Tom,

This is in response to your suggestion about using DBMS_SQL for the situation where different 
number of columns are to be selected at different times.

My question is when i dont know what columns i use until runtime how can i do 
DBMS.define_column(....) !!!

My actual need is like this.

We have a table with coulmns for each month from 2001 year onwards.i.e like c_200001,c_200002 .. 
c_200306
Every month we add a new column in a table and ppopulate values into column from c_200001 to the 
latest added column.
I have a script to generate SQL .. but i got stuck at taking the values into variables and then use 
them to update the table columns.
TIA
Rd 


Followup   July 29, 2003 - 11am Central time zone:

dbms_sql.describe_columns tells you how many and what types they are.

this does sound like "a bad design", seems to me you want to add a ROW per month, not a col 

4 stars dynamic columns   July 30, 2003 - 9am Central time zone
Reviewer: ramesh from sweden
Hi Tom,

Thanks for the followup.

You may be right .. it may be a bad design..
But what i asked was about columns not rows.

Please suggest me a better way..

I am pasting some parts of the code ..
--** code starts
....
type mthstab is table of varchar2(7) index by binary_integer;
mths mthstab;
....
....
mths(1):='jan'; mths(7):='jul'; 
mths(2):='feb'; mths(8):='aug'; 
mths(3):='mar'; mths(9):='sep'; 
mths(4):='apr'; mths(10):='oct';
mths(5):='may'; mths(11):='nov';
mths(6):='jun'; mths(12):='dec';

    vSql:='select acct_num ';
    for y in 2000 .. to_char(add_months(sysdate,-1),'yyyymm')
    loop
        for m in 1 .. 12
        loop
              vSql:=vSql||',sum(case when period='||y||lpad(m,2,'0')||' 
then balance else 0 end) balance'||'_'||mths(m)||'_'||substr(y,3,2);
        end loop;
    end loop;
    vSql:=vSql||' from accounts where acct_num=sys_context(''ACC_CTX'',''ACC'') and period between 
''200001'' and '||vper||' group by acct_num';
...
...
--** code ends

The above loop creates an SQL as follows..

select acct_num 
       ,sum(case when period=200001 then balance else 0 end) balance_jan_00
...
...       
       ,sum(case when period=200001 then balance else 0 end) balance_jul_03
from accounts 
where acct_num=sys_context('ACC_CTX','ACC') 
and period between '200001' and '200306' group by acct_num;

-- My source table (accounts) has records for each month ..
-- My target table has column for each month
-- My objective is to take this growing records of source table to growing columns of target table.


Kindly suggest something.

Thanks
Rd 


Followup   July 30, 2003 - 10am Central time zone:

the better way is what I did suggest -- that you fix the model.


else you are writing dynamic sql every single time.  nothing I can do to make that easy, efficient, 
fast, sleek, or correct.

it looks like you have the data correctly stored and are being forced to pivot it.

you've done all that you can do.
 

4 stars dynamic columns   July 30, 2003 - 10am Central time zone
Reviewer: ramesh from sweden
Hi ,

Thanks again ..
There is nothing i can do with the model.
We have gotto feed a SAS system and they need columns growing.

This loading will be monthly run .. and it would be a couple of thousand records a month to 
process.

If you say its worst possible approach i look for a totally different approach. Or you may suggect 
me how i can reacd the columns returned by Cursor in to array or whatever so that i can update the 
target table.

I will have to use another dymanic SQL ( in the same manner i did) for updation also for which i 
dont think i can do otherwise.

Thanks
Rd
 


Followup   July 30, 2003 - 10am Central time zone:

what you are doing -- with the pivot query -- is the approach to take to pivot data like that.

a couple thousand records won't be very hard to do. 

5 stars Session contexts with shared connections   August 22, 2003 - 1pm Central time zone
Reviewer: Steve from UK
Tom
I have written a procedure to build a dynamic statement using the session context method described 
here which works a treat. I am just a bit concerned how this works when the connections are shared 
for multiple web users. Should I be concerned about conexts from a previous user persisting in a 
session? It is not possible to clear the contexts after execution of the procedure since the 
ref_cursor returned to the calling app, will then return no rows. The alternative is to clear the 
contexts at the start of each execution but is it worth the overhead? Any contexts used in the new 
query will overwrite the old anyway? Your advice will be much appreciated. 


Followup   August 22, 2003 - 8pm Central time zone:

context is uga memory - migrates with the session when using shared server.

if you are using a connection pool -- you need to clear/set the context after a "grab connection" 
call. 

4 stars Which is more faster   August 25, 2003 - 4am Central time zone
Reviewer: Mohan K from Bangalore, India
I am developing an application package in PL/SQL where I have two choices. One to write static sql 
statements and the other to develop generic package where the tablename and the column name is 
passed dynamically by the calling application. My question is for a given query on a table which is 
faster, static SQL or Dynamic SQL. 

If the query is static SQL the everything can be written in one SQL statement. If it is dynamic SQL 
then there will be a sequence of procedure calls in a loop. Is there any way to make the 
performance of both mwthods same.

Mohan
 


Followup   August 25, 2003 - 6am Central time zone:

static sql wins hands down for so so so many reasons.

performance being one.  
maintainence another.
dependency tracking a third.
"bug free code" for a fourth.

and so on.  

use dynamic sql if and only if a static sql approach is not in the realm of possible.

the only way to make a dynamic sql implementation of a routine that could be done via static sql 
perform as well would be to recode it using static sql :) 

5 stars Hi Tom   August 25, 2003 - 10pm Central time zone
Reviewer: A reader 
Can you please explain?
"performance being one.  
maintainence another.
dependency tracking a third.
"bug free code" for a fourth.
"

I know performance is better in static?

But I don't understand the two points
  maintenance
  dependency tracking

Could you explain these a little more?

Thanx! 


5 stars   September 4, 2003 - 11am Central time zone
Reviewer: A reader 


5 stars 9.2.0.4 and sys_context   September 21, 2003 - 12pm Central time zone
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom
We are using sys_context on data from remote tables. Quoting from your example:

 begin
 16  
 17      if ( p_ename is NOT NULL ) then
 18          dbms_session.set_context( 'MY_CTX', 'ENAME',
 19                                    '%'||upper(p_ename)||'%');
 20          l_query := l_query ||
 21               ' and ename like
 22                 sys_context( ''MY_CTX'', ''ENAME'' ) ';
 23      end if;

The context is set on the local database but the query refers to tables and columns on the remote 
database through a database link. Till 9.2.0.3, Oracle was fetching all records from remote table 
and filtering them on the local database as seen in the explain plan. Since the context namespace 
was declared on the local database, the query was giving correct results. 

After applying the 9.2.0.4 patch, the optimizer started sending the entire where clause including 
sys_context to the remote database. Since the context namespace does not exist on the remote 
database, the where clause never finds any matching data and query fails to return any results.

Any suggestions? 

Thanks... 
 


Followup   September 21, 2003 - 1pm Central time zone:

use the driving site hint as a workaround, or  you can try the alternative suggested above of:

...
Can't you achieve the same (without using sys_context), creating dummy WHERE 
condition if passed parameter has null value. So, you will always use the same 
number of bind variables in USING clause. 
      if ( p_ename is NOT NULL ) then
           l_query := l_query ||
                ' and ename like  ''%''||:p_ename||''%''';
       else
           l_query := l_query ||
                ' and 1 = DECODE(:p_ename,NULL,1,1)';
       end if;

............


and then open the cursor with a fixed number of bind inputs. 

5 stars   September 21, 2003 - 10pm Central time zone
Reviewer: Arun Gupta from Harrisburg, PA USA
The driving_site hint is also sending the entire query including the where clause to the remote 
database.

The other alternative won't work because of the number of  parameters vary from all possible 
combinations of 0 to 9. This was the reason we used sys_context in the first place.

The only workaround which seems to work is to replace all occurrences of sys_context() with select 
sys_context() from dual. Is there any downside to using this ?

This behavior gives rise to another question. When does Oracle populate bind variables in queries 
issued against a remote database? The bind variable values are user session specific. Thus Oracle 
should populate bind variables before sending queries to the remote database. If sys_context is 
treated as a bind variable, shouldn't Oracle evaluate it's value before sending to remote database?

Thanks... 


Followup   September 22, 2003 - 7am Central time zone:

the other altnative DOES work -- you would always have a fixed number of inputs - it would work 
100% (it has to, else sys_context would not either!)

selecting from dual works nicely.

If the query is optimized on the remote site, thats where it'll take place with sys_context.  
sys_context is "like" a bind, it is not a "bind" 

3 stars Why using sys_context for dynamic sql is slower?   October 1, 2003 - 3am Central time zone
Reviewer: Jap Boon Churn from Malaysia
Hi Tom,

I was very happy to found this article yesterday and hoping i can apply and use this new technique 
(sys_context) for my query tuning activity currently.

However, it does not turn out to be satisfying.
I created 2 procedures, exactly the same, except one uses sys_context to assign the parameter value 
to dynamic sql statement, the other one just put in literal value into the query. Both using oracle 
text. My database is 8.1.6.

The one not using sys_context finishes query in 1 or 2 seconds for 90 records.

The one using sys_context finishes query in 9 or 10 seconds for 90 records as well.

I feel surprise to see this, cos that's a great difference.

Therefore, would appreciate if you can provide some advice for me, maybe there are some other areas 
that i miss out.

Would appreciate your followup.
Thanks. 


Followup   October 1, 2003 - 8am Central time zone:

tkprof will show us what the issue is.  lets see the tkprof of the two queries. 

5 stars 9.2.0.4 and sys_context   October 10, 2003 - 1pm Central time zone
Reviewer: Arun Gupta from Harrisburg, PA USA
Tom,
Please see my post above about 9.2.0.4 sending the sys_context to remote database. I opened a TAR 
with support and development agreed that this was a bug. This will be fixed in the next patchset.
Thanks. 


4 stars   October 29, 2003 - 10am Central time zone
Reviewer: A reader 
ops$tkyte@ORA920> select decode( count(*), 1, 'N', 'Y' )
  2    from dual
  3   where exists ( select null
  4                    from loans
  5                   where loan_key = :loan
  6                     and ( loan_total = 0
  7                           or
  8                           (loan_interest = 0 and loan_category <> 'I')
  9                         )
 10                )
 11  /


If we select the above selected value into a variable like the below, then do we need an exception 
section to handle the no_data_found exception...

ops$tkyte@ORA920> select decode( count(*), 1, 'N', 'Y' )
                          into lv_variable
  2    from dual
  3   where exists ( select null
  4                    from loans
  5                   where loan_key = :loan
  6                     and ( loan_total = 0
  7                           or
  8                           (loan_interest = 0 and loan_category <> 'I')
  9                         )
 10                )
 11  /
 


Followup   October 29, 2003 - 11am Central time zone:

an aggregate without a group by ALWAYS returns at least one row - and at most one row.

No exception handling needed -- you'll never get too_many_rows nor no_data_found.


ops$tkyte@ORA920LAP> select count(*) from dual where 1=0;

  COUNT(*)
----------
         0

1 row selected.

ops$tkyte@ORA920LAP> select max(1) from dual where 1=0;

    MAX(1)
----------


1 row selected.
 

5 stars cursor count   December 3, 2003 - 4pm Central time zone
Reviewer: mo 
Tom:

AS a followup to how to count records and print total in an implicit cursor you said to use rownum 
or count them.

For x in (select * from emp where soundex(last_name)= soundex(i_last_name) )
LOOP

END LOOP;

IF I count in the loop then I would not be able to print the total before the result set like 
"Total Matches: 6" and then print them.

I do not see how rownum will five me the total.

Do I have t orun a separate count before I print the result set?

Thank you 


Followup   December 3, 2003 - 6pm Central time zone:

don't print the total "first" would be *my* answer

otherwise, you have to fetch all of the rows, and THEN print them.

if the result set is "small" enough (less then hundreds) then


select * BULK COLLECT into ...
  from emp 
  where ....;


will work -- you'll fill up array(s) of data, which you can use the .count attribute on to see "how 
many" and then print. 

5 stars count   December 4, 2003 - 10am Central time zone
Reviewer: mo 
Tom:

Is this "BULK COLLECT" new feature in 9i?

Do you mean to use it instead of using implicit cursor since it allows me to do counts?

Thank you, 


Followup   December 4, 2003 - 11am Central time zone:

no, it was in 8i as well.


i'm saying "don't use it, put the count at the bottom (bottom, top, side, -- who cares really)"

but if you feel compelled to print it
and the result set is smallish (hundreds at most)
bulk collect it and you can use the .count attribute on the arrays to see "how many there are"


ops$tkyte@ORA815> declare
  2      type array is table of varchar2(2000) index by binary_integer;
  3
  4      l_ename array;
  5      l_empno array;
  6  begin
  7      select ename, empno bulk collect into l_ename, l_empno
  8        from scott.emp;
  9
 10      dbms_output.put_line( l_ename.count || ' rows selected' );
 11
 12      for i in 1 .. l_ename.count
 13      loop
 14          dbms_output.put_line( l_ename(i) || ',' || l_empno(i) );
 15      end loop;
 16  end;
 17  /
16 rows selected
A,7369
ALLEN,7499
WARD,7521
JONES,7566
MARTIN,7654
BLAKE,7698
CLARK,7782
SCOTT,7788
KING,7839
TURNER,7844
ADAMS,7876
JAMES,7900
FORD,7902
MILLER,7934
ant,999
ant,9999
 
PL/SQL procedure successfully completed.
 

5 stars set context   December 15, 2003 - 9am Central time zone
Reviewer: mo 
Tom:

I am trying to build a ref cursor as this.  Either fo a soundex search on last name, or do a last 
name character search or both.

  dbms_session.set_context('LN_CTX','LAST_NAME',i_last_name);

    IF (i_soundex_search = 'Y' and i_lname_search is null ) THEN
       open l_cursor for select * from applicant where soundex(last_name) = soundex(i_last_name);
    elsif ( i_soundex_search is null and i_lname_search = 'Y' ) THEN
       open l_cursor for select * from applicant where last_name like 
sys_context('LN_CTX','LAST_NAME');
    elsif ( i_soundex_search is null and i_lname_search = 'Y' ) THEN
         open l_cursor for select * applicant where soundex(last_name) = soundex(i_last_name)
            or last_name like sys_context('LN_CTX','LAST_NAME');
    END IF;

1.  I keep getting an error:
Mon, 15 Dec 2003 14:37:52 GMT

ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 78
ORA-06512: at "IDEV.DDS_APPLICANT", line 1246
ORA-06512: at line 10

DBA says he gave access to IDEV on DBMS_SESSION and when I log in as IDEV and do desc dbms_session 
i get results.

This is a 9i database.  Do you know what is the problem?

2.  When you build a query and want to use "like" it seems you do not have to use "%" and the bind 
does it automatically? Is this correct.

3. Is these any other way to build ref cursor and bind variables without dbms_session.

Thank you, 


Followup   December 15, 2003 - 10am Central time zone:

1) A context is created bound to a package or procedure:

create or replace context LN_CTX using <procedure>;

ONLY that procedure is allowed to set it, this error indicates you are not setting it in the 
procedure it was defined with.  It is not anything to do with dbms_session -- you are "privileged" 
to run dbms_session (the error is INSIDE of dbms_session -- if you weren't allowed to run it, the 
error wouldn't be INSIDE of it!)

2) that is not correct.  if you want a % you better add a %, else there will be no %

3) look at Darko Egersdorfer's comment above. 

4 stars set context   December 15, 2003 - 11am Central time zone
Reviewer: mo 
Tom:

I do login to sql*plus using IDEV account and run

SQL> create or replace context ln_ctx using dss_applicant.find_applicant
/
Context Created.

The name of my package is "DSS_APPLICANT".

The name of my procedure is "FIND_APPLICANT".

I still get the error.

Mon, 15 Dec 2003 16:27:36 GMT

ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 78
ORA-06512: at "IDEV.DSS_APPLICANT", line 1246
ORA-06512: at line 10

2.  How do you insert the "%" with the bind variable.

do you do like this:

open l_cursor for select * from emp where lname like :last_name ||'%'

Thank you, 


Followup   December 15, 2003 - 3pm Central time zone:

that is wrong.  the "using" is either the 

a) name of a package
b) name of a procedure (standalone)

you've just create a context that can be modified by a package (any function or procedure in that 
package) or standalone procedure in the schema DSS_APPLICANT

I thing you just want to say "using dss_applicant" 

5 stars ref cursor   December 17, 2003 - 11am Central time zone
Reviewer: mo 
Tom:

You are right. Context should be created using package.

1. If I want to use an index in my search for this cursor Would I just create a function based 
index on soundex(Last_name).  How about the 2nd and 3rd if clause when I want to search by either 
soundex or like last name.

 dbms_session.set_context('LN_CTX','LAST_NAME',lower(i_last_name));
  IF (i_soundex_search = 'Y' and i_lname_search is null ) THEN
       open l_cursor for select * from applicant where soundex(last_name) = soundex(i_last_name);
    elsif ( i_soundex_search is null and i_lname_search = 'Y' ) THEN
      open l_cursor for select * from applicant where lower(last_name) like 
sys_context('LN_CTX','LAST_NAME')||'%';
    elsif ( i_soundex_search ='Y' and i_lname_search = 'Y' ) THEN
       open l_cursor for select * from applicant where soundex(last_name) = soundex(i_last_name) or
              lower(last_name) like sys_context('LN_CTX','LAST_NAME')||'%';
    END IF;

 


Followup   December 18, 2003 - 8am Central time zone:

looks like you really might want a TEXT index on this (support the like, keyword matches, soundex, 
fuzzy searches, etc)

but -- yes, a b*tree index on soundex would let the first query use an index 

a b*tree index on lower(last_name) would let the second

and the third, could, if appropriate, use both. 

4 stars   December 29, 2003 - 5am Central time zone
Reviewer: reader from India
Presently we have code which uses cursor as follows

procedure sample
declare
cursor c1 is select ....
begin
for rs in c1
loop
 processing;
end loop;
end;

now the query for cursor has to be dynamic, the values in list will be selected from a table and 
the query string be formed so I am thinking of this way 
just an example

declare
lemp emp.empno%type;
type emprec is ref cursor;
c1 emprec;
lstring varchar2(200) := 'select * from emp where empno in';
begin
lstring := lstring || '(999,888)';
open c1 for lstring;
loop
fetch c1 into lemp;
dbms_output.put_line(lemp);
exit when c1%notfound;
end loop;
close c1;
end;


I would like to have your suggestion on this is this ok to go with or should I use dbms_sql. Please 
suggest.

Thanking you as always for all your help.

Best Regards. 


Followup   December 29, 2003 - 10am Central time zone:

USE BIND VARIABLES

do not, repeat, do not even consider this approach.

either

a) search this site for 

varying in list

to see a technique that'll let you bind a comma delimited list

b) use a global temporary table, insert the values from the list into the gtt and then query using 
"where empno in (select * from gtt)"


In short -- stick with STATIC SQL, use BIND VARIABLES. 

4 stars Thanks Tom for your help   December 30, 2003 - 5am Central time zone
Reviewer: reader from India
Hi Tom,
      Good day to you, well learning from you by your site and book I can't make the mistake of 
ignoring bind variables, but I didn't gave proper example sorry for that. I am a little confused 
with your last sentence "In short -- stick with STATIC SQL, use BIND VARIABLES" if I am getting it 
right do you mean the cursor definition should be like this 
Cursor c1 is select * from emp where empno in (select empno from tempemp), if I am wrong please do 
correct me, also this is just one condition there will be other conditions too depending on the 
values in a table those have to be incorporated in the query so in this case how should it be done.

Thanks again for your time and help.

Best Regards.
 


Followup   December 30, 2003 - 10am Central time zone:

that is great

select * 
  from emp
 where empno in ( select empno from gtt );


if you have to dynamically build the predicate, then yes, you will have to use dynamimc sql -- see 
the technique outlined in the original answer above.

 

5 stars   January 22, 2004 - 11am Central time zone
Reviewer: Valar from Littleton,MA
Hi Tom, Thanks for the simple and precise example of using an application context and sys_context 
functions. Sounds like a good use when the predicate clause is dynamic. I am trying to use this 
approach for one of my problems. My question here is, would you use the same method to build the 
predicate clause, if there are 40 input variables to the procedure. 

My procedure would be something like the following,

create or replace context dem_ctx using sp_getqueryresults;

create or replace procedure sp_getqueryresults
(p_lastname    dem.lastname%type default null,            -- Search condition1
 p_firstname    dem.firstname%type default null,
 p_dob        dem.dob%type       default null,
 p_height    dem.height%type       default null,
 p_eyecolor    dem.eyecolor%type  default null,
 p_haircolor    dem.haircolor%type default null,
 ...
 ...
 p_address1    dem.address1%type  default null        -- Search condition 40

)
is
type rc is REF CURSOR;
 
t_demcursor rc;
t_query        varchar2(5000)
        default 'select a.demid a.lastname, a.firstname, a.dob, a.createdate from dem a where 1=1';

cursor t_template is select * from dem;
t_demrec  t_template%rowtype;

begin
    -- Check for lastname
    if p_lastname is not null then
        dbms_session.set_context( 'DEM_CTX', 'LASTNAME','%'||upper(p_lastname)||'%');
              t_query := t_query ||    ' and lastname like sys_context( ''DEM_CTX'', ''LASTNAME'' ) 
';
           dbms_output.put_line('Inside lastname');
     end if;
 
    -- Check for firstname
    if p_firstname is not null then
         dbms_session.set_context( 'DEM_CTX', 'FIRSTNAME','%'||upper(p_firstname)||'%');
         t_query := t_query || ' and firstname like sys_context( ''DEM_CTX'', ''FIRSTNAME'' ) ';
         dbms_output.put_line('Inside firstname');
        end if;
        
         
    /* Code for search criteria 3 upto search criteria 39

    ...
    ...
    */

    -- Check for address1
    if p_address1 is not null then
         dbms_session.set_context( 'DEM_CTX', 'ADDRESS1',p_address1);                               

         t_query := t_query ||' and contains(address1, sys_context( ''DEM_CTX'', ''ADDRESS1'' )) > 
0';
         dbms_output.put_line('Inside address1');
        end if;

    p( t_query);

    open t_demcursor for t_query;
    loop
             fetch t_demcursor into t_demrec;
             exit when t_demcursor%notfound;
 
            dbms_output.put_line( t_demrec.demid || ',' ||
                                      t_demrec.lastname || ',' ||
                                   t_demrec.firstname );
    end loop;
 
    close t_demcursor; 
end;
/


Looking at the above code, you would find that for the different search criteria, we have to code 
atleast 40 "if-end if " blocks. Could you please suggest ways to refine/minimise the above code ? 
My search conditions involve number, date parameters, textdata (using contains) and also inlist 
type of variables.

Thanks,
Valar 


Followup   January 22, 2004 - 7pm Central time zone:

depends - here I might - MIGHT -- consider a text index and index an XML representation of that row 
and use "contains" with section searching as I doubt you'd index everything otherwise. 

5 stars   January 26, 2004 - 5pm Central time zone
Reviewer: Valar from Littleton, MA
Thanks for the last follow-up. You are right that almost all search fields are indexed. We are 
looking at atleast 35 individual indexes or so. I was further reading on to see what the options of 
storing in xml are and how section indexing worked. It's obvious if we go for such an option, we 
would have to store the demographic data pertaining to each row in a seperate column in the dem 
table. Is that right ?  (I think the answer would be 'Yes', as the data is currently stored in 
seperate columns in the dem table.)

Before going more into that, I would like to know, what might influence your decision to go for an 
xml representation with section searching ? Is it the volume of data in the dem table or is it the 
type of transactions involved in the dem table or Is it the required performance of search query ?

Currently this search procedure sp_getqueryresults is intended to provide core functionality for 
one of our searching Components which should serve a wide range of customers. The demographic 
records to search vary by each customer. The lowest volume of records to be searched is 300,000 and 
the highest is 15 million or so. We have to come up with a possible package/procedure to serve the 
varied customer base and provide efficient searching. The type of transactions involved are mostly 
'insert-only'. Of course there are a few update transactions to delink records, which have been 
linked due to mistyped customer number/ssn and so forth.  Should we plan such that we need to come 
up with different search routine packages/procedures depending upon the volume of data or so ?  Any 
other suggestions/pointers ? Please let us know.
Thanks,
Valar
 


Followup   January 26, 2004 - 5pm Central time zone:

it would be all about retrieval.  You want to search on N of M fields where N is always changing.  
I'd like to have a single index.  

sp_, hmmm, a sqlserver stored procedure in disguise ;)

I would use the technique of indexing a function -- store the data relationally, but index an xml 
representation of it (very close to what I do on asktom here.  I index "web pages" but the data is 
stored in 3 tables with master /detail /detail relationships.  I index a "function" that returns 
the formatted page so you think you are searching pages -- but you are really searching many rows 
across many tables)

You would use the same technique for 1 row, or 15 billion rows. 

5 stars   January 27, 2004 - 9am Central time zone
Reviewer: Valar from Littleton, MA
Thanks for pointing towards the technique of indexing a function and probably using a single index 
rather than N number of indexes. Shall look further into this approach and apply to this problem.

Regards usage of sp_, it so happened that in the place where I work, the usual convention for 
referencing a stored procedure is sp_ and for packages it is pack_. Here we do projects both in SQL 
Server and Oracle, so it's been like that.

 
Thanks again !!
Valar 


5 stars   January 28, 2004 - 2pm Central time zone
Reviewer: Valar from Littleton, MA
Tom,

Based on your follow-up, I tried to generate an xml representation of a simplistic dem table. 
Attached below are the sql outputs for the same. The create context index scripts was throwing up 
an error and I wasn't sure, if I was on the right path. Can you please suggest on this ? Thanks,
Valar
CTDLDBO@CTDL:CTTESTCIS> create table dem
  2  ( demid  number,
  3   lastname varchar(25),
  4   firstname varchar(10),
  5   address1 varchar(30)
  6  )
  7  tablespace data;

Table created.

CTDLDBO@CTDL:CTTESTCIS> 
CTDLDBO@CTDL:CTTESTCIS> 
CTDLDBO@CTDL:CTTESTCIS> insert into dem
  2  values (1,'MCDERMOTT', 'JIM', '30 Boston Road');

1 row created.

CTDLDBO@CTDL:CTTESTCIS> 
CTDLDBO@CTDL:CTTESTCIS> insert into dem
  2  values (2,'G', 'VALAR', '50 Kendall Square');

1 row created.

CTDLDBO@CTDL:CTTESTCIS> 
CTDLDBO@CTDL:CTTESTCIS> insert into dem
  2  values (3,'SMITH', 'BOB', '25 Taylor Street');

1 row created.

CTDLDBO@CTDL:CTTESTCIS> commit;

Commit complete.

CTDLDBO@CTDL:CTTESTCIS> 
CTDLDBO@CTDL:CTTESTCIS> 
CTDLDBO@CTDL:CTTESTCIS> create or replace package pack_testgenxml
  2  as
  3   function fn_gendemxml return clob DETERMINISTIC;
  4  end;
  5  /

Package created.

CTDLDBO@CTDL:CTTESTCIS> 
CTDLDBO@CTDL:CTTESTCIS> 
CTDLDBO@CTDL:CTTESTCIS> create or replace package body pack_testgenxml
  2  as
  3  
  4  function fn_gendemxml return clob
  5  is
  6  tqueryctx dbms_xmlquery.ctxtype;
  7  tempdata clob;
  8  begin
  9   tqueryctx := DBMS_XMLQuery.newContext('select * from dem');
 10   tempdata := dbms_xmlquery.getxml(tqueryctx);
 11   DBMS_XMLQuery.closecontext(tqueryctx);
 12   return tempdata;
 13  end;
 14  end;
 15  /

Package body created.

CTDLDBO@CTDL:CTTESTCIS> 
CTDLDBO@CTDL:CTTESTCIS> set autoprint on
CTDLDBO@CTDL:CTTESTCIS> variable x clob;
CTDLDBO@CTDL:CTTESTCIS> exec :x := pack_testgenxml.fn_gendemxml()

PL/SQL procedure successfully completed.


X
--------------------------------------------------------------------------------
<?xml version = '1.0'?>
<ROWSET>
   <ROW num="1">
      <DEMID>1</DEMID>
      <
LASTNAME>MCDERMOTT</LASTNAME>
      <FIRSTNAME>JIM</FIRSTNAME>
      <ADDRESS1>3
0 Boston Road</ADDRESS1>
   </ROW>
   <ROW num="2">
      <DEMID>2</DEMID>

 <LASTNAME>G</LASTNAME>
      <FIRSTNAME>VALAR</FIRSTNAME>
      <ADDRESS1>50 Ke
ndall Square</ADDRESS1>
   </ROW>
   <ROW num="3">
      <DEMID>3</DEMID>

<LASTNAME>SMITH</LASTNAME>
      <FIRSTNAME>BOB</FIRSTNAME>
      <ADDRESS1>25 T
aylor Street</ADDRESS1>
   </ROW>
</ROWSET>


CTDLDBO@CTDL:CTTESTCIS> 
CTDLDBO@CTDL:CTTESTCIS> create index idx_dem_fngendemxml on dem(pack_testgenxml.fn_gendemxml())
  2  indextype is ctxsys.context;
create index idx_dem_fngendemxml on dem(pack_testgenxml.fn_gendemxml())
                                        *
ERROR at line 1:
ORA-29958: fatal error occurred in the execution of ODCIINDEXCREATE routine
ORA-29960: line 1,
DRG-11304: function-based indexes are not supported by this indextype


CTDLDBO@CTDL:CTTESTCIS> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.0.1.1.1 - Production
PL/SQL Release 9.0.1.1.1 - Production
CORE    9.0.1.1.1       Production
TNS for 32-bit Windows: Version 9.0.1.1.0 - Production
NLSRTL Version 9.0.1.1.1 - Production

CTDLDBO@CTDL:CTTESTCIS>  


Followup   January 29, 2004 - 7am Central time zone:

see

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:440419921146
that demonstrates how to index a function with TEXT.  

What I do is I have three tables:

create table parent
create table detail1;
create table detail2;

and on detail1 and detail2 I put a trigger that touches parent with an update when they are 
updated.  the index is on the parent table and it assembles (based on it's primary key) the data 
from parent and detail1/detail2 and that is what text indexes. 

5 stars Thanks   January 29, 2004 - 10pm Central time zone
Reviewer: Valar from Littleton, MA
Tom, The link to the text index creation proved to be very useful. I was able to index a text 
representation of an xml output. I like the way you give such explanatory examples and share your 
knowledge with the Oracle community.

-Valar 


5 stars bind variables??   February 20, 2004 - 7am Central time zone
Reviewer: dwl from uk
Tom

This thread has proved very interesting to me and very useful.
I understand that we need to use bind variables.  My procedure is below. As you can see
i'm using several of your techniques!
I use your str2tble function to pass in a varying array of interger to the procedure to run.

However can you just explain if/why the second version of the same procedure does not use bind 
variables? All 
the second version does is remove the sys_context call and replace it with the bind variable:



PROCEDURE P_Get_SBE_ST (    P_Return_Date     IN    SBE_TABLEA.RETURN_DATE%TYPE,
                P_Org_String    IN    VARCHAR2 DEFAULT NULL,
                P_Cur         IN OUT     C_Return_Cursor    )


IS


l_query  varchar2(2000)
default 'SELECT    :P_Return_Date,
        o.Org_Id,
        o.Organisation_Code,
        CURSOR(SELECT    RETURN_DATE,
                PAYMENT_ORG_ID,
                T1_ST_COUNT,
                T2_OO_FULL,
                T2_OO_PART,
                T2_OMP_FULL,
                T2_OMP_PART,
                T2_TOTAL_OO,
                T2_TOTAL_OMP,
                T2_TOTAL_FULL,
                T2_TOTAL_PART,
                T2_TOTAL_DOM,
                T3_OO_ALL,
                T3_OMP_ALL,
                T3_TOTAL,
                T5A_ST_COUNT,
                T5B_DOM_COUNT
            FROM    SBE_TABLEA
            WHERE    Payment_Org_ID     = o.Org_ID
            AND    Return_Date     = :P_Return_Date) AS TABLEA,
        CURSOR(SELECT    RETURN_DATE,
                PAYMENT_ORG_ID,
                REASON,
                REASON_COUNT,
                REASON_DESCRIPTION
            FROM    SBE_TABLE4
            WHERE    Payment_Org_ID     = o.Org_ID
            AND    Return_Date     = :P_Return_Date) AS TABLE4
    FROM    ORGANISATION o
    WHERE     Type_Id IN (5, 7, 10)';



BEGIN



IF ( P_Org_String IS NOT NULL ) THEN

    DBMS_SESSION.SET_CONTEXT( 'MY_CTX', 'ORG_STRING', P_Org_String);
    l_query := l_query ||'     AND    o.Org_ID IN (    SELECT *
                FROM THE ( SELECT CAST( str2tbl( sys_context( ''MY_CTX'', ''ORG_STRING'' ) ) as 
tbl_Numbers ) from dual ) )  ';

END IF;

open P_Cur for l_query USING     P_Return_Date,
                P_Return_Date, 
                P_Return_Date;



END P_Get_SBE_ST ;




The following is a copy of the above without using the context method:
(P_Org_String    just contains a string such as '1, 2, 3, 4, 5')




PROCEDURE P_Get_SBE_ST (    P_Return_Date     IN    SBE_TABLEA.RETURN_DATE%TYPE,
                P_Org_String    IN    VARCHAR2 DEFAULT NULL,
                P_Cur         IN OUT     C_Return_Cursor    )


IS


l_query  varchar2(2000)
default 'SELECT    :P_Return_Date,
        o.Org_Id,
        o.Organisation_Code,
        CURSOR(SELECT    RETURN_DATE,
                PAYMENT_ORG_ID,
                T1_ST_COUNT,
                T2_OO_FULL,
                T2_OO_PART,
                T2_OMP_FULL,
                T2_OMP_PART,
                T2_TOTAL_OO,
                T2_TOTAL_OMP,
                T2_TOTAL_FULL,
                T2_TOTAL_PART,
                T2_TOTAL_DOM,
                T3_OO_ALL,
                T3_OMP_ALL,
                T3_TOTAL,
                T5A_ST_COUNT,
                T5B_DOM_COUNT
            FROM    SBE_TABLEA
            WHERE    Payment_Org_ID     = o.Org_ID
            AND    Return_Date     = :P_Return_Date) AS TABLEA,
        CURSOR(SELECT    RETURN_DATE,
                PAYMENT_ORG_ID,
                REASON,
                REASON_COUNT,
                REASON_DESCRIPTION
            FROM    SBE_TABLE4
            WHERE    Payment_Org_ID     = o.Org_ID
            AND    Return_Date     = :P_Return_Date) AS TABLE4
    FROM    ORGANISATION o
    WHERE     Type_Id IN (5, 7, 10)';



BEGIN



IF ( P_Org_String IS NOT NULL ) THEN

    l_query := l_query ||'     AND    o.Org_ID IN (    SELECT *
                FROM THE ( SELECT CAST( str2tbl( :P_Org_String ) as tbl_Numbers ) from dual ) )  ';

END IF;

open P_Cur for l_query USING     P_Return_Date,
                P_Return_Date, 
                P_Return_Date,
                :P_Org_String;



END P_Get_SBE_ST ;




I know you cannot run these procedures standalone but i am interested in the reasons behind if/why 
method 
2 does not use the bind variable P_Org_String, since it does use the bind variable :P_Return_Date 
(or at least
i hope it does!!)  ??

Many thanks

 


Followup   February 20, 2004 - 9am Central time zone:

both use binds fully. 

3 stars Confused   February 24, 2004 - 6am Central time zone
Reviewer: dwl from uk
Ok then so if both methods use binds then i don't understand the use of sys_contexts.  I followed 
the other threads and examples from your book, and utilised (i thought) the use of sys_context in 
the same way that you did.  So why do i not need to use it here, if binds are used by my 2nd method 
what is the use of sys_context??  Why did you use it in your methods and not do what i did in my 
2nd method??

Please explain i am getting confused here! 


Followup   February 24, 2004 - 8am Central time zone:

it is a matter of compile time binding versus runtime binding.

Say you have a routine that accepts a query.  It can be ANY query on earth.  It wants to use binds, 
you want to use native dynamic sql ( execute immediate or open p_refcursor for ).

Well, you don't know at compile time whether it'll have 0, 1, 2, .... 1000 binds right -- so you 
cannot code:

  open p_cursor for l_variable using A, B, C;

cause you don't know how many A, B, C's you'll need.  so the guy that sends you the query can pass:

   select * from emp where ename like sys_context( 'my_ctx', 'ename_like' );

and would call dbms_session.set_context to put a value for ename like in there.



Now, say you have a routine that takes a query and wants to return rows N thru M.

Now, you might code:

   open p_cursor for
     'select * from ( select a.*, rownum r from ( ' || their_query || ' ) a where rownum <= :m ) 
where r >= :n' using l_max_row, l_min_row;


there, their query might use SYS_CONTEXT to bind dynamically, you use "static" compile time binds 
-- since you know how many you have.


It is just different ways to achieve the same end goal.  static, compile time binding is easier 
(less obscure) but doesn't give you the flexibility to bind 1, 2, 3 or 4 things depending on what 
you need at runtime (sys_context however does)

 

5 stars   March 5, 2004 - 8am Central time zone
Reviewer: Mihail Bratu 


4 stars thanks but....   March 9, 2004 - 4am Central time zone
Reviewer: dwl from uk
Tom 

Thanks for the reply above.  I think i understood what you meant here about compile time and 
runtime binding, until that is i read this post:

http://asktom.oracle.com/pls/ask/f?p=4950:8:929564266780274490::NO::F4950_P8_DISPLAYID,F4950_P8_CRIT
ERIA:4473634500618,
in there you advise the guy Boris that his code is not using bind variables  since he has coded 
things like:

   IF p_sname <>'' THEN
        IF p_searchname='1' THEN
            SQLQuery := SQLQuery || ' AND ACCOUNT_NAME LIKE '''|| p_sname || 
'%''';


but surely p_sname is a bind variable isn't it?  In that post you advised him to change to 
sys_context as in:


if ( p_sname is NOT NULL ) then
           IF p_searchname='1' THEN
               dbms_session.set_context( 'MY_CTX', 'ACCOUNT_NAME',
                                         ''||p_sname||'%');
               l_query := l_query ||
                    ' and ACCOUNT_NAME like
                      sys_context( ''MY_CTX'', ''ACCOUNT_NAME'' ) ';


But why? Don't both methods use binds?? I appreciate that sys_context maybe the preferred better 
method but doesn't his first way still bind ?

Thanks 


Followup   March 9, 2004 - 11am Central time zone:

No, p_sname is a variable that they were using to build a string that looked like:

  and accountname like 'SMITH%'


In PLSQL if

a) you use static sql -- eg: "for x in ( select ....", "open CURSOR_NAME" -- it is impossible to 
NOT USE binds (static sql = always bound correctly = best thing)

b) you use DYNAMIC sql -- as they were in that example -- you must be careful to bind, either using 
"execute immediate statement_in_a_string using BIND_VARIABLE...." or using that sys_context trick 

5 stars how are ref cursors implemented?   March 24, 2004 - 3pm Central time zone
Reviewer: A reader 
How is that using a ref cursor I can have a pointer
in my java client (say using JDBC for the sake of
example) to a shared pool where 
sql cursors are stored?

Can you give a feel of how this may be implemented?
what happens (step by step) when we say
resultSet.next() to move the pointer in memory?

thanx! 


Followup   March 24, 2004 - 4pm Central time zone:

ref cursors are implemented the same as any cursor.  It takes more than a couple of sentences to 
explain -- if you are really interested, I do go into this in detail in either of

Effective Oracle By Design
Beginning Oracle Programming.

but you are pointing at an execution plan in the shared pool, the data is processed by the 
dedicated or shared server (not in the 'sga').  cursors live in the session -- not the shared pool. 
 the shared pool has parsed queries and their plans.

As as for "what happens" when you got resultset.next() -- it "depends", it totally depends.  But, 
at the end of the day -- you get your row. 

5 stars How does exists work   March 25, 2004 - 7pm Central time zone
Reviewer: A reader 
SQL> ED
Wrote file afiedt.buf

  1  SELECT ENAME, EMPNO, SUM(SAL)
  2  FROM EMP
  3  WHERE EXISTS( SELECT NULL FROM DEPT
  4                   WHERE EMP.DEPTNO=DEPT.DEPTNO
  5                   AND EMP.DEPTNO=30)
  6* GROUP BY ENAME, EMPNO
SQL> /

ENAME           EMPNO   SUM(SAL)
---------- ---------- ----------
WARD             7521       1250
ALLEN            7499       1600
BLAKE            7698       2850
JAMES            7900        950
MARTIN           7654       1250
TURNER           7844       1500

In the above why is it that I'm not agetting a too many rows error.

Further in the below, I just want to check for the existenc e of deptno 30 in the emp table, but 
using exists it is returning 6 rows. I read on your web site that exists will check for the first 
instance of the existance of the row for which we are validating and it returns. In that case I 
need to return only 1 row..

Wrote file afiedt.buf

  1  SELECT 'X'
  2  FROM EMP
  3  WHERE EXISTS( SELECT NULL FROM DEPT
  4                   WHERE EMP.DEPTNO=DEPT.DEPTNO
  5*                  AND EMP.DEPTNO=30)
  6  /

'
-
X
X
X
X
X
X

6 rows selected.

Please explain.
 


Followup   March 25, 2004 - 9pm Central time zone:

where exists by its very very definition cannot get too many rows.  it looks for the first one and 
STOPS (where exists -- stop when you see something exists...)

the subquery stops -- the query itself does not (that would be a bug)

if you just want the first random row, add "and rownum = 1" 

5 stars   March 26, 2004 - 8am Central time zone
Reviewer: A reader 
If that is the case then why is the above query returning 6 rows. Shouldnt it return only one row, 
OR is it that it is checking for the existence of dept 30 once for each row returned by the outer 
query?

In the above query, if EMP has 1000 rows, and if deptno happens to be at rows 1, 100, and 500 will 
I be scanning the whole table for each row. How does it work. 


Followup   March 26, 2004 - 9am Central time zone:

the query:

select * from t where exists ( subquery )

is conceptually like this:


  for x in ( select * from t )
  loop
      IF the subquery returns at least one row
      then
            OUTPUT X
      end if
  end loop


where exists checks each row in the outer query -- just like:


select * from t where x=5;

checks each row for "x=5", each row is checked to see if a row in the subquery "exists"


where exists is really not different from "x=5" in that context.  It is just a predicate applied to 
a row. 

3 stars Cursor bug   March 26, 2004 - 4pm Central time zone
Reviewer: Fan from Germany 
Hi Tom,

Recently our database has crashed having problem with memory (Oracle v9.2). Oracle support gave us 
an answer telling that we have hit oracle bug when a query (cursor) in DB procedure accessing 
partitioned table use function new_time and cursor_sharing is enabled => raise ORA-600. 
But to me this look quite strange. To describe:
cursor_sharing is set to exact, query itself does not use function new_time (procedure does) and 
system crashes only when in query where clause we use one particular column (in that case query 
have a cost of 5000).
If we execute the same query under the same conditions but use another column (in this case query 
have cost of 500 and return the same result set) system does not crash.
In both cases query (actually cursor) use bind variables. 
I have checked shared pool in second case and query has been parsed once and executed 120 times. In 
first case is the same but only difference is cost...query takes longer to finish.
This is confusing to me. If the bug is presistent, system will chrash in both cases.
Does the query execution time play some role in this bug and could you please give me some more 
information about this oracle bug if you have?
I do not know wheter my explanation was clear, but if you need some more detailed info, I can post 
it. 


Followup   March 26, 2004 - 6pm Central time zone:

so, your database isn't crashing, a session is getting an ora-600 with cursor_sharing...


but you say you are not using that -- so obviously -- support has either

a) misdiagnosed the issue
b) failed to explain that the but can happen due to other reasons.


without having access to the actual bug number and tar related information -- i'd be hard pressed 
to really "say more" 

5 stars very informative   March 29, 2004 - 1pm Central time zone
Reviewer: Susan from Watertown, MA
Tom,

I have a situation that sounds appropriate for ref cursors but would like your thoughts on the 
suggested design.  We have certain table rows that only certain users should be able to view.  We 
access all of these rows through a view.  I've implemented a FGAC method of controlling who sees 
what.  Now, my manager wants to add a permissions flag column on the view that will contain a 
string value for all permission types (add, edit, browse, delete, settomissing).  The application 
gets the string value and provides the appropriate access.  The goal is to keep the logic of the 
permission flagging in a table (most of our developers don't know pl/sql that well) and have a 
package that can dynamically create the where statement based on the values (or lack of values) in 
the table.  The logic will involve other tables (eg. form fm01 can be browsed by (select user_id 
from users where site_id> 100), so we'll be using parameters.  We're aiming to keep it flexible so 
that a form could have multiple conditions regarding the same permission type.  I think ref cursors 
might be the approach here, but, this is a little different in that the logic is table driven.  
Thanks for any suggestions. 


Followup   March 29, 2004 - 1pm Central time zone:

well, if you are using ref cursors to build a small finite set of performant SQL queries......

instead of trying to use one huge generic "one size fits all query" that might be not so 
performant.....

based on some inputs and maybe whose logged in currently, that works for me.

 

4 stars Must create context after package is completely defined.   March 29, 2004 - 3pm Central time zone
Reviewer: Jim Nasby from Austin, TX
This just baffled me for a while... If you do something like:

CREATE PACKAGE foo AS
...

CREATE CONTEXT ctx_foo USING foo;

CREATE PACKAGE BODY foo AS
...

You will get

ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 78

If you follow the exact procedure in the docs though, which is to create the package AND package 
body, then create the context, it will work fine. 


Followup   March 29, 2004 - 4pm Central time zone:

give us a full "for example"

you can create the context and then everything else

you can create everything else and then the context

you can mix them in any order you want.....

(must have been a typo in your code I'm guessing...)



ops$tkyte@ORA9IR2> create package foo
  2  as
  3          procedure p;
  4  end;
  5  /
 
Package created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create context ctx_foo using foo
  2  /
 
Context created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create package body foo
  2  as
  3
  4  procedure p
  5  is
  6  begin
  7          dbms_session.set_context( 'ctx_foo', 'x', 55 );
  8  end;
  9
 10  end;
 11  /
 
Package body created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec foo.p
 
PL/SQL procedure successfully completed.
 

4 stars Wonder   March 30, 2004 - 5am Central time zone
Reviewer: Arangaperumal from Chennai,India
I really wonder about the techniques you are 
using.

 


3 stars dynamic where   April 14, 2004 - 3pm Central time zone
Reviewer: mary w from wash dc
Trying to do dynamic where. i pass var through java into sored proceduer and back to webpage.  but 
get error ref cursor is invalid.  

can u see if something is wrong right away?

--thanks



CREATE OR REPLACE PACKAGE  LOCATION_SEARCH


AS

TYPE CURSOR_TYPE IS REF CURSOR;

/*-----------------------------------------------------------------------------------------*/
PROCEDURE calculate_distance_by_zip
                (in_zip_code IN NUMBER,
               in_radius   IN NUMBER,
               oresult_set IN OUT CURSOR_TYPE);
/*-----------------------------------------------------------------------------------------*/
PROCEDURE get_search_results
                (in_city     IN varchar2,
               in_state    IN varchar2,
               in_zip      IN varchar2,
               in_radius   IN NUMBER,
               oresult_set IN OUT CURSOR_TYPE);

END LOCATION_SEARCH;
/

CREATE OR REPLACE PACKAGE BODY  LOCATION_SEARCH AS


/*-----------------------------------------------------------------------------------------*/
PROCEDURE calculate_distance_by_zip
                (in_zip_code IN NUMBER,
               in_radius   IN NUMBER,
               oresult_set IN OUT CURSOR_TYPE)
    IS

      BEGIN



            open oresult_set for

                 select di.DEALER_NAME, di.DEALER_ADDRESS1, di.DEALER_ADDRESS2, di.DEALER_CITY, 
di.DEALER_STATE,di.DEALER_ZIP,
                   di.DEALER_PHONE, di.DEALER_FAX, di.DEALER_URL_LINK, di.brand1, di.brand2, 
di.brand3, zd.DISTANCE
            from xmr.dealer_information di, xmr.zip_distance zd
            where dealer_zip = zd.ZIP_CODE_to
            and distance <= in_radius
            and zip_code_from = in_zip_code
            order by dealer_name, distance;



      END calculate_distance_by_zip;



/*-----------------------------------------------------------------------------------------*/

PROCEDURE get_search_results
                (in_city     IN varchar2,
               in_state    IN varchar2,
               in_zip      IN varchar2,
               in_radius   IN NUMBER,
               oresult_set IN OUT CURSOR_TYPE)
        IS
            sql_by_city_state   VARCHAR2(200) := ' ';
      BEGIN




        --search by city
        if in_city !='' and in_state = '' and in_zip= '' then
         open oresult_set for

          select di.DEALER_NAME, di.DEALER_ADDRESS1, di.DEALER_ADDRESS2, di.DEALER_CITY, 
di.DEALER_STATE,
          di.DEALER_ZIP,   di.DEALER_PHONE, di.DEALER_FAX, di.DEALER_URL_LINK, di.brand1, 
di.brand2,
          di.brand3, zd.DISTANCE from dealer_information di, zip_distance zd
          where dealer_zip = zd.ZIP_CODE_TO
          and distance <= in_radius
          and di.DEALER_CITY = in_city;

          CLOSE oresult_set;
         end if;

         --search by state
         if in_city ='' and in_state != '' and in_zip= '' then
         open oresult_set for

          select di.DEALER_NAME, di.DEALER_ADDRESS1, di.DEALER_ADDRESS2, di.DEALER_CITY, 
di.DEALER_STATE,
          di.DEALER_ZIP,   di.DEALER_PHONE, di.DEALER_FAX, di.DEALER_URL_LINK, di.brand1, 
di.brand2,
          di.brand3, zd.DISTANCE from dealer_information di, zip_distance zd
          where dealer_zip = zd.ZIP_CODE_TO
          and distance <= in_radius
          and di.DEALER_STATE = in_state;
          CLOSE oresult_set;

         end if;

         --search by city and state
          if in_city !='' and in_state != '' and in_zip= '' then
         open oresult_set for

          select di.DEALER_NAME, di.DEALER_ADDRESS1, di.DEALER_ADDRESS2, di.DEALER_CITY, 
di.DEALER_STATE,
          di.DEALER_ZIP,   di.DEALER_PHONE, di.DEALER_FAX, di.DEALER_URL_LINK, di.brand1, 
di.brand2,
          di.brand3, zd.DISTANCE from dealer_information di, zip_distance zd
          where dealer_zip = zd.ZIP_CODE_TO
          and distance <= in_radius
          and di.DEALER_STATE = in_state
          and di.DEALER_CITY = in_city;

          CLOSE oresult_set;

         end if;
        
         -- search by zip code
        if in_zip != '' then

          open oresult_set for

          select di.DEALER_NAME, di.DEALER_ADDRESS1, di.DEALER_ADDRESS2, di.DEALER_CITY, 
di.DEALER_STATE,
          di.DEALER_ZIP,   di.DEALER_PHONE, di.DEALER_FAX, di.DEALER_URL_LINK, di.brand1, 
di.brand2,
          di.brand3, zd.DISTANCE from dealer_information di, zip_distance zd
          where dealer_zip = zd.ZIP_CODE_TO
          and distance <= in_radius
          and zip_code_from = in_zip
          order by distance, dealer_name;

          CLOSE oresult_set;

        end if;




      END get_search_results;




END  LOCATION_SEARCH;
/

 


Followup   April 14, 2004 - 4pm Central time zone:

umm, how can you read from a closed cursor? 

why are you closing it -- before even having read from it?

client closes the cursor after client exhausts result set.

also, not sure if ANY of your predicates are "true"

if in_city !='' and in_state = '' and in_zip= '' then


ops$tkyte@ORA9IR2> create or replace procedure p( in_city in varchar2, in_state in varchar2, in_zip 
in varchar2 )
  2  as
  3  begin
  4          if ( in_city != '' and in_state = '' and in_zip = '' )
  5          then
  6             dbms_output.put_line( 'is true' );
  7          else
  8             dbms_output.put_line( 'is not true' );
  9          end if;
 10  end;
 11  /
 
Procedure created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec p( 'x', '', '' )
is not true
 
PL/SQL procedure successfully completed.
 

versus

ops$tkyte@ORA9IR2> create or replace procedure p( in_city in varchar2, in_state in varchar2, in_zip 
in varchar2 )
  2  as
  3  begin
  4          if ( in_city IS NOT NULL and in_state IS NULL and in_zip IS NULL )
  5          then
  6             dbms_output.put_line( 'is true' );
  7          else
  8             dbms_output.put_line( 'is not true' );
  9          end if;
 10  end;
 11  /
 
Procedure created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec p( 'x', '', '' )
is true
 
PL/SQL procedure successfully completed.
 
 

3 stars how about this way   April 14, 2004 - 5pm Central time zone
Reviewer: mary w from washington dc
i rewrote it.  but now getting another error.  (SQL not properly ended line 109) cant firgure it 
out.

seems ok to me.


CREATE OR REPLACE PACKAGE  LOCATION_SEARCH


AS

TYPE CURSOR_TYPE IS REF CURSOR;

/*-----------------------------------------------------------------------------------------*/
PROCEDURE calculate_distance_by_zip
                (in_zip_code IN NUMBER,
               in_radius   IN NUMBER,
               oresult_set IN OUT CURSOR_TYPE);
/*-----------------------------------------------------------------------------------------*/
PROCEDURE get_search_results
                (in_city     IN varchar2,
               in_state    IN varchar2,
               in_zip      IN varchar2,
               in_radius   IN NUMBER,
               oresult_set IN OUT CURSOR_TYPE);

END LOCATION_SEARCH;
/

CREATE OR REPLACE PACKAGE BODY  LOCATION_SEARCH AS


/*-----------------------------------------------------------------------------------------*/
PROCEDURE calculate_distance_by_zip
                (in_zip_code IN NUMBER,
               in_radius   IN NUMBER,
               oresult_set IN OUT CURSOR_TYPE)
    IS

      BEGIN



            open oresult_set for

                 select di.DEALER_NAME, di.DEALER_ADDRESS1, di.DEALER_ADDRESS2, di.DEALER_CITY, 
di.DEALER_STATE,di.DEALER_ZIP,
                   di.DEALER_PHONE, di.DEALER_FAX, di.DEALER_URL_LINK, di.brand1, di.brand2, 
di.brand3, zd.DISTANCE
            from xmr.dealer_information di, xmr.zip_distance zd
            where dealer_zip = zd.ZIP_CODE_to
            and distance <= in_radius
            and zip_code_from = in_zip_code
            order by dealer_name, distance;



      END calculate_distance_by_zip;



/*-----------------------------------------------------------------------------------------*/

PROCEDURE get_search_results
                (in_city     IN varchar2,
               in_state    IN varchar2,
               in_zip      IN varchar2,
               in_radius   IN NUMBER,
               oresult_set IN OUT CURSOR_TYPE)
        IS
            sql_by_city_state   VARCHAR2(400) := ' select di.DEALER_NAME, di.DEALER_ADDRESS1, 
di.DEALER_ADDRESS2, di.DEALER_CITY,
di.DEALER_STATE,di.DEALER_ZIP,   di.DEALER_PHONE, di.DEALER_FAX, di.DEALER_URL_LINK, di.brand1, 
di.brand2,
di.brand3, zd.DISTANCE from dealer_information di, zip_distance zd  where dealer_zip = 
zd.ZIP_CODE_TO
and distance <= in_radius';

            where_clause        varchar2(400) :='AND';
      BEGIN



        --search by city
        /*if in_city !='' and in_state = '' and in_zip= '' then
         open oresult_set for

          select di.DEALER_NAME, di.DEALER_ADDRESS1, di.DEALER_ADDRESS2, di.DEALER_CITY, 
di.DEALER_STATE,
          di.DEALER_ZIP,   di.DEALER_PHONE, di.DEALER_FAX, di.DEALER_URL_LINK, di.brand1, 
di.brand2,
          di.brand3, zd.DISTANCE from dealer_information di, zip_distance zd
          where dealer_zip = zd.ZIP_CODE_TO
          and distance <= in_radius
          and di.DEALER_CITY = in_city;


         end if;

         --search by state
         if in_city ='' and in_state != '' and in_zip= '' then
         open oresult_set for

          select di.DEALER_NAME, di.DEALER_ADDRESS1, di.DEALER_ADDRESS2, di.DEALER_CITY, 
di.DEALER_STATE,
          di.DEALER_ZIP,   di.DEALER_PHONE, di.DEALER_FAX, di.DEALER_URL_LINK, di.brand1, 
di.brand2,
          di.brand3, zd.DISTANCE from dealer_information di, zip_distance zd
          where dealer_zip = zd.ZIP_CODE_TO
          and distance <= in_radius
          and di.DEALER_STATE = in_state;


         end if;

         --search by city and state
          if in_city !='' and in_state != '' and in_zip= '' then
         open oresult_set for

          select di.DEALER_NAME, di.DEALER_ADDRESS1, di.DEALER_ADDRESS2, di.DEALER_CITY, 
di.DEALER_STATE,
          di.DEALER_ZIP,   di.DEALER_PHONE, di.DEALER_FAX, di.DEALER_URL_LINK, di.brand1, 
di.brand2,
          di.brand3, zd.DISTANCE from dealer_information di, zip_distance zd
          where dealer_zip = zd.ZIP_CODE_TO
          and distance <= in_radius
          and di.DEALER_STATE = in_state
          and di.DEALER_CITY = in_city;



         end if;
        */
         -- search by zip code
        --if in_zip != '' then

           if in_city is not null and in_state  is null and in_zip is  null then
                   where_clause := where_clause || '    di.DEALER_CITY = '''||in_city||'';
           elsif in_city is  null and in_state  is not null and in_zip is  null then
                   where_clause := where_clause || '    di.DEALER_STATE = '''||in_state||'';
           elsif in_city is  null and in_state  is  null and in_zip is not null then
                   where_clause := where_clause || '  zip_code_from = '''||in_zip||'';
           elsif in_city is not null and in_state  is not null and in_zip is  null then
                   where_clause := where_clause || '  di.DEALER_CITY = '''||in_city||'';
                where_clause := where_clause || '  di.DEALER_STATE = '''||in_state||'';
           end if;


          open oresult_set for (sql_by_city_state||where_clause);

          /*select di.DEALER_NAME, di.DEALER_ADDRESS1, di.DEALER_ADDRESS2, di.DEALER_CITY, 
di.DEALER_STATE,
          di.DEALER_ZIP,   di.DEALER_PHONE, di.DEALER_FAX, di.DEALER_URL_LINK, di.brand1, 
di.brand2,
          di.brand3, zd.DISTANCE from dealer_information di, zip_distance zd
          where dealer_zip = zd.ZIP_CODE_TO
          and distance <= in_radius
          and zip_code_from = in_zip
          order by distance, dealer_name;
          */


        --end if;




      END get_search_results;




END  LOCATION_SEARCH;
/

 


Followup   April 15, 2004 - 7am Central time zone:

please re-read the above discussion where I demonstrate application contexts.

I will not help anyone fix code that doesn't use bind variables like that -- sends chills down my 
back....


(a little dbms_output.put_line and some testing in sqlplus will be most useful for you -- so you 
can actually SEE what it is you developed in that string)

but the error is obvious to me and is 100% caused by the lack of you using bind variables. You have 
an unterminated character string constant in your string. Rather then tell you how to correctly 
terminate it, i'll insist you do it right with binds!


Here is what I recommend, uncomment the OPEN (note: in_radius needs to be BOUND as i do) to get 
your refcursor.  Using P you can "debug" your code in sqlplus (make sure serveroutput is ON)


ops$tkyte@ORA9IR2> create or replace package my_pkg
  2  as
  3      type cursor_type is ref cursor;
  4
  5      PROCEDURE get_search_results
  6      (in_city     IN varchar2,
  7       in_state    IN varchar2,
  8       in_zip      IN varchar2,
  9       in_radius   IN NUMBER,
 10       oresult_set IN OUT CURSOR_TYPE);
 11  end;
 12  /
 
Package created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace context my_ctx using my_pkg
  2  /
 
Context created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace procedure p ( p_str in varchar2 )
  2  is
  3     l_str   long := p_str;
  4  begin
  5     loop
  6        exit when l_str is null;
  7        dbms_output.put_line( substr( l_str, 1, 250 ) );
  8        l_str := substr( l_str, 251 );
  9     end loop;
 10  end;
 11  /
 
Procedure created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package body my_pkg
  2  as
  3
  4  PROCEDURE get_search_results
  5  (in_city     IN varchar2,
  6   in_state    IN varchar2,
  7   in_zip      IN varchar2,
  8   in_radius   IN NUMBER,
  9   oresult_set IN OUT CURSOR_TYPE)
 10  is
 11      l_query long := 'select di.DEALER_NAME,
 12      di.DEALER_ADDRESS1, di.DEALER_ADDRESS2, di.DEALER_CITY,
 13      di.DEALER_STATE,di.DEALER_ZIP,   di.DEALER_PHONE, di.DEALER_FAX,
 14      di.DEALER_URL_LINK, di.brand1, di.brand2,
 15      di.brand3, zd.DISTANCE from dealer_information di, zip_distance zd  where
 16      dealer_zip = zd.ZIP_CODE_TO
 17      and distance <= :in_radius';
 18  begin
 19      if ( in_city is not null )
 20      then
 21          l_query := l_query || ' and di.dealer_city = sys_context( ''my_ctx'',''city'' )'; 22   
       dbms_session.set_context( 'my_ctx', 'city', in_city );
 23      end if;
 24      if ( in_state is not null )
 25      then
 26          l_query := l_query || ' and di.dealer_state = sys_context( ''my_ctx'',''state'' )';
 27          dbms_session.set_context( 'my_ctx', 'state', in_state );
 28      end if;
 29      if ( in_zip is not null )
 30      then
 31          l_query := l_query || ' and zip_code_from = sys_context( ''my_ctx'',''zip'' )';
 32          dbms_session.set_context( 'my_ctx', 'zip', in_zip );
 33      end if;
 34
 35      p(l_query);
 36      -- open oresult_set for l_query USING IN_RADIUS;
 37  end;
 38
 39
 40  end;
 41  /
 
Package body created.
 
ops$tkyte@ORA9IR2> show error
No errors.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable x refcursor
ops$tkyte@ORA9IR2> exec my_pkg.get_search_results( 'city', 'state', 'zip', 0, :x )
select di.DEALER_NAME,
    di.DEALER_ADDRESS1, di.DEALER_ADDRESS2,
di.DEALER_CITY,
    di.DEALER_STATE,di.DEALER_ZIP,   di.DEALER_PHONE,
di.DEALER_FAX,
    di.DEALER_URL_LINK, di.brand1, di.brand2,
 
di.brand3, zd.DISTANCE from dealer_information d
i, zip_distance zd  where
    dealer_zip = zd.ZIP_CODE_TO
    and
distance <= :in_radius and di.dealer_city = sys_context( 'my_ctx','city'
) and di.dealer_state = sys_context( 'my_ctx','state' ) and
zip_code_from = sys_context( 'my_ctx','zip' )
 
PL/SQL procedure successfully completed.
 

 

5 stars privileges on using context   April 15, 2004 - 9am Central time zone
Reviewer: mary w from washington dc
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 58
ORA-06512: at "XMR.LOCATION_SEARCH_V1", line 29
ORA-06512: at line 1


my pacakge has all privileges and so does my procedure.  is there anything else that needs to 
happend here?

this is how i call my package:
SQL> variable x refcursor
SQL>  exec LOCATION_SEARCH_V1.get_search_results( 'city', 'state', 'zip', 0, :x );


thanks. 


Followup   April 15, 2004 - 9am Central time zone:

you showed everything but the create context...

Unless you did this:



ops$tkyte@ORA9IR2> create or replace context my_ctx using LOCATION_SEARCH_V1
  2  /
 
Context created.


it won't work for you -- you need to bind the context to the package itself.
 

5 stars Can she do this?   April 15, 2004 - 9pm Central time zone
Reviewer: Vera from VA
Tom,
I realize that your solutions are always the best, but whatever they say women are not as smart as 
men :)so please help me understand the difference between what you suggested and the following:

IF (in_city IS NOT NULL OR in_state IS NOT NULL OR in_zip IS NOT NULL)
THEN

OPEN oresult_set FOR

select di.DEALER_NAME,
    di.DEALER_ADDRESS1, di.DEALER_ADDRESS2, di.DEALER_CITY,
     di.DEALER_STATE,di.DEALER_ZIP,   di.DEALER_PHONE, di.DEALER_FAX,
      di.DEALER_URL_LINK, di.brand1, di.brand2,
     di.brand3, zd.DISTANCE from dealer_information di, zip_distance zd  
where
     dealer_zip = zd.ZIP_CODE_TO
      and distance <= in_radius
AND ( di.dealer_city=NVL(in_city,'-') OR di.dealer_state=NVL(in_state,'-') OR 
zip_code_from=NVL(in_zip,'-'));

END IF;
--I don't know what characters are stored in their
--database, but of course NVL should return characters
--that cannot be there. And OR can be replaced with UNION 
--depending on which one is faster in their environment

Also I want to thank you for your help with one of my questions. Since I removed TRUNCATEs and 
COMMITs from loops and re-wrote everything related to loading and truncating "intermediate" tables 
two months ago, applications that took around 24 and 36 hours are now running 1.5 and 2 hours. I 
also used your advise to apply DBMS_APPLICATION_INFO to inform users on the profgess and it works 
great!
God bless you, Tom. 


Followup   April 16, 2004 - 7am Central time zone:

(spare me on the women vs men ;)  I'm not as 'right' as often at home!)

tell me if you think the optimal plans for:


where dealer_zip = zd.ZIP_CODE_TO
  and distance <= in_radius

where dealer_zip = zd.ZIP_CODE_TO
  and distance <= in_radius
  AND di.dealer_city= :x;


where dealer_zip = zd.ZIP_CODE_TO
  and distance <= in_radius
  AND di.dealer_state = :x


where dealer_zip = zd.ZIP_CODE_TO
  and distance <= in_radius
  AND zip_code = :x


where dealer_zip = zd.ZIP_CODE_TO
  and distance <= in_radius
  AND di.dealer_city= :x;
  AND di.dealer_state = :y

and so on, would be the same for each -- or might they be radically different.

this:

AND ( di.dealer_city=NVL(in_city,'-') OR di.dealer_state=NVL(in_state,'-') OR 
zip_code_from=NVL(in_zip,'-'));

would preclude indexes pretty much.


When you have a situation where you are getting a bunch of columns and constraints against those 
columns -- dynamic sql to generate a bind friendly query in a standard fashion is going to give the 
optimizer the best chance. 

5 stars I think I know the answer   April 16, 2004 - 12am Central time zone
Reviewer: Vera from VA
Tom, 
Will the answer to my question be that OR or UNION are going to be slower than 
"WHERE column_value=value_of_bind_variable"? 


Followup   April 16, 2004 - 7am Central time zone:

the functions are the primary culprit here. 

4 stars Dynamic Query   April 19, 2004 - 12pm Central time zone
Reviewer: mary W from washington dc
Thank you, Tom for all of your help so far.  I got two questions:

1.  Do I have to do' create or replace context LOC_SEARCH_CTX using LOCATION_SEARCH_V1;" everytime 
before i call my stored procedure so i can set those sessions?

i will be calling the procedure from a servlet using callable statement

2.  What is the meaning of this error:
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00201: identifier 'LOCATION_SEARCH_V1.GET_SEARCH_RESULTS' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

resulted from:  variable x refcursor
exec LOCATION_SEARCH_V1.get_search_results( '', '', 22206, 10, :x);

thanks 


Followup   April 19, 2004 - 12pm Central time zone:

1) no, it is like creating a table, you do it once.

2) desc location_search_v1 in sqlplus.  what do you see. 

4 stars Dynamic Query   April 19, 2004 - 12pm Central time zone
Reviewer: Mary W from washington dc
Of course i was on the wrong server!

Everything seems to work ok now.  But I am not seeing results not even one row....

SQL> variable x refcursor
SQL> exec LOCATION_SEARCH_V1.get_search_results( '', '', 22206, 10, :x);
select di.DEALER_NAME,
     di.DEALER_ADDRESS1, di.DEALER_ADDRESS2,
di.DEALER_CITY,
   di.DEALER_STATE,di.DEALER_ZIP,   di.DEALER_PHONE,
di.DEALER_FAX,
   di.DEALER_URL_LINK, di.brand1, di.brand2,
      di.brand3,
zd.DISTANCE from dealer_information
di, zip_distance zd
where
      dealer_zip = zd.ZIP_CODE_TO
      and distance
<= :in_radius and zip_code_from = sys_context( 'LOC_SEARCH_CTX','zip' )

PL/SQL procedure successfully completed. 


Followup   April 19, 2004 - 3pm Central time zone:

well, if you are using my package:

 34
 35      p(l_query);
 36      -- open oresult_set for l_query USING IN_RADIUS;
 37  end;
 38

the open is commented out (i don't have your tables!)

if you uncommented that, then

SQL> print x

will show you what you got (or just set autoprint on in sqlplus and it'll print all output bind 
variables)
 

5 stars Works great!   April 19, 2004 - 3pm Central time zone
Reviewer: Mary Wiest from washington dc
Thank you so much, Tom!

I will be using bind variables all the time now!

--mw 


5 stars Callable statement and bind variables   April 19, 2004 - 3pm Central time zone
Reviewer: Mary W from washington dc
One last question.

If i am calling a stored procedure from java using callable statement  will the :x mess me up?

SQL="{call xmr.LOCATION_SEARCH_V1.get_search_results(?,?,?,?,?,:?)}"; 


Followup   April 19, 2004 - 7pm Central time zone:

I would think it would -- ? is how to bind.  not sure what :? would do.

? is all you need. 

5 stars   April 27, 2004 - 9pm Central time zone
Reviewer: A reader 
Tom,

How can i track my cursor meaning, how may records the cursor has processed and how many more to go 
what is it doing other than using a counter in the fetch.

Thanks. 


4 stars Sys_context   May 5, 2004 - 4am Central time zone
Reviewer: Oleg Oleander 
I have 2 querys that use sys_context for binding variables. The only textual difference is that 
they use different attribute names in the sys_context. Will the optimizer hard parse the second 
query after the first have been executed? 


Followup   May 5, 2004 - 7am Central time zone:

yes, they are different queries.

a Trailing blank is enough to make two queries different. 

4 stars   May 12, 2004 - 10am Central time zone
Reviewer: Max from Singapore
Hi Tom,

  I would like to know is it possible to know the record count of a cursor before opening it? If 
so, how?

Regrads

Max 


Followup   May 12, 2004 - 6pm Central time zone:

no, not even oracle knowns how many rows it'll return until it actually returns it!


consider the implication of a simple:



select * from ten_billion_row_table;


what if Oracle "knew" how many rows that would return? That would imply Oracle ran the query.  That 
would be painful.  We execute the query as you fetch -- we don't pre-answer the query anywhere.  
(if you want a much more detailed discussion -- i did cover this in Effective Oracle by Design) 

4 stars still hard parsing   May 14, 2004 - 7pm Central time zone
Reviewer: Oleg Oleander from Hungary
Dear Tom,

I have a rutine that executes a grouping SQL 16 times in execute immediate to get the results. The 
SQL is the same every time, it uses sys_context to bind variables. Than I run tkprof, and see this:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       16      3.22       3.56          0        444          0           0
Execute     16      0.01       0.01          0          0          0           0
Fetch       16      4.04       5.43        241       5590          0          16
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       48      7.28       9.01        241       6034          0          16

Misses in library cache during parse: 16
Optimizer goal: CHOOSE
Parsing user id: 48     (recursive depth: 3)

Why is the query hard parsed for every single execution?
How can I get rid of those hard parses than?

Thank you for your time.
Oleg 


Followup   May 15, 2004 - 12pm Central time zone:

gotta example for me?


declare
    l_cnt number;
begin
    for i in 1 .. 16
    loop
        execute immediate 'select count(*) from dual group by dummy'
        into l_cnt;
    end loop;
end;
                                                                                                    
                  
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.01       0.00          0          3          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.01          0          3          0           1
                                                                                                    
                  
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 120
                                                                                                    
                  
Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.00          0.00
********************************************************************************
                                                                                                    
                  
select count(*)
from
 dual group by dummy
                                                                                                    
                  
                                                                                                    
                  
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse       16      0.00       0.00          0          0          0           0
Execute     16      0.00       0.00          0          0          0           0
Fetch       16      0.00       0.00          0         48          0          16
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       48      0.00       0.00          0         48          0          16
                                                                                                    
                  
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 120     (recursive depth: 1)
                                                                                                    
                  
Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT GROUP BY (cr=3 r=0 w=0 time=99 us)
      1   TABLE ACCESS FULL DUAL (cr=3 r=0 w=0 time=45 us)
                                                                                                    
                  
That is mine. 

5 stars I think I found the reason   May 17, 2004 - 5pm Central time zone
Reviewer: Oleg 
Dear Tom,

The SQL statment in question uses a temporary table that is populated and ANALYZED for every 
execution. Behind this implementation is the following reason: 
The temporary table stores 3 to 2000 rows as a result from a 3 table join and filtering conditions. 
The TMP table is then joined to a big table constaining couple of 100000 rows. The temp table has a 
2 column unique index for the join, the bigger table has also the equivalent columns indexed. An 
inline view -instead of the temp table - could not use index so the join would be slower. Does it 
make any sense? (we are about to test these scenarios) pls comment. If it does than Can we leave 
the analyze statement out?
Thank you very much
 


Followup   May 18, 2004 - 7am Central time zone:

not understanding why an index view "could not be used", it would not preclude indexes or anything. 

4 stars   May 18, 2004 - 8am Central time zone
Reviewer: Oleg from Hungary
What is an "index view"? Am I missing a feature of the server?

The temp table is the result of a cartasian join of really tiny tables (containing access rights), 
and the two column index is not present only on the temp table. Thank you 


Followup   May 18, 2004 - 3pm Central time zone:

sorry -- meant the INLINE view, don't see why that cannot be used with an index. 

4 stars DBMS_SESSION.SET_CONTEXT parameter limit   May 24, 2004 - 10am Central time zone
Reviewer: dxl from uk
Tom

I have used the DBMS_SESSION.SET_CONTEXT method that you have described at the top of this 
question.  Until now it has been working perfectly.

However since i am using a query which selects a long list of ids i have hit the limit of 256 
characters in the set_context method.  
The following script demonstates this:



drop table t1;
create table t1 (c1 number(10));

insert into t1 values (1);
insert into t1 values (2);
insert into t1 values (3);
insert into t1 values (4);
insert into t1 values (5);


select * from 
t1 where 1=1
and c1 in (1, 2, 3)
or c1 in (4,5,6);

CREATE OR REPLACE TYPE tbl_Numbers AS TABLE OF NUMBER;
/



CREATE OR REPLACE FUNCTION str2tbl( p_str in varchar2 ) 

RETURN tbl_Numbers

AS

l_str    long default p_str || ',';
l_n    number;
l_data    tbl_Numbers := tbl_Numbers();

BEGIN
    LOOP
        l_n := instr( l_str, ',' );
        exit when (nvl(l_n,0) = 0);
        l_data.extend;
        l_data( l_data.count ) := ltrim(rtrim(substr(l_str,1,l_n-1)));
        l_str := substr( l_str, l_n+1 );
    END LOOP;

RETURN l_data;

END;

/



CREATE OR REPLACE PACKAGE PKG1 AS

TYPE C_Return_Cursor IS REF CURSOR;

procedure p1 (    P_String    IN    VARCHAR2 DEFAULT NULL,
        P_Cur         IN OUT     C_Return_Cursor    );

end pkg1;
/


CREATE OR REPLACE PACKAGE BODY PKG1 AS


procedure p1 (    P_String    IN    VARCHAR2 DEFAULT NULL,
        P_Cur         IN OUT     C_Return_Cursor    )

is

l_query  varchar2(32000)
default 'SELECT    c1 from t1 where 1=1 ';


begin



DBMS_SESSION.SET_CONTEXT( 'MY_CTX', 'STRING', P_String);
l_query := l_query ||'     AND    c1 IN (    SELECT *
            FROM THE ( SELECT CAST( str2tbl( sys_context( ''MY_CTX'', ''STRING'' ) ) as tbl_Numbers 
) from dual ) )  ';




open P_Cur for l_query ;


    

end p1;


end pkg1;
/


CREATE OR REPLACE CONTEXT MY_CTX USING PKG1;

set autoprint on
variable x refcursor;
exec 
PKG1.P1('40,141,142,143,144,145,146,147,148,150,151,153,154,155,156,158,159,160,161,182,183,184,185,
186,187,188,189,190,191,192,193,194,195,196,199,200,201,202,203,204,205,206,207,208,209,210,211,213,
264,216,217,218,219,231,252,254,255,257,258,259,260,261,263,214,1', :x);

PL/SQL procedure successfully completed.


        C1
----------
         1

So now if i add another id to this list of ids to select id 2 as well it fails:

15:30:14 OPT1@OP2>exec 
PKG1.P1('40,141,142,143,144,145,146,147,148,150,151,153,154,155,156,158,159,1
60,161,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,199,200,201,202,203,204,205,206,2

07,208,209,210,211,213,264,216,217,218,219,231,252,254,255,257,258,259,260,261,263,214,1,2', :x);

PL/SQL procedure successfully completed.


        C1
----------
         1

This time i use the same list as above but shorten it by a few characters (i chopped id 40 from the 
front of the list):

15:33:05 OPT1@OP2>exec 
PKG1.P1('141,142,143,144,145,146,147,148,150,151,153,154,155,156,158,159,160,
161,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,199,200,201,202,203,204,205,206,207,

208,209,210,211,213,264,216,217,218,219,231,252,254,255,257,258,259,260,261,263,214,1,2', :x);

PL/SQL procedure successfully completed.


        C1
----------
         1
         2

So set_context only accepts the first 256 characters. How could i best get around this problem?? 

One way would be to use an OR in the select query and split the string up. 
Would this mean i need an additional application context though? eg something like

DBMS_SESSION.SET_CONTEXT( 'MY_CTX', 'STRING', SUBSTR(P_String, 1, 256));
DBMS_SESSION.SET_CONTEXT( 'MY_CTX_2', 'STRING', SUBSTR(P_String, 257));

l_query := l_query ||'     AND    c1 IN (    SELECT *
            FROM THE ( SELECT CAST( str2tbl( sys_context( ''MY_CTX'', ''STRING'' ) ) as tbl_Numbers 
) from dual ) )  

OR c1 IN     SELECT *
            FROM THE ( SELECT CAST( str2tbl( sys_context( ''MY_CTX_2'', ''STRING'' ) ) as 
tbl_Numbers ) from dual ) )';


Would this be the best way or can you think of any better method?? 

Thanks 


Followup   May 24, 2004 - 11am Central time zone:

alternative -- instead of using str2tbl, put the values in the context space itself.



      for i in 1 .. 99999
      LOOP
        l_n := instr( l_str, ',' );
        exit when (nvl(l_n,0) = 0);
        dbms_session.set_context( 'my_ctx', 'in_list_' || i, 
                                   ltrim(rtrim(substr(l_str,1,l_n-1)) );
        l_str := substr( l_str, l_n+1 );
      END LOOP;

and then use  

   in ( select to_number(value) from session_context where namespace = 'MY_CTX' and attribute like 
'IN\_LIST\_%' escape '\' )

as you "in"
 

5 stars thanks   May 24, 2004 - 11am Central time zone
Reviewer: dxl from uk
Thanks, that was very useful. 
I understand everything you did except the last bit:

   in ( select to_number(value) from session_context where namespace = 'MY_CTX' 
and attribute like 'IN\_LIST\_%' escape '\' )


why is the escape \ necessary in the when using the like function? wouldn't 

like 'IN_LIST_%' 

work too?

So is that what the call to sys_context is doing then? ie selecting from the session_Context 
table??
Does the session_context table only get cleared down after the session is terminated? 


Followup   May 24, 2004 - 1pm Central time zone:

"_" is a wildcard as well.  it would "work", but it would match inxlisty as well as in_list_


sys_context is accessing the same information presented in this view.

session_context contains the set of values you have set in your session, they stay there until the 
corresponding sys_context retrievable values go away.


 

4 stars scope of application contect   June 14, 2004 - 3pm Central time zone
Reviewer: A reader from sanfrancisco
Is the scope application wise or within the procedure?
 
eg:
9          dbms_session.set_context( 'MY_CTX', 'ENAME', upper(p_ename) );
10          l_query := l_query ||   ' and ename like  sys_context( ''MY_CTX'', ''ENAME'' ) ';

 
suppose the procedure is executed with p_ename value as "ABC" and line 9 is completed.
another user may run the same procedure with a different value like " XYZ" and line 9 is completed.
now when line 10 is being executed , for the first user, will the value be "XYZ" or "ABC"?
 
Hope you understood this. if not let me know so that i can explain clearly.
 
Thanks,
 


Followup   June 15, 2004 - 8am Central time zone:

either

a) session -- your SESSION sees the values
b) as long as you like -- global application contexts can persist over/across sessions.

by default, the context is scoped at the session.


since each user would have their own session -- or in a connection pool use the session "one after 
the other", your concern is not a problem.  they will not see eachothers context values. 

5 stars dynamically reference value from cursor   June 16, 2004 - 9am Central time zone
Reviewer: A reader 
Given the following scenario:

SQL> set serveroutput on
SQL> drop table emp;

Table dropped.

SQL> 
SQL> drop table t;

Table dropped.

SQL> 
SQL> create table emp
  2  ( first_name varchar2(30),
  3    last_name varchar2(30)
  4  );

Table created.

SQL> 
SQL> insert into emp
  2  values('JOHN','SMITH');

1 row created.

SQL> 
SQL> insert into emp
  2  values('JANE','JONES');

1 row created.

SQL> 
SQL> create table t
  2  ( 
  3    name varchar2(10),
  4    cursor_column varchar2(30)
  5  );

Table created.

SQL> 
SQL> insert into t
  2  values ('TEST1','emp_cur.last_name');

1 row created.

SQL> 
SQL> insert into t
  2  values ('TEST2','emp_cur.first_name');

1 row created.

SQL> 
SQL> commit;

Commit complete.

SQL> 
SQL> begin
  2   for emp_cur in (select * from emp)
  3   loop
  4     for t_cur in (select * from t where name='TEST1')
  5     loop
  6     dbms_output.put_line(t_cur.cursor_column);
  7     dbms_output.put_line(' I really wanted to output '||emp_cur.last_name||' but I dont know 
how
 to dynamically reference the cursur value');
  8     end loop;
  9   end loop;
 10  end;
 11  /
emp_cur.last_name
I really wanted to output SMITH but I dont know how to dynamically reference the
cursur value
emp_cur.last_name
I really wanted to output JONES but I dont know how to dynamically reference the
cursur value

PL/SQL procedure successfully completed.

SQL> 
SQL> begin
  2   for emp_cur in (select * from emp)
  3   loop
  4     for t_cur in (select * from t where name='TEST2')
  5     loop
  6     dbms_output.put_line(t_cur.cursor_column);
  7     dbms_output.put_line(' I really wanted to output '||emp_cur.first_name||' but I dont know 
ho
w to dynamically reference the cursur value');
  8     end loop;
  9   end loop;
 10  end;
 11  /
emp_cur.first_name
I really wanted to output JOHN but I dont know how to dynamically reference the
cursur value
emp_cur.first_name
I really wanted to output JANE but I dont know how to dynamically reference the
cursur value

PL/SQL procedure successfully completed.

SQL> 
SQL> 


I want to be get a value from a cursor dynamically. Hopefully my question makes sense. 


Followup   June 16, 2004 - 12pm Central time zone:

ops$tkyte@ORA9IR2> select decode( t.cursor_column, 'first_name', emp.first_name, 'last_name', 
emp.last_name )
  2    from t, emp
  3   where t.name = 'TEST1'
  4  /
 
DECODE(T.CURSOR_COLUMN,'FIRST_
------------------------------
SMITH
JONES
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select decode( t.cursor_column, 'first_name', emp.first_name, 'last_name', 
emp.last_name )
  2    from t, emp
  3   where t.name = 'TEST2'
  4  /
 
DECODE(T.CURSOR_COLUMN,'FIRST_
------------------------------
JOHN
JANE
 



let the database join.
never code loops like you did:

  for x in ( select * from t1 )
  loop
     for y in ( select * from t2 where t2.xxx = x.xxx )
     loop

never do that, JUST JOIN. 

1 stars Try again   June 16, 2004 - 1pm Central time zone
Reviewer: A reader 
You said:
>let the database join.
>never code loops like you did:

>  for x in ( select * from t1 )
>  loop
>     for y in ( select * from t2 where t2.xxx = x.xxx )
>     loop

>never do that, JUST JOIN.

I would never do that, I would just join. However, I was just trying to give you an easy example 
that illustrated my problem. Your decode solution would not work because at run time I do not know 
the values that I would be comparing on. They are coming from table T ( the second cursor). The 
second cursor returns from table T, the column name from the first cursor that I want to get the 
value from. 


Followup   June 16, 2004 - 3pm Central time zone:

then you will be using dynamic sql.  you cannot dynamically refer to the values (and you did do 
that which you said you would not do).

beware "overly generic" code.  it always seems soooo cool at design time.  rarely ever again after 
that.



 

3 stars How to just set hints dynamically in a cursor?   June 17, 2004 - 3am Central time zone
Reviewer: Arn from Sydney, Australia
I hope this is relevant...

I am taking a similar approach to that outlined originally here to solve our problem.

We are converting an existing program into a new cursor. The existing program, for various reasons, 
used 6 cursors that were essentially identical, but varied in the hints they used depending on the 
parameters passed in. (The table isn't analysed, and simply analysing the table and leting the CBO 
do its thing is not an option.)

I have started using Native Dynamic SQL to allow me to set the hint at runtime. However, this is 
complicating and reducing the readability of the rest of the SQL due to the positional parameters.  
Everything bar the hint can be written statically, and is much more readable that way.

Is there a simpler way to set a hint for a cursor at runtime? So far dynamic SQL seems to be the 
only way.

Thanx for any advice. 


Followup   June 17, 2004 - 9am Central time zone:

i'd rethink my options.  really.  most of the time "not an option" is "an option" (or an 
opportunity)......


if you want to change the text of the query at runtime, dynamic sql is your only alternative 
(besides letting the software do what the software does for you...) 

5 stars Implicit Conversion Problem?   June 29, 2004 - 5pm Central time zone
Reviewer: Mike Burford from San Diego, CA USA
Hello Tom,
We use a different approach to REF CURSORS with dynamic 
where clauses. We are populating a PLSQL table with values and then binding like this:
    IF (tBindVars.COUNT = 2)
    THEN
      OPEN l_cursor FOR l_query_pl USING tBindVars(1), tBindVars(2);
    ELSIF (tBindVars.COUNT = 1) THEN ...

We discovered a performance problem with one query involving a date. I found that if I used a true 
date datatype as the bind variable as opposed to the to_date(varchar2) from the PLSQL table, the 
query plan was different and very fast. The SYS_CONTEXT test I ran mimicked the slower 
to_date(varchar2) method.

I put together a test procedure with your SYS_CONTEXT method, our PLSQL Table method, and the 
"Dummy Where" clause method mentioned in this article (so I could pass a date datatype). The output 
below shows the different execution plans, resources used, and response times. The PLSQL method 
shows dty=1 whereas the Dummy method shows dty=12. The SYS_CONTEXT method doesn't expose any bind 
information. Could it be that implicit conversion is still happening in the PLSQL method and 
SYS_CONTEXT method and this is causing the optimizer to generate different access paths? Do you 
have any other thoughts on what might be happening and how to get around it?

***SYS_CONTEXT TRACE:
PARSING IN CURSOR #2 len=521 dep=1 uid=42 oct=3 lid=42 tim=5353026057216 hv=3250717028 ad='7df3f68'
PARSE #2:c=16667,e=16384,p=0,cr=2,cu=0,mis=1,r=0,dep=1,og=4,tim=5353026057216
BINDS #2:
EXEC #2:...

***SYS_CONTEXT TKPROF:
select distinct cas_caseid, cas_id, d.dispatch_status,
               cas_name, cas_adddate, cas_queueassn,
             first_value(notes) over (partition by unotes_bcs.addid
               order by dateofca,timeofca,note_grp asc) notes
             from cases cs, service_dispatch d, unotes_bcs
             where  1 = 1
        and cas_adddate <= to_date(sys_context('MY_CTX','CAS_ADDDATE'),'yyyymmddhh24miss')
      and cas_caseid = d.DISPATCH_NUMBER and addid=cas_caseid and recordty = 'IA' order by 
cas_adddate desc

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch       56      5.73      15.35      15871      23009          0          55
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       58      5.75      15.37      15871      23009          0          55

Rows     Row Source Operation
-------  ---------------------------------------------------
     55  SORT UNIQUE
    164   WINDOW SORT
    164    TABLE ACCESS BY INDEX ROWID UNOTES_BCS
    221     NESTED LOOPS
     56      HASH JOIN
    361       TABLE ACCESS FULL SERVICE_DISPATCH
 353765       TABLE ACCESS BY INDEX ROWID CASES
 353765        INDEX RANGE SCAN CASES_N14 
    164      INDEX RANGE SCAN UNOTES_BCS_N1 


***PLSQL TABLE TRACE:
PARSING IN CURSOR #2 len=468 dep=1 uid=42 oct=3 lid=42 tim=5353041426432 hv=11255473 ad='81b27e0'
PARSE #2:c=0,e=1024,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=5353041426432
BINDS #2:
 bind 0: dty=1 mxl=32(09) mal=00 scl=00 pre=00 oacflg=13 oacfl2=1 size=32 offset=0
   bfp=1403ec118 bln=32 avl=09 flg=05
   value="01-JUN-04"
EXEC #2:...

***PLSQL TABLE TKPROF:
select distinct cas_caseid, cas_id, d.dispatch_status,
               cas_name, cas_adddate, cas_queueassn,
             first_value(notes) over (partition by unotes_bcs.addid
               order by dateofca,timeofca,note_grp asc) notes
             from cases cs, service_dispatch d, unotes_bcs
             where  1 = 1 and cas_adddate <= to_date(:EndDate)
      and cas_caseid = d.DISPATCH_NUMBER and addid=cas_caseid and recordty = 'IA' order by 
cas_adddate desc

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       56      4.21      14.15      15864      23009          0          55
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       58      4.21      14.15      15864      23009          0          55

Rows     Row Source Operation
-------  ---------------------------------------------------
     55  SORT UNIQUE
    164   WINDOW SORT
    164    TABLE ACCESS BY INDEX ROWID UNOTES_BCS
    221     NESTED LOOPS
     56      HASH JOIN
    361       TABLE ACCESS FULL SERVICE_DISPATCH
 353765       TABLE ACCESS BY INDEX ROWID CASES
 353765        INDEX RANGE SCAN CASES_N14 
    164      INDEX RANGE SCAN UNOTES_BCS_N1 


***DUMMY WHERE TRACE:
PARSING IN CURSOR #2 len=493 dep=1 uid=42 oct=3 lid=42 tim=5353055599616 hv=3452234128 ad='7a986f0'
END OF STMT
PARSE #2:c=0,e=1024,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=5353055599616
BINDS #2:
 bind 0: dty=1 mxl=32(00) mal=00 scl=00 pre=00 oacflg=13 oacfl2=1 size=40 offset=0
   bfp=1403ec118 bln=32 avl=00 flg=05
 bind 1: dty=12 mxl=07(07) mal=00 scl=00 pre=00 oacflg=13 oacfl2=1 size=0 offset=32
   bfp=1403ec138 bln=07 avl=07 flg=01
   value="6/1/2004 0:0:0"
EXEC #2:...

***DUMMY WHERE TKPROF:
select distinct cas_caseid, cas_id, d.dispatch_status,
               cas_name, cas_adddate, cas_queueassn,
             first_value(notes) over (partition by unotes_bcs.addid
               order by dateofca,timeofca,note_grp asc) notes
             from cases cs, service_dispatch d, unotes_bcs
               order by dateofca,timeofca,note_grp asc) notes
             from cases cs, service_dispatch d, unotes_bcs
             where  1 = 1 and 1 = DECODE(:Account,NULL,1,1) and cas_adddate <= :EndDate
      and cas_caseid = d.DISPATCH_NUMBER and addid=cas_caseid and recordty = 'IA' order by 
cas_adddate desc

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       56      0.01       0.06         61       1277          0          55
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       58      0.01       0.07         61       1277          0          55

Rows     Row Source Operation
-------  ---------------------------------------------------
     55  SORT UNIQUE
    164   WINDOW SORT
    164    FILTER
    164     TABLE ACCESS BY INDEX ROWID UNOTES_BCS
    221      NESTED LOOPS
     56       NESTED LOOPS
    361        TABLE ACCESS FULL SERVICE_DISPATCH
     56        TABLE ACCESS BY INDEX ROWID CASES
    361         INDEX UNIQUE SCAN CASES_PK 
    164       INDEX RANGE SCAN UNOTES_BCS_N1 
 


Followup   June 29, 2004 - 6pm Central time zone:

be interesting to compare the autotrace traceonly explain with the tkprofs -- to see why the 
optimizer chose the wrong index.  this is CBO right?

it is not the datatypes.

you are comparing 

where x = to_date....   -- a date to a date

vs

where x = :bind_variable that is a date  -- a date to a date

that's not it.  


 

5 stars Baffled by optimizer   June 30, 2004 - 2pm Central time zone
Reviewer: Mike Burford from San Diego, CA USA
Yes, this is CBO on 9.2.0.5. I've included the autotrace output below. It clearly shows the cost of 
the fast plan is higher than the cost of the slow plan, but I don't know how to read how it got 
there.

Another intesesting finding is that if I put the bind variable (date datatype) into a function like 
TRUNC or TO_DATE, the plan reverts to the slow plan.

Thanks for your time Tom,
Mike

SQL> @mb2
PL/SQL procedure successfully completed.
55 rows selected.
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=9984 Card=130 Bytes= 20150)
   1    0   SORT (UNIQUE) (Cost=9977 Card=130 Bytes=20150)
   2    1     WINDOW (SORT) (Cost=9984 Card=130 Bytes=20150)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'UNOTES_BCS' (Cost=27 Card=1 Bytes=74)
   4    3         NESTED LOOPS (Cost=9963 Card=130 Bytes=20150)
   5    4           HASH JOIN (Cost=216 Card=361 Bytes=29241)
   6    5             TABLE ACCESS (FULL) OF 'SERVICE_DISPATCH' (Cost=5 Card=361 Bytes=6137)
   7    5             TABLE ACCESS (BY INDEX ROWID) OF 'CASES' (Cost=210 Card=17710 Bytes=1133440)
   8    7               INDEX (RANGE SCAN) OF 'CASES_N14' (NON-UNIQUE) (Cost=14 Card=1)
   9    4           INDEX (RANGE SCAN) OF 'UNOTES_BCS_N1' (NON-UNIQUE) (Cost=2 Card=1)
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      23012  consistent gets
      17064  physical reads
          0  redo size
       4127  bytes sent via SQL*Net to client
        688  bytes received via SQL*Net from client
          5  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
         55  rows processed


SQL> @mb2
PL/SQL procedure successfully completed.
55 rows selected.
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10495 Card=130 Bytes=20150)
   1    0   SORT (UNIQUE) (Cost=10488 Card=130 Bytes=20150)
   2    1     WINDOW (SORT) (Cost=10495 Card=130 Bytes=20150)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'UNOTES_BCS' (Cost=27 Card=1 Bytes=74)
   4    3         NESTED LOOPS (Cost=10474 Card=130 Bytes=20150)
   5    4           NESTED LOOPS (Cost=727 Card=361 Bytes=29241)
   6    5             TABLE ACCESS (FULL) OF 'SERVICE_DISPATCH' (Cost= 5 Card=361 Bytes=6137)
   7    5             TABLE ACCESS (BY INDEX ROWID) OF 'CASES' (Cost=2 Card=1 Bytes=64)
   8    7               INDEX (UNIQUE SCAN) OF 'CASES_PK' (UNIQUE) (Cost=1 Card=1)
   9    4           INDEX (RANGE SCAN) OF 'UNOTES_BCS_N1' (NON-UNIQUE) (Cost=2 Card=1)
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       1282  consistent gets
         39  physical reads
          0  redo size
       4127  bytes sent via SQL*Net to client
        688  bytes received via SQL*Net from client
          5  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
         55  rows processed 


Followup   June 30, 2004 - 3pm Central time zone:

i've no idea what I'm looking at here....  

5 stars Baffled by optimizer   June 30, 2004 - 4pm Central time zone
Reviewer: Mike Burford from San Diego, CA USA
I was trying to supply what you suggested above:

"Followup:  

be interesting to compare the autotrace traceonly explain with the tkprofs -- to 
see why the optimizer chose the wrong index.  this is CBO right?"

I pasted the output from autotrace traceonly explain on the two statements that were in my post 
above that included the tkprof output.

I must be misinterpreting what you were suggesting. What should I be supplying?

Thanks,
Mike


 


Followup   June 30, 2004 - 8pm Central time zone:

i didn't put the two together (didn't "look up").

the lack of query text didn't clue me in that this was a follow on.



so, the indexes, what are they on exactly? 

4 stars How can we make the Sys_Context Available to every one   July 5, 2004 - 12pm Central time zone
Reviewer: Qaisar from Canada
Hi Tom,
It's so nice but I have a question, How can we use the context created in a schema while the 
procedure is being used by different users. We have only one schema under the name PDMAN and every 
user is given the execute priviliges on the packages through roles. When I execute the package, I 
get the error Insufficient Privileges. I'm able to execute the packaged procedures if I comment out 
the sys_context clause.
Please help.
 


Followup   July 5, 2004 - 1pm Central time zone:

how did you create the context?  if you:

create context your_context_name using that_package_name;

there should not be a problem -- if you have one, show the entire method to reproduce it -- all 
necessary commands one would need to type in. 

3 stars Error getting in SQL Clause   July 5, 2004 - 6pm Central time zone
Reviewer: Qaisar from Canada
I always get an error "ORA-28106: input value for argument #2 is not valid" when I'm testing the 
procedure that builds the dynamic sql by using context method for one parameter. All the other 
parameters are fine, this is only one parameter that throws an error, Any Idea?

Here is the Context statement that I created under particular schema "Create Context CTX_EDUC_PGM 
Using FMS_EDUCATION_PROGRAM_Q" but the others users get error when executing the procedure 
FMS_EDUCATION_PROGRAM_Q.FMS_EDUCATION_PROGRAM, even the users do have execute privileges and they 
can still execute if I comment out the context statement in building the dynamic sql.

Please help? 


Followup   July 5, 2004 - 8pm Central time zone:

you *must* supply a concise, yet 100% complete test case to demonstrate your issue.

just like I do -- starting with CREATE USER if that is relevant to the problem. 

3 stars Triggers & Procedures   July 6, 2004 - 9am Central time zone
Reviewer: venkat from INDIA
Why a trigger is allowed to have the same name as the table??
Can two different schema objects have the same name??
 


Followup   July 6, 2004 - 9am Central time zone:

because the trigger namespace is the trigger namespace.

Just like a constraint can have the same name as an index.

names in the trigger namespace = unique;
names in the constraint namespace = unique;
other names are unique in the more "global" namespace.


remember also that a user scott can create a table or package scott (not recommend!) 

3 stars Procedure in Error   July 6, 2004 - 12pm Central time zone
Reviewer: Qaisar from Canada
create or replace context MY_CTX using pk_emp;
Create table employee as select * from emp;
alter table employee add(employee_soundex_indicate_on Varchar2(1));
Update employee set employee_soundex_indicate_on = 'Y';

create or replace package pk_emp
is
Type search_rec is RECORD
    (pempno    emp.empno%type,
     pename emp.ename%TYPE,
     pjob   emp.job%TYPE,
     phiredate emp.hiredate%TYPE,
     psal    emp.sal%TYPE);
--Type search_cur is REF CURSOR return search_rec ;
Type search_cur is REF CURSOR; -- return search_rec ;

Procedure p_search (resultset    IN out search_cur,
            p_empno IN number,
            p_name  Varchar2,
            p_employee_soundex_indicate_on IN Varchar2);
end pk_emp;
/

create or replace package body pk_emp
is 
Procedure p_search (resultset    IN out search_cur,
            p_empno IN number,
            p_name  Varchar2,
            p_employee_soundex_indicate_on IN Varchar2 )
is
    lv_sql    Varchar2(2000);
begin
    lv_sql := 
    'Select emp.empno,
        emp.ename,
        emp.job,
        emp.hiredate,
        emp.sal
    From    Employee emp 
    Where 1 = 1 ';
    If p_empno is not null Then
        dbms_session.set_context( 'MY_CTX', 'emp.empno', p_empno);
        lv_sql := lv_sql||' and emp.empno = sys_context( ''MY_CTX'', ''emp.empno'' ) ';
    end if;

    If p_name is not null Then
        dbms_session.set_context( 'MY_CTX', 'emp.ename', p_name);
        lv_sql := lv_sql||' and emp.ename = sys_context( ''MY_CTX'', ''emp.Ename'' ) ';
    end if;

    If p_employee_soundex_indicate_on is not null Then
        dbms_session.set_context( 'MY_CTX', 'emp.employee_soundex_indicate_on', 
p_employee_soundex_indicate_on);
        lv_sql := lv_sql||' and emp.employee_soundex_indicate_on = sys_context( ''MY_CTX'', 
''emp.employee_soundex_indicate_on'' ) ';
    end if;

    DBMS_OUTPUT.PUT_LINE(LV_SQL);
    Open resultset for lv_sql;
End p_search;

End pk_emp;
/

If you execute this with the parameter p_employee_soundex_indicate_on as Y, You get the error 
"ORA-28106: input value for argument #2 is not valid"  


Followup   July 6, 2004 - 2pm Central time zone:

the variable name needs to be 30 characters or less.  it must be a valid identifier.  suggest you 
drop the emp. in the context, period.

ops$tkyte@ORA9IR2> create or replace package body pk_emp
  2  is
  3  Procedure p_search (resultset       IN out search_cur,
  4              p_empno IN number,
  5              p_name  Varchar2,
  6              p_employee_soundex_indicate_on IN Varchar2 )
  7  is
  8      lv_sql    Varchar2(2000);
  9  begin
 10      lv_sql :=
 11      'Select emp.empno,
 12          emp.ename,
 13          emp.job,
 14          emp.hiredate,
 15          emp.sal
 16      From    Employee emp
 17      Where 1 = 1 ';
 18      If p_empno is not null Then
 19          dbms_session.set_context( 'MY_CTX', 'empno', p_empno);
 20          lv_sql := lv_sql||' and emp.empno = sys_context( ''MY_CTX'', ''empno'' ) ';
 21      end if;
 22
 23      If p_name is not null Then
 24          dbms_session.set_context( 'MY_CTX', 'emp.ename', p_name);
 25          lv_sql := lv_sql||' and emp.ename = sys_context( ''MY_CTX'', ''Ename'' ) ';
 26      end if;
 27
 28      If p_employee_soundex_indicate_on is not null Then
 29          dbms_session.set_context( 'MY_CTX', 'employee_soundex_indicate_on', 
p_employee_soundex_indicate_on);
 30          lv_sql := lv_sql||' and emp.employee_soundex_indicate_on = sys_context( ''MY_CTX'', 
''employee_soundex_indicate_on'' ) ';
 31      end if;
 32
 33      Open resultset for lv_sql;
 34  End p_search;
 35
 36  End pk_emp;
 37  /
 
Package body created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable x refcursor
ops$tkyte@ORA9IR2> exec pk_emp.p_search( :x, null, 'SMITH', 'Y' )
 
PL/SQL procedure successfully completed.
 

1 stars The Problem is Occuring in 8i   July 6, 2004 - 5pm Central time zone
Reviewer: Qaisar from Canada
Hi Tom,

You are executing this on 9i, I have 8i and getting the error. 


Followup   July 6, 2004 - 7pm Central time zone:

I corrected your code -- please reread my comment.

Your code as written did not run on 9i either.

the second argument to set_context must be a valid 30 character identifier, yours was not. 

2 stars What if I have to include an alias   July 6, 2004 - 5pm Central time zone
Reviewer: Maq from Ind
Hi Tom,

You said the variable length not to exceed 30. In this case the Column name is 
"employee_soundex_indicate_on" and length is 27, Let's suppose I still have to use emp.column_name 
in multi table join, Is there any way to handle this. Column length is not even 30 but with the 
alias it could exceed 30. 


Followup   July 6, 2004 - 7pm Central time zone:

it is the set_context name that is limited to 30 characters.

there are no issues here at all -- not sure what you are concerned about.

You have a 30 character namespace "MY_CTX"

it can have a large set of values (think 26^30 number of values at the very least)


use "where emp.this_is_a_very_long_col_name = sys_context( 'my_ctx', 'x' )"

for example.  It need not be named after the entire name. 

4 stars Using a search form   July 12, 2004 - 1pm Central time zone
Reviewer: A reader 
I have a query like

select ...
from table
where 1=1
and field1=?
and field2=?
and field3=?
and ....

There are a total of 8 search fields. Some are varchar2, some are numeric, some are date (in which 
case it actually a date range i.e. date_field between d1 and d2)

i.e. a table (view actually) that has a "search form" on top of it for users to search based on 
diffent criteria. Your typical search form.

So, as with any search form, any field that the user enters a value for has to be ANDed into the 
query.

If I use dynamic SQL using the sys_context approach you demonstrate above, I would end up with 
(potentially) a factorial (or some high combinatorial number) based on 8 distinct SQLs (even if I 
use bind variables for each of the searchable fields)

Yes, if fields 1,3,5 are searched on most often, I will only have the SQL with these 3 fields in 
the WHERE clause in my shared pool.

But still, isnt there a way to just use 1 SQL statement, or at least minimize the distinct SQLs 
generated to do this?

Thanks 


Followup   July 12, 2004 - 9pm Central time zone:

you want to minimize the number of unique statements -- taking things that don't use binds and 
binding them.

but you also want to get the best plan possible.  a single query -- a single plan.  no indexes.


I would use dynamic sql, you'll find that there will be a finite combo of columns people actually 
use. 

5 stars Thanks a bunch!   July 13, 2004 - 1pm Central time zone
Reviewer: Jamie from CA USA
This was a fantastic article.  I was easy to follow and helped my project out enormously. Thanks 
again! 


4 stars question about using context   July 14, 2004 - 5pm Central time zone
Reviewer: James Su from Toronto
hi Tom,

I tried to use context:

SQL> create or replace context CTX_TEST using SP_TEST;

Context created.

SQL>
SQL> CREATE OR REPLACE PROCEDURE SP_TEST (p_value in number)
  2  as
  3  begin
  4     dbms_session.set_context('CTX_TEST', 'TEST', to_char(p_value));
  5  end;
  6  /

Procedure created.

SQL>
SQL> EXEC SP_TEST(10000);

PL/SQL procedure successfully completed.

SQL>
SQL> select to_number(sys_context('CTX_TEST','TEST')) from dual;

TO_NUMBER(SYS_CONTEXT('CTX_TEST','TEST'))
-----------------------------------------
                                    10000

SQL>
SQL> select CUST_ID from CUSTOMER where CUST_ID = to_number(sys_context('CTX_TEST','TEST'));

no rows selected

SQL> select CUST_ID from CUSTOMER where CUST_ID = 10000;

   CUST_ID
----------
     10000

You see, the row does exist, but I can't select it. 

Believe it or not, it does not work on this server(9.2.0.4.0), but works fine on another server 
(9.2.0.1.0) and I don't know why. 


Followup   July 15, 2004 - 11am Central time zone:

i'd need a reproducible example -- start from scratch on 9204, with a create table/insert into etc 
-- do everything you need to do to reproduce the issue

(compare the plans from all 4 cases as well -- are they the same or different) 

4 stars about my last post   July 15, 2004 - 10am Central time zone
Reviewer: James Su from Toronto
hi Tom,

"CUSTOMER" from the example I showed in my last post is a synonym for a remote table. I tested on a 
local table and it worked. 

Now I have to try another approach, to use package values instead of context. 


4 stars Variable in Cursor   July 29, 2004 - 2pm Central time zone
Reviewer: A reader from VA, USA
Hi Tom,

I have a procedure below,

--------------

PROCEDURE create_lookup_mviews IS

    remote_dbname    VARCHAR2(30);

    cursor c1 is select table_name 
             from user_tables@
             where table_name not like '%$%';
                      
BEGIN

  select db_link into remote_dbname from dba_db_links where owner='PUBLIC';

  for c1_rec in c1 loop

    if c1_rec.table_name='SHOP' then
    dbms_output.put_line('CREATE MATERIALIZED VIEW '||c1_rec.table_name||' REFRESH FAST WITH 
PRIMARY KEY FOR UPDATE AS SELECT * FROM '||c1_rec.table_name||'@' || remote_dbname);
    
    
    end if;
    
  end loop;

END create_lookup_mviews;

--------------------

How do I put the dblink after the @ in the cursor c1 like I did inside the Begin part?  How do I 
put another select statement after the @

Thanks! 


Followup   July 29, 2004 - 2pm Central time zone:

you cannot -- you have to use dynamic sql

procedure...
is

   type rc is ref cursor;
   c1 rc;
begin
   open c1 for 'select ..... @' || remote_dbname;
   loop
       fetch c1 into ..
       exit when c1%notfound;
       .....

   end loop;
   close c1;
end;
 

3 stars Return a weak ref cursor with datatypes   August 11, 2004 - 4pm Central time zone
Reviewer: Doug from PA, USA
Since the only way I can use NDS is with a weak REF CURSOR. I was wondering what the best way would 
be to RETURN the result set as an OUT keeping the datatypes bound. Basically I need to know that 
"hiredate" is a DATE and "salary" is a NUMBER. Would the addition of a fetch/insert into global 
temp table with a separate cursor selecting from that be the solution. I am looking at the method 
you outlined to handle parameters passed from a search screen, the only problem is it appears the 
data types on the return wont be known and another level of coding will need to be added. Someone 
suggested that a possible solution would be to handle the parameters being passed from the search 
engine like a crystal report using a strong REF CURSOR...

I dont see this below as a valuble solution, if there are parameters that are not passed why add it 
to the WHERE clause and compare a wild.

WHERE column1 LIKE '%'
      column2 LIKE '%param2%'
      column3 LIKE '%param3%'
      column4 LIKE '%'

what are your suggestions 


Followup   August 12, 2004 - 8am Central time zone:

what is the client?  they all should be able to describe the result set making weak ref cursors 
workable.

else, you won't be using NDS. 

4 stars ORA 28106   August 12, 2004 - 6am Central time zone
Reviewer: dxl from uk
Tom

I am getting some strange behaviour when using application contexts. (db 8.1.7.4)
When i execute my package from a fresh session it will work the first time as expected then fail 
the next time
with an error

ORA-28106: input value for argument #1 is not valid



My package is:




CREATE OR REPLACE CONTEXT OPH_CTX USING PKG_OPH_GOS;


CREATE OR REPLACE PACKAGE PKG_OPH_GOS AS

TYPE C_Return_Cursor IS REF CURSOR;
 
PROCEDURE P_Get_Voucher (    P_Org_String    IN    VARCHAR2 DEFAULT NULL,
                P_Cycle_String    IN    VARCHAR2 DEFAULT NULL,
                P_Pract_String    IN    VARCHAR2 DEFAULT NULL,
                P_Table_FLAG     IN    VARCHAR2,
                P_Order        IN    VARCHAR2,
                P_Cur         IN OUT     C_Return_Cursor    );

END PKG_OPH_GOS;
/


CREATE OR REPLACE PACKAGE BODY PKG_OPH_GOS AS



PROCEDURE P_Get_Voucher (    P_Org_String    IN    VARCHAR2 DEFAULT NULL,
                P_Cycle_String    IN    VARCHAR2,
                P_Pract_String    IN    VARCHAR2 DEFAULT NULL,
                P_Table_FLAG     IN    VARCHAR2,
                P_Order        IN    VARCHAR2,
                P_Cur         IN OUT     C_Return_Cursor    )


IS

V_Org_String     VARCHAR2(32000);
V_Cycle_String     VARCHAR2(32000);
V_Pract_String     VARCHAR2(32000);
l_n        NUMBER(38);
l_query      varchar2(32000);

V_Table     VARCHAR2(10);
V_Order        VARCHAR2(1000);


BEGIN

IF ( P_Table_FLAG = 'Y' ) THEN

    V_Table := 'PAID_CLAIM';

ELSE    V_Table := 'CLAIM';

END IF;


l_query := '
    SELECT     c.PAYMENT_ORG_CODE
    FROM     ' || V_Table || ' c,
        ORGANISATION org 
    WHERE    c.PAYMENT_ORG_ID = org.ORG_ID
    and c.payment_org_code = ''QQQ''';

    For rec_Cur IN (SELECT     Namespace,
                Attribute
            FROM    Session_Context
            WHERE    Namespace = 'OPH_CTX'
            AND    Attribute LIKE 'CYCLE\_IN\_LIST\_%' escape '\' ) LOOP

dbms_output.put_line('rec_Cur.Namespace = '|| rec_Cur.Namespace);
dbms_output.put_line('rec_Cur.Attribute = '|| rec_Cur.Attribute);

        DBMS_SESSION.SET_CONTEXT( rec_Cur.Namespace, rec_Cur.Attribute, null);    --  <--- ***** 
THS IS WHERE IT FALLS OVER *******


    END LOOP;

    V_Cycle_String := P_Cycle_String;


    FOR i IN 1 .. 99999    LOOP
        l_n := INSTR( V_Cycle_String, ',' );
        EXIT WHEN (NVL(l_n, 0) = 0 AND length(V_Cycle_String) IS NULL );

    IF l_n = 0 THEN
        DBMS_SESSION.SET_CONTEXT( 'OPH_CTX', 'cycle_in_list_' || i, LTRIM(RTRIM(SUBSTR( 
V_Cycle_String, 1)) ));
        V_Cycle_String := NULL;
    ELSE
        DBMS_SESSION.SET_CONTEXT( 'OPH_CTX', 'cycle_in_list_' || i, LTRIM(RTRIM(SUBSTR( 
V_Cycle_String, 1, l_n-1)) ));
        V_Cycle_String := SUBSTR( V_Cycle_String, l_n+1 );
    END IF;


    END LOOP;


    l_query := l_query ||'     AND    c.PAYMENT_CYCLE_ID IN ( SELECT     TO_NUMBER(Value) 
                                FROM     Session_Context 
                                WHERE     Namespace = ''OPH_CTX''
                                AND    Attribute LIKE ''CYCLE\_IN\_LIST\_%'' escape ''\'' )';



open P_Cur for l_query ;



END P_Get_Voucher;    


END PKG_OPH_GOS;
/














It works the first time when it doesn't have to reset the session contexts but fails on the second 
run when session contexts exist.
I have looked at the first argument of the line where it falls over 

DBMS_SESSION.SET_CONTEXT( rec_Cur.Namespace, rec_Cur.Attribute, null);    


and the rec_Cur.Namespace evaluates to 'OPH_CTX'  as you can see in the pasted sql text below :









SQL*Plus: Release 9.2.0.1.0 - Production on Thu Aug 12 10:45:59 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.


Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - 64bit Production
With the Partitioning option
JServer Release 8.1.7.4.0 - 64bit Production


Session altered.

10:45:59 ***@***>
10:46:15 ***@***>
10:46:15 ***@***>
10:46:15 ***@***>set autoprint on
10:46:16 ***@***>variable x refcursor
10:46:16 ***@***>exec PKG_OPH_GOS.P_Get_Voucher ('QQQ','9',null,'N','PRACTITIONER_CODE',:x)

PL/SQL procedure successfully completed.


PAYMENT
-------
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ

12 rows selected.

Elapsed: 00:00:00.00
Elapsed: 00:00:00.00
10:46:16 ***@***>
10:46:16 ***@***>
10:46:16 ***@***>select * from session_context;

NAMESPACE                      ATTRIBUTE
------------------------------ ------------------------------
VALUE
----------------------------------------------------------------------------------------------------

OPH_CTX                        CYCLE_IN_LIST_1
9


1 row selected.

Elapsed: 00:00:00.01
10:46:17 ***@***>set autoprint on
10:46:18 ***@***>variable x refcursor
10:46:18 ***@***>exec PKG_OPH_GOS.P_Get_Voucher ('QQQ','9',null,'N','PRACTITIONER_CODE',:x)
rec_Cur.Namespace = OPH_CTX
rec_Cur.Attribute = CYCLE_IN_LIST_1
BEGIN PKG_OPH_GOS.P_Get_Voucher ('QQQ','9',null,'N','PRACTITIONER_CODE',:x); END;

       *
ERROR at line 1:
ORA-28106: input value for argument #1 is not valid
ORA-00911: invalid character
ORA-06512: at "SYS.DBMS_SESSION", line 58
ORA-06512: at "OP5.PKG_OPH_GOS", line 101
ORA-06512: at line 1


ERROR:
ORA-24338: statement handle not executed


SP2-0625: Error printing variable "x"
Elapsed: 00:00:00.00
Elapsed: 00:00:00.02
10:46:19 ***@***>
10:46:19 ***@***>
10:46:19 ***@***>select * from session_context;

NAMESPACE                      ATTRIBUTE
------------------------------ ------------------------------
VALUE
----------------------------------------------------------------------------------------------------

OPH_CTX                        CYCLE_IN_LIST_1
9


1 row selected.

Elapsed: 00:00:00.01
10:46:19 ***@***>







As you can see from a fresh sqlplus session it fails the second time i execute it due to there only 
being session context values after 
the 2nd run.  However if i then recompile the package, then select from session_context to show 
values still present then run it again
it works ok first time then fails again 2nd time:



10:51:49 ***@***>@C:\oph\packages\PKG_OPH_GOS.sql

Package body created.

Elapsed: 00:00:00.02
10:51:53 ***@***>select * from session_context;

NAMESPACE                      ATTRIBUTE
------------------------------ ------------------------------
VALUE
----------------------------------------------------------------------------------------------------

OPH_CTX                        CYCLE_IN_LIST_1
9


1 row selected.

Elapsed: 00:00:00.01
10:51:57 ***@***>exec PKG_OPH_GOS.P_Get_Voucher ('QQQ','9',null,'N','PRACTITIONER_CODE',:x)
rec_Cur.Namespace = OPH_CTX
rec_Cur.Attribute = CYCLE_IN_LIST_1

PL/SQL procedure successfully completed.


PAYMENT
-------
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ
QQQ

12 rows selected.

Elapsed: 00:00:00.02
Elapsed: 00:00:00.02
10:52:02 ***@***>
10:52:02 ***@***>
10:52:02 ***@***>select * from session_context;

NAMESPACE                      ATTRIBUTE
------------------------------ ------------------------------
VALUE
----------------------------------------------------------------------------------------------------

OPH_CTX                        CYCLE_IN_LIST_1
9


1 row selected.

Elapsed: 00:00:00.01
10:52:02 ***@***>exec PKG_OPH_GOS.P_Get_Voucher ('QQQ','9',null,'N','PRACTITIONER_CODE',:x)
rec_Cur.Namespace = OPH_CTX
rec_Cur.Attribute = CYCLE_IN_LIST_1
BEGIN PKG_OPH_GOS.P_Get_Voucher ('QQQ','9',null,'N','PRACTITIONER_CODE',:x); END;

       *
ERROR at line 1:
ORA-28106: input value for argument #1 is not valid
ORA-00911: invalid character
ORA-06512: at "SYS.DBMS_SESSION", line 58
ORA-06512: at "OP5.PKG_OPH_GOS", line 101
ORA-06512: at line 1


ERROR:
ORA-24338: statement handle not executed


SP2-0625: Error printing variable "x"
Elapsed: 00:00:00.00
Elapsed: 00:00:00.02
10:53:47 ***@***>
10:53:47 ***@***>
10:53:47 ***@***>select * from session_context;

NAMESPACE                      ATTRIBUTE
------------------------------ ------------------------------
VALUE
----------------------------------------------------------------------------------------------------

OPH_CTX                        CYCLE_IN_LIST_1
9


1 row selected.

Elapsed: 00:00:00.01
10:53:48 ***@***>




What could be causing the ORA-28106, since you can see from the dbms_output that the first argument 
rec_Cur.Namespace looks ok

rec_Cur.Namespace  = OPH_CTX




I'm sorry i tried to produce a test case using dummy tables but could not reproduce the problem.
When you change sql statement to reference 2 dummy tables and use the same package it will work 
correctly with no errors, i can't reproduce.
It seems to be dependent on the tables, which is strange as its complaining about setting a 
context.

Please do you know why i'm getting ORA-28106 ??

Thanks 


Followup   August 12, 2004 - 9am Central time zone:

how's about a test case I too can run -- tables, data, et.al.  

2 stars ..   August 12, 2004 - 9am Central time zone
Reviewer: dxl from uk
i tried for a long time to reproduce a simple test case but couldn't, which makes me think it is 
bug related anyway. 

I was just wondering if you have ever come across this sort of error anyway, and if you have any 
idea what could cause it??

If you look in the error message it seems to be complaining about an invalid character too, what 
could that be?? 


Followup   August 12, 2004 - 10am Central time zone:

Ok, got it.  

when plsql fetched from the table -- it was not null terminating the strings.  set_context for 
whatever reason was expecting null terminated strings.  I was able to reproduce in 817:

ops$tkyte@ORA817DEV> CREATE OR REPLACE CONTEXT OPH_CTX USING PKG_OPH_GOS;
 
Context created.
 
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> CREATE OR REPLACE PACKAGE PKG_OPH_GOS
  2  AS
  3
  4          TYPE C_Return_Cursor IS REF CURSOR;
  5
  6      PROCEDURE P_Get_Voucher
  7      ( P_Org_String   IN    VARCHAR2 DEFAULT NULL,
  8        P_Cycle_String IN    VARCHAR2 DEFAULT NULL,
  9        P_Pract_String IN    VARCHAR2 DEFAULT NULL,
 10        P_Table_FLAG   IN    VARCHAR2,
 11        P_Order        IN    VARCHAR2,
 12        P_Cur          IN OUT     C_Return_Cursor    );
 13
 14  END PKG_OPH_GOS;
 15  /
 
Package created.
 
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> CREATE OR REPLACE PACKAGE BODY PKG_OPH_GOS
  2  AS
  3
  4  PROCEDURE P_Get_Voucher
  5      ( P_Org_String   IN    VARCHAR2 DEFAULT NULL,
  6        P_Cycle_String IN    VARCHAR2 DEFAULT NULL,
  7        P_Pract_String IN    VARCHAR2 DEFAULT NULL,
  8        P_Table_FLAG   IN    VARCHAR2,
  9        P_Order        IN    VARCHAR2,
 10        P_Cur          IN OUT     C_Return_Cursor    )
 11  IS
 12     V_Org_String     VARCHAR2(32000);
 13     V_Cycle_String     VARCHAR2(32000);
 14     V_Pract_String     VARCHAR2(32000);
 15     l_n        NUMBER(38);
 16     l_query      varchar2(32000);
 17
 18     V_Table     VARCHAR2(10);
 19     V_Order        VARCHAR2(1000);
 20  BEGIN
 21      For rec_Cur IN
 22          (SELECT Namespace, Attribute
 23         FROM Session_Context
 24        WHERE Namespace = 'OPH_CTX'
 25          AND Attribute LIKE 'CYCLE\_IN\_LIST\_%' escape '\' )
 26          loop
 27                  dbms_output.put_line('rec_Cur.Namespace = "'|| rec_Cur.Namespace || '"' );
 28                  dbms_output.put_line('rec_Cur.Attribute = "'|| rec_Cur.Attribute || '"' );
 29          DBMS_SESSION.SET_CONTEXT( rec_Cur.Namespace, rec_Cur.Attribute, null);
 30      END LOOP;
 31
 32          V_Cycle_String := P_Cycle_String;
 33
 34      FOR i IN 1 .. 99999
 35          LOOP
 36          l_n := INSTR( V_Cycle_String, ',' );
 37          EXIT WHEN (NVL(l_n, 0) = 0 AND length(V_Cycle_String) IS NULL );
 38
 39          IF l_n = 0 THEN
 40              DBMS_SESSION.SET_CONTEXT
 41                          ( 'OPH_CTX', 'cycle_in_list_' || i, LTRIM(RTRIM(SUBSTR( 
V_Cycle_String, 1)) ));
 42              V_Cycle_String := NULL;
 43          ELSE
 44              DBMS_SESSION.SET_CONTEXT
 45                          ( 'OPH_CTX', 'cycle_in_list_' || i, LTRIM(RTRIM(SUBSTR( 
V_Cycle_String, 1, l_n-1)) ));
 46              V_Cycle_String := SUBSTR( V_Cycle_String, l_n+1 );
 47          END IF;
 48      END LOOP;
 49
 50          open p_cur for select * from dual;
 51  END P_Get_Voucher;
 52
 53  END PKG_OPH_GOS;
 54  /
 
Package body created.
 
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> set autoprint on
ops$tkyte@ORA817DEV> variable x refcursor
ops$tkyte@ORA817DEV> exec PKG_OPH_GOS.P_Get_Voucher('QQQ','9',null,'N','PRACTITIONER_CODE',:x)
 
PL/SQL procedure successfully completed.
 
 
D
-
X
 
ops$tkyte@ORA817DEV> exec PKG_OPH_GOS.P_Get_Voucher('QQQ','9',null,'N','PRACTITIONER_CODE',:x)
rec_Cur.Namespace = "OPH_CTX"
rec_Cur.Attribute = "CYCLE_IN_LIST_1"
BEGIN PKG_OPH_GOS.P_Get_Voucher('QQQ','9',null,'N','PRACTITIONER_CODE',:x); END;
 
       *
ERROR at line 1:
ORA-28106: input value for argument #1 is not valid
ORA-00911: invalid character
ORA-06512: at "SYS.DBMS_SESSION", line 62
ORA-06512: at "OPS$TKYTE.PKG_OPH_GOS", line 29
ORA-06512: at line 1



the workaround is fairly simple and not needed in 9i and up


 26          loop
 27                  dbms_output.put_line('rec_Cur.Namespace = "'|| rec_Cur.Namespace || '"' );
 28                  dbms_output.put_line('rec_Cur.Attribute = "'|| rec_Cur.Attribute || '"' );
 29          DBMS_SESSION.SET_CONTEXT( rec_Cur.Namespace||'', rec_Cur.Attribute||'', null);
 30      END LOOP;



alternatively, just assign the variable to itself, that'll null terminate it as well. 

5 stars thanks!   August 12, 2004 - 12pm Central time zone
Reviewer: dxl from uk
Thanks Tom that was truly amazing that you were not only able to reproduce the error but also 
offered a fix.

I was able to implement the fix ok without problems and its working great now.

Just out of interest though can you give a bit more detail as to what was going on?
I'm not sure i totally understood why it was happening, what was actually the "not null" bit on the 
end of the strings?
Which bit of your test case actually forced the problem to occur??

Many thanks
 


Followup   August 12, 2004 - 12pm Central time zone:

it is an intermittent bug in 8i with the context value.  if you:

select something into plsql_variable from table;

in 8i - the plsql_variable was not "null terminated" (a C language'ism).  If you assigned 
plsql_variable := plsql_variable - it is.

sys_context was assuming null terminated.

hence when you sent it the variable without the null terminator, it failed IF the string wasn't 
accidently null terminated (which it could be).  it depends on what bytes were left lying about in 
memory.

creating the temporary plsql variable via the ||'' is like doing an assignment. 

3 stars Return a weak ref cursor with datatypes   August 12, 2004 - 4pm Central time zone
Reviewer: Doug from PA, USA
The client is Java using a JDBC connection.

Thanks Tom 


Followup   August 12, 2004 - 4pm Central time zone:

they work just as functionally with weak as with strongly typed -- why do you need a strongly typed 
cursor?   

3 stars My production solution   August 20, 2004 - 2am Central time zone
Reviewer: Michael from Germany
Hello,

i used package variables and package functions to solve this problem. This approach works very well 
in production.

CREATE TYPE NUMBERTABLE AS TABLE OF NUMBER;

CREATE OR REPLACE PACKAGE BIND_TEST_PKG as  
  function getEnameFilter return emp.ename%type;  
  function getDeptnoFilterTable return numbertable;
  function getSalUpperLimit return emp.sal%type;
  function getSalLowerLimit return emp.sal%type;
    
  function getCursor(p_ename in emp.ename%type := null,
                     p_deptno in numbertable := null,
                     p_sal_lowerlimit in emp.sal%type := null,
                     p_sal_upperlimit in emp.sal%type := null)
    return sys_refcursor;
end;

CREATE OR REPLACE PACKAGE BODY BIND_TEST_PKG  as
  g_Ename emp.ename%type := null;
  g_DeptnoTable numbertable := null;
  g_SalUpperLimit emp.sal%type := null;
  g_SalLowerLimit emp.sal%type := null;
 
  function getEnameFilter return emp.ename%type as
  begin
    return g_Ename;
  end;  
  
  function getDeptnoFilterTable return numbertable as
  begin
    return g_DeptnoTable;
  end;
  
  function getSalUpperLimit return emp.sal%type as
  begin
    return g_SalUpperLimit;
  end;
  
  function getSalLowerLimit return emp.sal%type as
  begin
    return g_SalLowerLimit;
  end;  
  
  function getCursor(p_ename in emp.ename%type := null,
                     p_deptno in numbertable := null,
                     p_sal_lowerlimit in emp.sal%type := null,
                     p_sal_upperlimit in emp.sal%type := null)
    return sys_refcursor
  as
    l_cursor sys_refcursor;
    l_query varchar2(2000) := 'select e.empno, e.ename, e.sal, e.deptno ' ||
                              'from   emp e ' ||
                              'where  1 = 1 ';
  begin
    if p_ename is not null then
      g_Ename := p_ename;
      l_query := l_query || 'and e.ename like bind_test_pkg.getEnameFilter || ''%'' ';
    end if;
    if p_deptno is not null then
      g_DeptnoTable := p_deptno; 
      l_query := l_query || 'and e.deptno in (select column_value ' ||
                                             'from   table(bind_test_pkg.getDeptnoFilterTable)) ';
    end if; 
    if p_sal_lowerlimit is not null and p_sal_upperlimit is not null then
      g_SalUpperLimit := p_sal_upperlimit;
      g_SalLowerLimit := p_sal_lowerlimit;
      l_query := l_query || 'and e.sal between bind_test_pkg.getSalLowerLimit and 
bind_test_pkg.getSalUpperLimit ';
    elsif p_sal_lowerlimit is not null then
      g_SalLowerLimit := p_sal_lowerlimit;
      l_query := l_query || 'and e.sal >= bind_test_pkg.getSalLowerLimit ';
    elsif p_sal_upperlimit is not null then
      g_SalUpperLimit := p_sal_upperlimit;
      l_query := l_query || 'and e.sal <= bind_test_pkg.getSalUpperLimit ';      
    end if;     
    open l_cursor for l_query;
    return l_cursor;
  end;  
end;

I think this works as good as the sys_context solution, or even better, because it's easy to do 
in-lists (deptno in this example) and you have no datatype conversions.

Tom, do you see any drawback of this solution? 


Followup   August 20, 2004 - 10am Central time zone:

yes, performance is less then "good" in general.  

the overhead of calling plsql from sql is huge compared to using sys_context.  this could be 
*deadly* in general.  I would (and do) avoid this approach.  It is what we had to do in v7/v8.0 but 
not anymore.

at the *very least* -- use:

  and e.sal <= (select bind_test_pkg.getSalUpperLimit from dual)

use scalar subqueries so that the scalar subquery caching can kick in and invoke your function once 
per query instead of one per row per query. 

5 stars You are right ...   August 25, 2004 - 7am Central time zone
Reviewer: Michael from Germany
Hi,

Yes, the scalar subquery caching works great!!!
Thank-you very much for this tip!

To compare the sys_context with the package variables, i made the following test (with 
RUNSTATS_PKG):

The package:

CREATE OR REPLACE PACKAGE BIND_TEST_PKG as  
  function getEnameFilter return emp.ename%type;  
      
  function getCursor1(p_ename in emp.ename%type := null)
    return sys_refcursor;
    
  function getCursor2(p_ename in emp.ename%type := null)
    return sys_refcursor;  
end;

getCursor1 uses the package variable and a package function. getCursor2 uses a context.

CREATE OR REPLACE PACKAGE BODY BIND_TEST_PKG as
  g_Ename emp.ename%type := null;
  
  function getEnameFilter return emp.ename%type as
  begin
        return g_Ename;
  end;  
 
  function getCursor1(p_ename in emp.ename%type := null)
    return sys_refcursor
  as
    l_cursor sys_refcursor;
    l_query varchar2(2000) := 'select e.empno, e.ename, e.sal, e.deptno ' ||
                              'from   emp e ' ||
                              'where  1 = 1 ';
  begin
    if p_ename is not null then
      g_Ename := p_ename;
      l_query := l_query || 'and e.ename like (select bind_test_pkg.getEnameFilter || ''%'' from 
dual) ';
    end if;
    open l_cursor for l_query;
    return l_cursor;
  end; 
  
  function getCursor2(p_ename in emp.ename%type := null)
    return sys_refcursor
  as
    l_cursor sys_refcursor;
    l_query varchar2(2000) := 'select e.empno, e.ename, e.sal, e.deptno ' ||
                              'from   emp e ' ||
                              'where  1 = 1 ';
  begin
    if p_ename is not null then
      dbms_session.set_context('bind_test_ctx', 'ename', p_ename);
      l_query := l_query || 'and e.ename like sys_context(''bind_test_ctx'', ''ename'') || ''%'' ';
    end if;
    open l_cursor for l_query;
    return l_cursor;
  end;        
end;

The context:

create context bind_test_ctx using bind_test_pkg;

Then i executed the following test (the query returns from my emp table about 100 rows):

declare
  c sys_refcursor;
  type l_numtab_t is table of number;
  type l_varchartab_t is table of varchar2(40);
  l_empno l_numtab_t;
  l_ename l_varchartab_t;
  l_sal l_numtab_t;
  l_deptno l_numtab_t;
begin
  runstats_pkg.rs_start;
  for l_i in 1 .. 1000 loop
    c := bind_test_pkg.getCursor1(p_ename => 'EMP7');
    fetch c bulk collect into l_empno, l_ename, l_sal, l_deptno;
    close c;
  end loop;
  runstats_pkg.rs_middle;
  for l_i in 1 .. 1000 loop
    c := bind_test_pkg.getCursor2(p_ename => 'EMP7');
    fetch c bulk collect into l_empno, l_ename, l_sal, l_deptno;
    close c;
  end loop;
  runstats_pkg.rs_stop;
end;

Result of run 1:
================

Run1 ran in 302 hsecs
Run2 ran in 287 hsecs
run 1 ran in 105,23% of the time

Name                                Run1      Run2      Diff
LATCH.active checkpoint queue          2         1        -1
LATCH.kwqit: protect wakeup ti         0         1         1
LATCH.spilled msgs queues list         0         1         1
STAT...redo entries                    6         7         1
LATCH.session timer                    1         0        -1
LATCH.Consistent RBA                   1         3         2
LATCH.cache buffer handles             0         2         2
LATCH.dummy allocation                 2         0        -2
LATCH.process allocation               2         0        -2
LATCH.process group creation           2         0        -2
LATCH.event group latch                2         0        -2
LATCH.channel handle pool latc         2         0        -2
LATCH.lgwr LWN SCN                     0         3         3
STAT...cleanout - number of kt         2         5         3
STAT...consistent gets - exami     1,002     1,005         3
STAT...calls to kcmgcs                 2         5         3
LATCH.mostly latch-free SCN            0         3         3
STAT...active txn count during         2         5         3
LATCH.cache buffers lru chain         28        24        -4
STAT...parse time elapsed              3         7         4
STAT...parse time cpu                  3         7         4
LATCH.messages                        14        19         5
STAT...recursive cpu usage           215       220         5
LATCH.redo writing                     7        12         5
STAT...consistent changes             10        15         5
LATCH.session idle bit                 6         1        -5
STAT...db block changes               16        22         6
STAT...db block gets                  10        17         7
LATCH.channel operations paren        10         2        -8
LATCH.enqueues                        15         6        -9
LATCH.row cache enqueue latch          0        54        54
LATCH.row cache objects                0        54        54
LATCH.checkpoint queue latch         136        70       -66
LATCH.session allocation               4        92        88
LATCH.simulator hash latch        10,116     9,984      -132
STAT...redo size                   1,744     1,876       132
LATCH.redo allocation                  8       147       139
LATCH.undo global data                 3       190       187
LATCH.dml lock allocation              0       190       190
LATCH.enqueue hash chains             15       284       269
LATCH.library cache               10,006     9,375      -631
STAT...buffer is not pinned co    23,000    22,000    -1,000
STAT...table scans (short tabl     1,000         0    -1,000
STAT...table scan rows gotten      1,000         0    -1,000
STAT...table scan blocks gotte     1,000         0    -1,000
STAT...no work - consistent re    23,000    22,000    -1,000
LATCH.shared pool                  4,003     5,212     1,209
LATCH.library cache pin            8,006     6,188    -1,818
STAT...calls to get snapshot s     3,001     1,001    -2,000
LATCH.library cache pin alloca     2,000     4,160     2,160
STAT...session logical reads      27,015    24,026    -2,989
STAT...consistent gets            27,005    24,009    -2,996
LATCH.cache buffers chains        53,143    48,419    -4,724
STAT...session pga memory        262,144   196,608   -65,536

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
87,601    84,564    -3,037 103.59%

Result of run 2:
================

Run1 ran in 281 hsecs
Run2 ran in 313 hsecs
run 1 ran in 89,78% of the time

Name                                Run1      Run2      Diff
LATCH.Consistent RBA                   1         2         1
LATCH.active checkpoint queue          1         2         1
LATCH.enqueues                         6         7         1
LATCH.session idle bit                 0         1         1
LATCH.spilled msgs queues list         0         1         1
LATCH.kwqit: protect wakeup ti         0         1         1
LATCH.cache buffer handles             0         2         2
LATCH.lgwr LWN SCN                     0         2         2
LATCH.simulator hash latch         9,986     9,984        -2
STAT...parse time cpu                  5         7         2
LATCH.mostly latch-free SCN            0         2         2
STAT...redo entries                    6         9         3
LATCH.redo writing                     6        10         4
STAT...parse time elapsed              5         9         4
STAT...active txn count during         2         7         5
STAT...calls to kcmgcs                 2         7         5
STAT...cleanout - number of kt         2         7         5
STAT...consistent gets - exami     1,002     1,007         5
LATCH.messages                        10        18         8
STAT...consistent changes             10        23        13
STAT...db block gets                  10        23        13
STAT...db block changes               16        32        16
STAT...recursive cpu usage           224       204       -20
LATCH.row cache enqueue latch          0        54        54
LATCH.row cache objects                0        54        54
LATCH.cache buffers lru chain          0        61        61
LATCH.session allocation               0        92        92
LATCH.checkpoint queue latch          48       168       120
LATCH.redo allocation                  8       147       139
LATCH.undo global data                 4       193       189
LATCH.dml lock allocation              0       190       190
LATCH.enqueue hash chains              6       286       280
STAT...redo size                   1,744     2,124       380
LATCH.library cache               10,006     9,375      -631
STAT...buffer is not pinned co    23,000    22,000    -1,000
STAT...table scans (short tabl     1,000         0    -1,000
STAT...table scan rows gotten      1,000         0    -1,000
STAT...table scan blocks gotte     1,000         0    -1,000
STAT...no work - consistent re    23,000    22,000    -1,000
LATCH.shared pool                  4,001     5,212     1,211
LATCH.library cache pin            8,006     6,188    -1,818
STAT...calls to get snapshot s     3,001     1,001    -2,000
LATCH.library cache pin alloca     2,000     4,160     2,160
STAT...session logical reads      27,015    24,036    -2,979
STAT...consistent gets            27,005    24,013    -2,992
LATCH.cache buffers chains        53,079    48,567    -4,512
STAT...session pga memory         65,536   262,144   196,608

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
87,238    84,849    -2,389 102.82%

Result of run 3:
================

Run1 ran in 269 hsecs
Run2 ran in 297 hsecs
run 1 ran in 90,57% of the time

Name                                Run1      Run2      Diff
LATCH.active checkpoint queue          1         2         1
LATCH.redo allocation                  9        10         1
LATCH.undo global data                 3         4         1
STAT...physical reads                  1         0        -1
LATCH.redo writing                     6         7         1
STAT...redo entries                    6         7         1
LATCH.simulator hash latch         9,986     9,984        -2
STAT...cleanout - number of kt         2         4         2
STAT...consistent gets - exami     1,002     1,004         2
STAT...calls to kcmgcs                 2         4         2
STAT...active txn count during         2         4         2
STAT...recursive cpu usage           224       226         2
LATCH.messages                         9        12         3
STAT...parse time cpu                  3         6         3
STAT...parse time elapsed              3         7         4
STAT...consistent changes             10        15         5
STAT...db block changes               16        23         7
STAT...db block gets                  10        17         7
LATCH.cache buffers lru chain          1        29        28
LATCH.checkpoint queue latch          53       146        93
STAT...redo size                   1,748     1,928       180
LATCH.library cache               10,006     9,012      -994
STAT...buffer is not pinned co    23,000    22,000    -1,000
STAT...table scans (short tabl     1,000         0    -1,000
STAT...table scan rows gotten      1,000         0    -1,000
STAT...table scan blocks gotte     1,000         0    -1,000
STAT...no work - consistent re    23,000    22,000    -1,000
LATCH.shared pool                  4,001     5,003     1,002
LATCH.library cache pin            8,006     6,012    -1,994
LATCH.library cache pin alloca     2,000     4,000     2,000
STAT...calls to get snapshot s     3,001     1,001    -2,000
STAT...session logical reads      27,015    24,025    -2,990
STAT...consistent gets            27,005    24,008    -2,997
LATCH.cache buffers chains        53,071    47,174    -5,897
STAT...session pga memory        262,144   196,608   -65,536

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
87,233    81,476    -5,757 107.07%

Result of run 4:
================

Run1 ran in 275 hsecs
Run2 ran in 254 hsecs
run 1 ran in 108,27% of the time

Name                                Run1      Run2      Diff
LATCH.Consistent RBA                   1         0        -1
LATCH.archive process latch            1         0        -1
LATCH.simulator lru latch              0         1         1
STAT...free buffer requested           0         1         1
LATCH.redo allocation                  8         7        -1
STAT...parse time cpu                  3         2        -1
STAT...parse time elapsed              3         2        -1
LATCH.archive control                  2         0        -2
LATCH.redo writing                     5         3        -2
STAT...redo entries                    6         8         2
LATCH.simulator hash latch         9,988     9,985        -3
LATCH.cache buffers lru chain          1         5         4
LATCH.messages                        11         6        -5
STAT...consistent gets - exami     1,002     1,007         5
STAT...cleanout - number of kt         2         7         5
STAT...calls to kcmgcs                 2         7         5
STAT...active txn count during         2         7         5
LATCH.undo global data                 4        10         6
STAT...consistent changes             10        20        10
STAT...db block changes               16        29        13
STAT...db block gets                  10        24        14
STAT...recursive cpu usage           230       195       -35
STAT...redo size                   1,752     2,060       308
LATCH.library cache               10,006     9,012      -994
STAT...buffer is not pinned co    23,000    22,000    -1,000
STAT...no work - consistent re    23,000    22,000    -1,000
STAT...table scan blocks gotte     1,000         0    -1,000
STAT...table scans (short tabl     1,000         0    -1,000
STAT...table scan rows gotten      1,000         0    -1,000
LATCH.shared pool                  4,001     5,003     1,002
LATCH.library cache pin            8,006     6,012    -1,994
LATCH.library cache pin alloca     2,000     4,000     2,000
STAT...calls to get snapshot s     3,001     1,001    -2,000
STAT...session logical reads      27,015    24,036    -2,979
STAT...consistent gets            27,005    24,012    -2,993
LATCH.cache buffers chains        53,084    47,155    -5,929
STAT...session pga memory        262,144   196,608   -65,536

Run1 latches total versus runs -- difference and pct
Run1      Run2      Diff     Pct
87,254    81,335    -5,919 107.28%

Performance-wise they are nearly identical (there is some "noise").
The package solution needs a little more latches (2-7%), but it's not dramatic.
So, i think - in the end - the package solution (getCursor1 with scalar subquery caching of 
course!) is better, because you can't have type conversion issues and, above all, you can use 
in-lists.

Or has my test an error/misinterpretation? 


4 stars How can we Use subquery in Sys_Context   August 28, 2004 - 5pm Central time zone
Reviewer: Alex from Canada
Hi Tom,

let's suppose p_program_name is procedure's parameter and it's checked as

If p_program_name is not null then
lv_where := lv_where ||'epnh.education_program_name_id  IN
      (    Select    hist.education_program_name_id
    From    education_pgm_name_hist hist
    Where    hist.uppercase_program_name like "'
                ||p_program_name||'")';
End If;

How can this situtaion be used in sys_context?

Thanks. 


Followup   August 28, 2004 - 5pm Central time zone:

the subquery isn't really relevant in this context.  this is not any different than what is 
demonstrated above:

dbms_session.set_context( 'my_ctx', 'prog_name', p_program_name );

lv_where := lv_where ||'epnh.education_program_name_id  IN
      (    Select    hist.education_program_name_id
    From    education_pgm_name_hist hist
    Where    hist.uppercase_program_name 
      like sys_context( ''my_ctx'', ''prog_name'' ) )';


 

4 stars Reference the column or a variable ?   September 2, 2004 - 10am Central time zone
Reviewer: robert from CT
for i in cursor_x
loop
  *** whole lot of repeated references to i.columnx ***
end loop;


for situation like above
Is it more performent to assign i.columnx to a variable & reference that var or makes no difference 
?

Thanks 


Followup   September 2, 2004 - 10am Central time zone:

benchmark it and let us know!   

4 stars Seems No Difference to me....   September 2, 2004 - 11am Central time zone
Reviewer: robert from CT
nope, no difference that I could tell.
My sample is a loop size of 500 and about 30 repeated refernces to i.columnx

thanks 


4 stars varying select list   October 12, 2004 - 12pm Central time zone
Reviewer: koola from uk
Tom

We are building packages for a java app to call. This is so that we have no sql statements embedded 
in the java app, all our sql will reside in packaged procedures stored in the database.

Now there is a requirement to have a "filter" page in the app. This will need to calculate lots of 
different counts from a few tables depending on how the user filters the data and what they choose 
to "group by" to do the count(*).

Someone has suggested that we build a table and fill a column with the text for  all the different 
"base" queries 

eg  Query_Table:

Query_id   TABLE_NAME   GROUP_BY_FIELDS
--------- -----------    ----------------
1          tble1         col1, col2, co3
2          tble1         col1, col2


That type of thing.
Then they would use the text in these fields to dynamically build a query:

procedure (returning ref_cursor) etc..


....
select table_name, group_by_fields
into   v_table_name, v_group 
from ...

..
v_Sql := 'select ...' from ' || V_table_name ||  '..etc.
         ||' where id = :id || 'group by '|| v_group 

open V_REF_CURSOR FOR  V_Sql using v_id;

etc


Do you get the idea?!  At the moment there are 20 to 30 different combinations of group by clauses 
to produce the counts required. So this would save writing 20 to 30 stored procedures and just put 
the queries in a table.

Now i am totally against this idea but i am having trouble explaining to them why!  Would it 
flooding the shared pool with unique sql??

Their arguement is that:

1) as far as binds go the WHERE clause arguement ie the id, is passed in using bind variables so 
thats ok.

2) It makes it very easy to build the queries dynamically like this using a table, since it makes 
it easy to update and add to the queries we are able to run and each query will eventually make it 
in to the shared_pool once it is used.

3) The system is only going to be used by 100 users at most so its not like there are going to be 
1000s of unique sql statements in the shared pool.






Please can you help me give reasons why this is not a good idea (unless you think it is valid 
enough) maybe with some examples to prove it?



My alternative approach would involve passing in to the procedure an array of select columns ?? and 
using application contexts to bind the data ??
Not sure about the group by clauses though? maybe use dbms_sql to build the queries?? but would 
this still involve a lot more procedures, i'm not sure?


This is on 9.2.0.5.

Can you suggest anything better?

Many thanks
 


Followup   October 12, 2004 - 2pm Central time zone:

<quote>
v_Sql := 'select ...' from ' || V_table_name ||  '..etc.
         ||' where id = :id || 'group by '|| v_group 
...
Would it flooding the shared pool with unique sql??
</quote>

not the way you show it -- that uses a bind variable -- everyone that used the same "filter" would 
get the same query -- there would be at most 20-30 queries right?  (if replacing 20-30 stored 
procedures)...


As long as the queries are made "consistently the same way" - as the table driven procedure would 
do, this will work just fine (although for just 20/30 queries, not sure it is worth it -- unless 
you expect this to go to 200/300 queries over time).

In this case, either static or dynamic sql would be OK, and the dynamic sql just needs to be 
constructed "in the same manner" so it is not unique -- and this seems to fit that fine. 

5 stars   November 15, 2004 - 4pm Central time zone
Reviewer: A reader 
I need to write a SP takes 5 inputs and returns a refcursor. The query is determined by any of the 
5 input parameters that are non-null, just like the original question on this thread.

I guess your original  answer  still stands? That was for 8i, how would it change using 9iR2 and 
sys_refcursor?  Do I still need to use application contexts?

How would it go?

create or replace context c using p
/

create or replace procedure p (p_i in varchar2,p_j varchar2,
p_rc in out sys_refcursor)
is
l_sql long;
begin
l_sql := 'select ... from ... where 1=1 ';
if (p_i is not null) then
 dbms_session.set_context(...);
 l_sql := l_sql || ' and i = sys_context...'
end if;

...

open p_rc for l_sql;

end;

Does that look right?

Thanks 


Followup   November 15, 2004 - 9pm Central time zone:

a sys_refcursor is just a predefined type, saves you have having to define your own.

would be the same otherwise. 

5 stars Very helpful, but ....   November 17, 2004 - 4am Central time zone
Reviewer: David from UK
Tom,



Great example, as usual, but given the number of possible inputs this does mean 'x' number of 
possible cache'd queries in the shared pool.  We have a similar situation, where many differing 
inputs could result in the same query being executed, but with just different WHERE clauses.  In 
your example using contexts, this means for every combination of inputs there will be another SQL 
statement parsed.  Our query is a large view, so each statement is large in itself (in terms of 
bytes) so quickly fills up the SGA.

My question is this.. Is there any way to do this context trick, but only using 1 parsed query in 
the shared pool?

I've tried doing things like 

...
AND col1 = NVL(p_col1, col1)
AND col2 = NVL(p_col2, col2)
etc..

which works in a logical sense, but runs like a dog due to the indexes not being used.  I've 
resorted to creating multiple views based around the most popular inputs, but would like a neater 
solution.

Any help/comments appreciated.  We're on ORACLE 8i (v8.1.7.4.0)

Great site BTW.

David. 


Followup   November 17, 2004 - 10am Central time zone:

say you have 3 columns...

each column is indexed...

each column could use an index nicely.  

you build one query with:

select * from t
where col1 = nvl(p_col1, col1 )
  and col2 = nvl(p_col2, col2 )
  and col3 = nvl(p_col3, col3 )


now, how many plans would there be?  one.

How many plans do you want to have?  at least 3 in this case.

What would that one plan be?  well, it would be optimal for "col3 = " and suboptimal for the rest.  
consider:



ops$tkyte@ORA817DEV> create table t
  2  as
  3  select rownum c1, rownum c2, rownum c3,
  4         rpad('*',80,'*') data
  5    from all_objects;
 
Table created.
 
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create index t_idx1 on t(c1);
 
Index created.
 
ops$tkyte@ORA817DEV> create index t_idx2 on t(c2);
 
Index created.
 
ops$tkyte@ORA817DEV> create index t_idx3 on t(c3);
 
Index created.
 
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> exec dbms_stats.gather_table_stats( user, 'T', cascade => true );
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> variable c1 number
ops$tkyte@ORA817DEV> variable c2 number
ops$tkyte@ORA817DEV> variable c3 number
ops$tkyte@ORA817DEV> exec :c1 := 1;
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA817DEV> @trace
ops$tkyte@ORA817DEV> alter session set events '10046 trace name context forever, level 12';
 
Session altered.
 
ops$tkyte@ORA817DEV> select *
  2    from t t1
  3   where c1 = nvl(:c1,c1)
  4     and c2 = nvl(:c2,c2)
  5     and c3 = nvl(:c3,c3)
  6  /

tkprof says...

select *
  from t t1
 where c1 = nvl(:c1,c1)
   and c2 = nvl(:c2,c2)
   and c3 = nvl(:c3,c3)
                                                                                                    
  
Rows     Row Source Operation
-------  ---------------------------------------------------
      1  CONCATENATION
      1   FILTER
      1    TABLE ACCESS FULL T
      0   FILTER
      0    TABLE ACCESS BY INDEX ROWID T
      0     INDEX RANGE SCAN (T_IDX3)

so, it would optimize the c3=nvl(:c3,c3) but not all of the combinations

my point at the end here is.....

with this above technique you generate a FINITE (small relatively speaking) number of distinct 
sql's (the goal is not "1" sql or "42" sql's the goal is a finite number of sql's) and you size 
your shared pool accordingly.  You won't have millions of statements, you'll have dozens and that 
you can size for.
 
 

4 stars cursor   November 22, 2004 - 7am Central time zone
Reviewer: bhagirath pesaru from india
very good thanks 


4 stars Using bind variables   November 29, 2004 - 8pm Central time zone
Reviewer: Sanjaya Balasuriya from Colombo, Sri Lanka
Hi Tom,

I'm trying to write a code to get the row count of all the tables my schema. (just to try dynamic 
SQL).

When I write;
  for x in (select table_name from user_tables)
    loop
       execute immediate 'select count(*) from '||x.table_name    into tc;
       dbms_output.putline(tc);
    end loop;

it works fine. But no bind variables have been used. Since "x.table_name" is a PL/SQL variable it 
should be bound no ?

Then I tried;

  for x in (select table_name from user_tables)
    loop
      execute immediate 'select count(*) from :1' into tc using x.table_name;
    end loop;

Now it says "invalid table name". What should be the syntax here ?

Thanks in advance.

-San 


Followup   November 29, 2004 - 8pm Central time zone:

you cannot "bind" an identifier.  the plans change for each on.

you can only bind where you could put a literal.


select * from t where x = 'X'

means 'X' could be bound, but 

select * from 'T' where x = 'X';

shows that 'T' cannot be bound, it doesn't make sense.


You cannot bind the table name -- the plans change for each and every query. 

3 stars Just another idea ...   November 30, 2004 - 3am Central time zone
Reviewer: Michael from Germany
Hi!

Here is another idea to solve the problem:

CREATE TYPE VARCHAR2TABLE AS TABLE OF VARCHAR2(4000);

CREATE OR REPLACE PACKAGE BIND_TEST_PKG  as  
    
  function getCursor3(p_dname in varchar2table := null,
                      p_ename in emp.ename%type := null)
    return sys_refcursor;    
    
end;

CREATE OR REPLACE PACKAGE BODY BIND_TEST_PKG as
  
  function getCursor3(p_dname in varchar2table := null,
                      p_ename in emp.ename%type := null)
    return sys_refcursor
  as
    l_cursor sys_refcursor;
    l_query varchar2(2000) := 'select e.empno, e.ename, e.sal, e.dname ' ||
                              'from   (select e2.empno, e2.ename, e2.sal, d.dname ' ||
                              '        from   emp e2, dept d ' ||
                              '        where  e2.deptno = d.deptno) e ' ||
                              'where  1 = 1 ';
  begin
    if p_dname is not null then
      l_query := l_query || 'and e.dname in (select column_value ' ||
                                             'from  table(cast(:dname as varchar2table))) ';
    else
      l_query := l_query || 'and (:dname is null or 1=1) ';                                         

    end if;
    if p_ename is not null then
      l_query := l_query || 'and e.ename like :ename || ''%'' ';
    else
      l_query := l_query || 'and (:ename is null or 1=1) ';
    end if;
    open l_cursor for l_query using p_dname, p_ename;
    return l_cursor;
  end;        

end;

Test:

SQL> ed
Datei afiedt.buf wurde geschrieben

  1  begin
  2    :c := bind_test_pkg.getCursor3(
  3        p_dname => varchar2table('ACCOUNTING', 'RESEARCH'),
  4        p_ename => 'EMP234');
  5* end;
SQL> /

PL/SQL-Prozedur wurde erfolgreich abgeschlossen.

SQL> print c

     EMPNO ENAME             SAL DNAME
---------- ---------- ---------- --------------
      3340 EMP2340       6378,37 ACCOUNTING
      3343 EMP2343       4213,17 ACCOUNTING
      3345 EMP2345       2360,47 ACCOUNTING
      3347 EMP2347       4192,19 ACCOUNTING
      3349 EMP2349       3216,77 ACCOUNTING
      3342 EMP2342       6875,46 RESEARCH
      3344 EMP2344       3141,76 RESEARCH
      3346 EMP2346       2376,16 RESEARCH
      3348 EMP2348       2926,88 RESEARCH

9 Zeilen ausgewõhlt.

SQL>

It works with simple binds, and binds always all possible filters. When a filter is not needed it 
is bound in a way so that it is obviously - for the database - irrelevant.

It seems to work ... in my test environment.

So, what's your opinion about this idea? 


Followup   November 30, 2004 - 7am Central time zone:

it is very much almost the same as the decode suggestion above by Darko Egersdorfer  from South 
Africa 

4 stars Doubt on Cursor   December 2, 2004 - 4am Central time zone
Reviewer: TH from Manama, BAH
Dear Tom, Please see this.

Table Employees
------------------
EMP_ID,
NAME,
CATEGORY,



CATEGORY can be 'A' or 'B' or 'C'


I have one cursor:


cursor cur_emp(my_type varchar2(1)) 
is select * from employees where 
category=decode(my_type,'1','A','2',  ????? ) ;



My_type will take two values '1' or '2'


If I pass '1' it should  bring employees with category 'A'. And If I pass '2' it should bring the 
employees with category 'B' or 'C'. 


What is the simple solution? 
Can I apss an operator as a parameter ('=' and '<>')?

Thanks in advance,

TH, BAH
 


Followup   December 2, 2004 - 7am Central time zone:

in your case, 


where category in ( decode(my_type,'1','A'),  
                    decode(my_type,'2','B'), 
                    decode(my_type,'2','C') );


you cannot "pass" an operator, that would require an entirely different query with an entirely 
different execution plan. 

5 stars Just Beautiful!   December 4, 2004 - 2am Central time zone
Reviewer: TH from Manma, BAH
Thanks..

Just Beautiful!

TH, BAH
 


5 stars Passing a ResultSet as Ref Cursor   December 4, 2004 - 4pm Central time zone
Reviewer: A reader 
Hi Tom,

I am trying to bind a ResultSet object as a pl/sql ref cursor to an OracleCallableStatement in 
Java. I tried using the deprecated setCursor() with no success. Is this possible in 9i or 10g?

Thanks. 


Followup   December 4, 2004 - 8pm Central time zone:

have you tried the documented ways?

why would you use a deprecated function in new code?


http://asktom.oracle.com/~tkyte/ResultSets/index.html

3 stars Passing a ResultSet as Ref Cursor   December 5, 2004 - 1pm Central time zone
Reviewer: A reader 
I am not trying to bind a cursor returned from a function as a ResultSet. I'm trying to bind a 
ResultSet object as a parameter to a pl/sql procedure. Is there a way to do this other than the 
deprecated setCursor(int, ResultSet)? 


Followup   December 5, 2004 - 7pm Central time zone:

ref cursors go OUT from plsql, there isn't a way to send them IN (where would they "come from"?) 

4 stars Very useful but having a few problems!   December 9, 2004 - 2pm Central time zone
Reviewer: Siobhan from UK
I have created a procedure based on your sample code as follows:

create or replace package pkgSelectClient as

    type rc is REF CURSOR;

    procedure prcSelectClient( p_surname   in varchar2 default NULL,
            p_dob  in date default NULL,
            p_inumber       in number default NULL,
            c_results in out rc);

end pkgSelectClient;
/

create or replace package body pkgSelectClient as

procedure prcSelectClient( p_surname   in varchar2 default NULL,
            p_dob  in date default NULL,
            p_inumber       in number default NULL,
            c_results in out rc) is
    

    l_query  varchar2(512) default 'select * from t_Client where 1 = 1 ';

    cursor l_template is select * from t_Client;
    l_rec  l_template%rowtype;

begin

    if ( p_surname is NOT NULL ) then
        dbms_session.set_context( 'MY_CTX', 'SURNAME', '%'||upper(p_surname)||'%');
        l_query := l_query || ' and SURNAME like sys_context( ''MY_CTX'', ''SURNAME'' ) ';
    end if;

    if ( p_DOB is NOT NULL ) then
        dbms_session.set_context( 'MY_CTX', 'DOB', to_char(p_DOB,'yyyymmddhh24miss'));
        l_query := l_query || ' and DOB > to_date(sys_context( ''MY_CTX'',''DOB'' 
),''yyyymmddhh24miss'') ';
    end if;


    if ( p_INUMBER is NOT NULL ) then
        dbms_session.set_context( 'MY_CTX', 'INUMBERL', p_INUMBER);
        l_query := l_query || ' and INUMBER > to_number(sys_context( ''MY_CTX'',''INUMBER'' )) ';
    end if;

    open c_results for l_query;

end;

end pkgSelectClient;
/
create or replace context MY_CTX using prcSelectClient;
/


When I try and test using the following SQL Script:

VAR C REFCURSOR
execute lcid.prcSelectClient('SIOBHAN','01-JAN-1980',1,:c);
Print c

I get:

BEGIN lcid.prcSelectClient('SIOBHAN','01-JAN-1980',1,:c); END;

      *
ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00306: wrong number or types of arguments in call to 'PRCSELECTCLIENT'
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored


ERROR:
ORA-24338: statement handle not executed


SP2-0625: Error printing variable "c"

Am I doing something stupid (most likely!)?

Also - this is Version  9.2.0.1.0 but it will also have to run against 7.3.4 (!!)  Am I right in 
thinking this will not work and what was would i do the same thing for Version 7

Many thanks
 


Followup   December 9, 2004 - 3pm Central time zone:

you 

execute lcid.prcSelectClient('SIOBHAN','01-JAN-1980',1,:c);

but you programmed:

create or replace package pkgSelectClient as

    type rc is REF CURSOR;

    procedure prcSelectClient( p_surname   in varchar2 default NULL,
            p_dob  in date default NULL,
            p_inumber       in number default NULL,
            c_results in out rc);

end pkgSelectClient;
/

so..... where is PKGSELECTCLIENT in your call. 

3 stars DBMS_SESSION error   December 10, 2004 - 4am Central time zone
Reviewer: Siobhan from UK
Oops - I changed the call to include the package name and now I get a privileges message:
BEGIN lcid.pkgSelectClient.prcSelectClient('SIOBHAN','01-JAN-1980',1,:c); END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 78
ORA-06512: at "LCID.PKGSELECTCLIENT", line 17
ORA-06512: at line 1


ERROR:
ORA-24338: statement handle not executed


SP2-0625: Error printing variable "c"

I checked some of your other questions and someone had been getting the same message but I think 
they had another object with the same name - I definitely do not.
Thanks again
S 


Followup   December 10, 2004 - 10am Central time zone:

did you see your other typo?

pkc vs pkg in the create context.....

you don't have the permission to set the context your package name is wrong. 

4 stars Ishan Bansal   December 10, 2004 - 6am Central time zone
Reviewer: A reader 


5 stars Most helpful!   December 13, 2004 - 5am Central time zone
Reviewer: Siobhan from UK
Thank you very much - although I do feel like a twit for not seeing the silly mistakes!  Sorry to 
bother you with that!  Now I am getting no results but I am sure I can play around with for a while 
now that its running and get that sorted
Thanks Again
 


5 stars Damn Good   December 17, 2004 - 3am Central time zone
Reviewer: Ramesh from Singapore
I see the article is very very usefull.I learnt about Sys_context. I managed to write a package 
with a procedure similar to the start of the web page.When i execute the package from the SQL Plus 
its working fine and am able to see the records.But when i call the same procedure of the package 
from the .NET interface am not empty datatable when am binding to my DataGrid.
If have any idea please help me out.
Many Thanks. 


Followup   December 17, 2004 - 7am Central time zone:

no idea what a ".net interface" is.

or how it could possible affect a stored procedure returning a ref cursor. 

5 stars Damn Good   December 17, 2004 - 4am Central time zone
Reviewer: Ramesh from Singapore
This is follow up for my earlier review.I managed to find the problem. My DataGrid not able to fill 
with the cursor because for one of the column Orderdate the data type is not matching with Oracle 
DataType.
Any how i found the article is very very usefull.Thanks again.
 


5 stars Query utilizing Union   December 21, 2004 - 3pm Central time zone
Reviewer: Ramesh 
Hi Tom,
I have a question on using Application Contexts:

1. If I have a query that uses the Union Operator, to return a result set, how can I use 
Application Context to set the predicate for both the queries? The queries in the Union go against 
a separate set of tables.Is this a viable option?

declare
l_query varchar2(1000);
l_query1 varchar2(500);
l_query2 varchar2(500);
type rc is REF CURSOR;
l_cursor rc;

begin
-- I set the predicates for l_query1 and l_query2 here
l_query:=l_query1||' union '||l_query2;
open l_cursor for l_query;
fetch
close;

end;



 


Followup   December 21, 2004 - 3pm Central time zone:

how "can't" you use an application context in this sense?

I mean, you have a query (forget union, union all, intersect, minus -- whatever) you just have a 
query.

queries have predicates.

you plop your reference to the application context in the predidate -- done. 

5 stars DBA_CONTEXT View   December 21, 2004 - 7pm Central time zone
Reviewer: Ramesh 
Hi Tom,
Could you throw some light on this?

I have a package called LOOKUP, and a procedure called OPEN_INVOICE_SEARCH within this package.
I want to set up the context to use this package and procedure.

So,

bis_web @ BIS_DEV>create or replace context MY_CONTEXT using LOOKUP.OPEN_INVOICE_SEARCH;

Context created.

bis_web @ BIS_DEV>select * from dba_context;

NAMESPACE                      SCHEMA                         PACKAGE
------------------------------ ------------------------------ ------------------------------
MY_CONTEXT                     LOOKUP                         OPEN_INVOICE_SEARCH

bis_web @ BIS_DEV>exec 
lookup.open_invoice_search('oracle%',null,null,null,null,'abc',86979,null,:x,
'2004');
BEGIN lookup.open_invoice_search('oracle%',null,null,null,null,'abc',86979,null,:x,'2004'); END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 62
ORA-06512: at "BIS_WEB.LOOKUP", line 759
ORA-06512: at line 1

I get this error while I try to run this procedure within the package.

Now, if I set the context again specifying just the PACKAGE name, then

bis_web @ BIS_DEV>CREATE OR REPLACE CONTEXT MY_CONTEXT USING LOOKUP;

Context created.

bis_web @ BIS_DEV>SELECT * FROM dba_context;

NAMESPACE                      SCHEMA                         PACKAGE
------------------------------ ------------------------------ -------------
MY_CONTEXT                     BIS_WEB                        LOOKUP

Now, if I run the procedure, I am able to set the context properly and I get the results back.

How I can link a CONTEXT to a particular procedure within a PACKAGE? 

Thanks. 


Followup   December 21, 2004 - 7pm Central time zone:

you associate a context with a top level schema object:

o procedure
o package
o function

not a packaged function, not a packaged procedure. 


that is the way it works.


create or replace context MY_CONTEXT using 
LOOKUP.OPEN_INVOICE_SEARCH;


was looking for a top level schema object owned by someone named LOOKUP 

5 stars Thanks   December 21, 2004 - 8pm Central time zone
Reviewer: Ramesh 
Hi Tom,
Thanks for the clarification. 
 


5 stars Cursor   December 22, 2004 - 2am Central time zone
Reviewer: Vithal from INDIA
Hi Tom,

is it posibal to use for loop for ref cursor if yes then how?
and if want to use case statment without using if else and and ref cursot then how can i do? can 
please help me out.



Thanks
Vithal 


Followup   December 22, 2004 - 9am Central time zone:


   open c for ....
   loop
      fetch c into ....;
      exit when c%notfound;
      process record
   end loop;
   close c;


No idea what the case comment was about. 

4 stars How to user rowid in a ref cursor   December 25, 2004 - 1am Central time zone
Reviewer: Abubaker Khered from Jeddah, KSA
Hi,

I am having a procedure which returns (create_by, create_date) from a particular table based on two 
parameters which I am passing 1-table name and 2-rowid. But it gives me error whenever I run it, 
the error is regarding the type and format of the rowid. It works ok when I hard code the rowid.

The procedure is:

CREATE OR REPLACE PROCEDURE PRC_CRE_MDF (i_table   in   varchar2,
                                         i_rowid   in   varchar2,
                                         o_cre_by  out  varchar2,
                                         o_cre_dt  out  date     ) is
  type      RefType IS ref cursor;
  cr_ref    RefType;
            
  l_query   varchar2(512);
  l_cre_by  varchar2(30);
  l_cre_dt  date;
  
begin
  l_query := 'SELECT create_by, create_date FROM '||i_table||
             ' WHERE rowid = '|| '''i_rowid''' ;
  Open  cr_ref for  l_query;
  Fetch cr_ref Into l_cre_by, l_cre_dt ;
     IF cr_ref%FOUND THEN
        o_cre_by := l_cre_by ;
        o_cre_dt := l_cre_dt ;
     END IF;
  Close cr_ref;
end;

This is the error ORA-01410.
But when I change '''i_rowid'''  to 'i_rowid' I recived this error ORA-00904.

Kindly advice on how to solve this problem.

Regards,
Abubaker
 


Followup   December 25, 2004 - 9am Central time zone:

BIND VARIABLES he screams at the top of his lungs

bind variables, bind variables, bind variables (just like spam, spam, spam from monty python...)


you gotta BIND, BIND, BIND....



ops$tkyte@ORA9IR2> CREATE OR REPLACE PROCEDURE PRC_CRE_MDF (i_table   in        varchar2,
  2                                           i_rowid   in   varchar2,
  3                                           o_cre_by  out  varchar2,
  4                                           o_cre_dt  out  date     )
  5  is
  6  begin
  7      execute immediate
  8      'select create_by, create_date
  9         from ' || i_table || '
 10       where rowid = :x' into o_cre_by, o_cre_dt using i_rowid;
 11  exception
 12      when no_data_found then NULL; -- you ignored this before
 13                                    -- that is PROBABLY a BUG
 14                                    -- in your logic though, think
 15                                    -- about that.
 16  end;
 17  /
 
Procedure created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table t ( create_by varchar2(30), create_date date );
 
Table created.
 
ops$tkyte@ORA9IR2> insert into t values ( user, sysdate );
 
1 row created.
 
ops$tkyte@ORA9IR2> commit;
 
Commit complete.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2      l_cre_by varchar2(30);
  3      l_cre_dt date;
  4  begin
  5      for x in ( select rowid rid from t )
  6      loop
  7          prc_cre_mdf( 'T', x.rid, l_cre_by, l_cre_dt );
  8          dbms_output.put_line( l_cre_by || ' ' || l_cre_dt );
  9      end loop;
 10  end;
 11  /
OPS$TKYTE 25-DEC-04
 
PL/SQL procedure successfully completed.
 
 

5 stars Thanks, and another question?   December 26, 2004 - 1am Central time zone
Reviewer: Abubaker Khered from Jeddah, KSA
Dear Tom,

Thank you so much for your help.

I have another small question? 
In our system we have in some of the tables 
(a)  create_by
     create_date

and in some other tables we go
(b)  create_by
     create_date
     modify_by 
     modify_date

What is the best way to determined whether this table is (a) or (b) and accordingly returns two or 
four columns in the above procedure?

Thanks in advance

Regards,
Abubaker
 


Followup   December 26, 2004 - 12pm Central time zone:

they invoker should do that -- eg, you PACKAGE these procedures and overload it.  the invoker will 
either call one with 4 outputs, or with 2.  


(i actually don't like the premise of this routine, dynamic sql that should be avoided, the invoker 
should have SELECTED this stuff in the first place) 

3 stars Thanks   December 27, 2004 - 12am Central time zone
Reviewer: Abubaker Khered 
Thanks 


5 stars Interval Sum   December 29, 2004 - 8am Central time zone
Reviewer: Vithal from India
I had a table as bello
Error_id  Error_no   Err_Date
1         12               12-dec-2004 12:30 PM
2         11               12-dec-2004 12:32 PM
3         22               12-dec-2004 12:39 PM
4         3                12-dec-2004 12:42 PM
5         45               12-dec-2004 12:45 PM
6         6                12-dec-2004 12:50 PM
7         78               12-dec-2004 12:51 PM
8         8                12-dec-2004 12:54 PM
9         45               12-dec-2004 12:56 PM
10        4                12-dec-2004 12:59 PM

I want the sum of the errors for every 20 min interval 
like

start time  end time  errors
12:20       12:40     ?
12:40       13:00     ?
can u please guide on this..
 


Followup   December 29, 2004 - 10am Central time zone:

you just need to take your DATE field and make it conform to 20 minute intervals.

so xx:00:00 .. xx:19:59 => xx:00:00
   xx:20:00 .. xx:39:59 => xx:20:00
   xx:40:00 .. xx:59:59 => xx:40:00


we can do that with simple division:

ops$tkyte@ORA9IR2> select r, int
  2    from (
  3  select r, int,
  4         lag(int) over (order by r) last_int,
  5             lead(int) over (order by r) next_int
  6    from (
  7  select rownum r, trunc(rownum/20) int
  8    from all_objects
  9   where rownum <= 60
 10         )
 11         )
 12   where int <> last_int or int <> next_int
 13  /
 
         R        INT
---------- ----------
        19          0
        20          1
        39          1
        40          2
        59          2
        60          3
 
6 rows selected.
 


r's 1..19 => 0
   20..39 => 1
   40..59 => 2


so:

ops$tkyte@ORA9IR2> select dt,
  2         trunc(dt,'hh') + (trunc(to_char(dt,'mi')/20) * 20)/24/60 newdt
  3    from (
  4  select trunc(sysdate,'hh')+(rownum-1)/24/60 dt
  5    from all_objects
  6   where rownum <= 60
  7         )
  8  /
 
DT                   NEWDT
-------------------- --------------------
29-dec-2004 10:00:00 29-dec-2004 10:00:00
...
29-dec-2004 10:19:00 29-dec-2004 10:00:00
29-dec-2004 10:20:00 29-dec-2004 10:20:00
...
29-dec-2004 10:38:00 29-dec-2004 10:20:00
29-dec-2004 10:39:00 29-dec-2004 10:20:00
29-dec-2004 10:40:00 29-dec-2004 10:40:00
...
29-dec-2004 10:59:00 29-dec-2004 10:40:00
 
60 rows selected.



You would select newdt (as a function of err_date) and group by that. 

5 stars ORA-08103   December 30, 2004 - 5am Central time zone
Reviewer: Vithal from India
Hi Tom,

I am creating a table using select from other tables.

but after an 1 hour I got the error ORA-08103: object no longer exists But the table is exists in 
the database.. then why this error came in this kind of situation. the table contain 1,000,000,000 
data.

can you please guide on this...
 


Followup   December 30, 2004 - 10am Central time zone:

the 8103 most likely means one of the tables you were querying had DDL applied to it (like an alter 
table exchange partition, truncate partition, truncate table, drop table)...

the query will run fine -- as long as the space occupied by the dropped/truncated/altered object is 
not reused.

if it gets reused -- then the old data is wiped out and you get "object no longer exists"


so, any chance -- any chance at all that the objects you were QUERYING were being worked on by 
someone else. 

5 stars Interval Sum   December 30, 2004 - 9am Central time zone
Reviewer: A reader 
Hi Tom,

Still not clear can u please give me another example.

 


Followup   December 30, 2004 - 11am Central time zone:

kidding right?

I showed a function to take any date/time as input and return a date time rounded to the 00, 20, 40 
minute interval.

group by it.


select f(date_column), sum(whatever) 
  from any_table
 group by f(date_column)

wah-lah -- data aggregated to the intervals 00, 20, 40?


select trunc(dt,'hh') + (trunc(to_char(dt,'mi')/20) * 20)/24/60, 
       <YOUR AGGREGATES>
  from t
 group by trunc(dt,'hh') + (trunc(to_char(dt,'mi')/20) * 20)/24/60 

5 stars transpose in SQL   January 5, 2005 - 5am Central time zone
Reviewer: Vithal from INDIA
Hi Tom,

If i want to show the rows in columns like

Empno
--------
7722
1234
4432
1234
2334
1234

with

EMP    EMP1 EMP2 EMP3 EMP4 EMP5 EMP6
------------------------------------
EMPNO  7722 1234 4432 1234 2334 1234


Can u please guide on this


 


Followup   January 5, 2005 - 9am Central time zone:

search this site for 

pivot 

5 stars Viable Alternative?   January 27, 2005 - 6pm Central time zone
Reviewer: Kyle from Orlando, FL
Tom:

Is this a viable alternative to using an application context?  I have commented the changes to your 
example...

create or replace 
procedure my_procedure( p_ename   in varchar2 default NULL,
                        p_hiredate  in date default NULL,
                        p_sal       in number default NULL)
as
    type rc is REF CURSOR;
  
    l_cursor rc;
    l_query  varchar2(512)
             default 'select * from emp where 1 = 1 ';

    cursor l_template is select * from emp;
    l_rec  l_template%rowtype;

    -- initialize an array of varchar2 to hold the 
    -- dymanic variable values 
    type var_array is varray(3) of varchar2(25); 
    varr var_array := var_array(); 
    i integer;

begin

    if ( p_ename is NOT NULL ) then
        -- extend the array, add the variable's value and 
        -- append to the query string 
        varr.EXTEND; 
        varr(varr.LAST) := '%'||upper(p_ename)||'%'; 
         l_query := l_query || 
            ' and ename like :x';
    end if;
 
    if ( p_hiredate is NOT NULL ) then
        varr.EXTEND; 
        varr(varr.LAST) := to_char(p_hiredate,'yyyymmddhh24miss'); 
        l_query := l_query ||
            ' and hiredate > to_date(:x, ''yyyymmddhh24miss'') ';
    end if;

    if ( p_sal is NOT NULL ) then
        varr.EXTEND; 
        varr(varr.LAST) := to_char(p_sal); 
        l_query := l_query ||
            ' and sal > to_number(:x) ';
    end if;

    -- loop through the remaining array elements and initialize them 
    -- append to the query string 
    for i in (nvl(varr.last, 0) + 1)..varr.limit loop
        varr.extend;
        varr(varr.last) := 'X';
        l_query := l_query || ' AND :x = ''X''';
    end loop;

    p( l_query );
 
    -- execute the query using the array elements 
    open l_cursor for l_query USING varr(1), varr(2), varr(3);

    loop
        fetch l_cursor into l_rec;
        exit when l_cursor%notfound;

        dbms_output.put_line( l_rec.ename || ',' ||
                              l_rec.hiredate || ',' ||
                              l_rec.sal );
    end loop;

    close l_cursor;
end;


It works, and is using bind variables, but I'm concerned about optimization being affected by 
adding "AND :x = 'X'" multiple times in the WHERE clause?  I'm thinking its ok, since 1 = 1 gets 
optimized away.

Thank you for your time!  It is much appreciated!  I always learn from your examples.

Kyle
 


Followup   January 28, 2005 - 7am Central time zone:

ctl-f for "Darko" on this page.

that would be a cleaner way to do this, no array needed. 

5 stars Viable Alternative   January 28, 2005 - 9am Central time zone
Reviewer: Kyle from Orlando, FL
Tom:

Ahhh, how did I miss that?  So something like this?

CREATE OR REPLACE 
procedure my_procedure2( p_ename   in varchar2 default NULL,
                        p_hiredate  in date default NULL,
                        p_sal       in number default NULL)
as
    type rc is REF CURSOR;

    l_cursor rc;
    l_query  varchar2(512)
             default 'select * from emp where 1 = 1 ';

    cursor l_template is select * from emp;
    l_rec  l_template%rowtype;

    c_p_ename varchar2(25) default null;
    c_p_hiredate varchar2(25) default null;
    c_p_sal varchar2(25) default null;
begin

    if ( p_ename is NOT NULL ) then
        c_p_ename := '%' || upper(p_ename) || '%';
        l_query := l_query ||
            ' and ename like :x';
    else
         l_query := l_query ||
            ' and :x is null';
    end if;

    if ( p_hiredate is NOT NULL ) then
        c_p_hiredate := to_char(p_hiredate,'yyyymmddhh24miss');
        l_query := l_query ||
            ' and hiredate > to_date(:x, ''yyyymmddhh24miss'') ';
    else
         l_query := l_query ||
            ' and :x is null';
    end if;

    if ( p_sal is NOT NULL ) then
        c_p_sal := to_char(p_sal);
        l_query := l_query ||
            ' and sal > to_number(:x) ';
    else
         l_query := l_query ||
            ' and :x is null';
    end if;

    p( l_query );

    open l_cursor for l_query USING c_p_ename, c_p_hiredate, c_p_sal;

    loop
        fetch l_cursor into l_rec;
        exit when l_cursor%notfound;

        dbms_output.put_line( l_rec.ename || ',' ||
                              l_rec.hiredate || ',' ||
                              l_rec.sal );
    end loop;

    close l_cursor;
end;
/


Thanks again,

Kyle
 


Followup   January 28, 2005 - 2pm Central time zone:

    if ( p_hiredate is NOT NULL ) then
        c_p_hiredate := to_char(p_hiredate,'yyyymmddhh24miss');
        l_query := l_query ||
            ' and hiredate > to_date(:x, ''yyyymmddhh24miss'') ';
    else
         l_query := l_query ||
            ' and :x is null';
    end if;

would just be:

    if ( p_hiredate is NOT NULL ) then
        l_query := l_query ||
            ' and hiredate > :x ';
    else
         l_query := l_query ||
            ' and :x is null ';
    end if;

and same with the "to_number", you would be BINDING a date, binding a number -- no conversion 
needed or desired.

using '%'||upper(p_ename)||'%', p_hiredate, p_sal;


 

5 stars Viable Alternative   January 28, 2005 - 9am Central time zone
Reviewer: Kyle from Orlando, FL
Tom:

Sorry to immediately reply to my own post, but one thing I just realized is that in my last 
example, you must have a fixed number of bind variables.  When using an application context or the 
array example I provided previously, you do not have this constraint.

Kyle
 


Followup   January 28, 2005 - 2pm Central time zone:

you do have a fixed MAX number of binds, that is all you need (and you always have that... a 
MAXIMUM, else you would not know how many IF's to code) 

5 stars Viable Alternative   January 31, 2005 - 3pm Central time zone
Reviewer: Kyle from Orlando, FL
Hi Tom:

For clarification, in the followup you provided before, I don't think passing 
'%'||upper(p_ename)||'%' in the open..using statement will work properly.  Wouldn't it evaluate to 
'%%' when p_ename is NULL, which would be false when checking if the variable is null in the query 
string?  Wouldn't the proper method be to pass the variable p_ename in the open..using statement 
and use the following to build the query string:

    if ( p_ename is NOT NULL ) then
        l_query := l_query ||
            ' and ename like ''%'' || upper(:x) || ''%''';
    else
         l_query := l_query ||
            ' and :x is null';
    end if;

Thanks again,

Kyle 


Followup   January 31, 2005 - 4pm Central time zone:

yup, good point.

 

5 stars Great discussion   February 3, 2005 - 3am Central time zone
Reviewer: Martin from UK
This is a great discussion thread, however, I've recently been looking into the possibility of 
tuning some of our SQL by utilising scalar subquery caching, and while i've had great success when 
dealing with PL/SQL function calls etc, I thought  i'd look into maybe applying the same techniques 
to certain 
SQL function calls (such as user, sysdate etc). 

However, these type of function calls perform significantly worse when using a dual-subquery type 
approach, which I assume means that they're already cached for us?

Can you quickly outline the rules behind what types of function calls *may* benefit from subquery 
caching? And, is there a document somewhere which describes it in more detail?

Thanks in advance 


Followup   February 3, 2005 - 1pm Central time zone:

user, sysdate, etc are semi-bind variables.  

select to_char(sysdate,'sssss') from one_billion_row_table_that_takes_30_seconds_to_scan;

will return a constant value.


PLSQL called from SQL can benefit from this


select a, f(a) from t;

can benefit from 

select a, (select f(a) from dual) from t;

and even more so perhaps (if the sort is less work than the f(a) call)

select a, (select f(a) from dual) from (select a from t order by a);


that will call f once typically for each distinct value of A, so if A has 100 distinct values, but 
T has 100,000 rows -- you might find sorting and then calling is better. 

3 stars Cursors   February 3, 2005 - 2pm Central time zone
Reviewer: JOJEDA from Costa Rica
Tom, I need to create a package with refcursors to store the sql stmt and get the resultset on the 
client, do you have a better way to to do that??

CREATE OR REPLACE PACKAGE BNFTEST.PKG_RC
AS
  TYPE RC IS REF CURSOR;
  c1 varchar2(255) default 'select sysdate from dual';
  c2 varchar2(255) default 'select user from dual';
  PROCEDURE SP_RC(p_refcur OUT PKG_RC.RC, p_rc in number);
END PKG_RC;
/

CREATE OR REPLACE PACKAGE BODY BNFTEST.PKG_RC is
PROCEDURE SP_RC(p_refcur OUT PKG_RC.RC, p_rc in number) 
IS
  BEGIN
    CASE
      WHEN p_rc=1 THEN
        OPEN p_refcur FOR c1;
      WHEN p_rc=2 THEN
        OPEN p_refcur FOR c2;
    END CASE;
  END SP_RC;
END PKG_RC;
/

Thanks, 


Followup   February 4, 2005 - 1am Central time zone:

CREATE OR REPLACE PACKAGE BNFTEST.PKG_RC
AS
  TYPE RC IS REF CURSOR;
  PROCEDURE SP_RC(p_refcur OUT PKG_RC.RC, p_rc in number);
END PKG_RC;
/

CREATE OR REPLACE PACKAGE BODY BNFTEST.PKG_RC is
PROCEDURE SP_RC(p_refcur OUT PKG_RC.RC, p_rc in number) 
IS
  BEGIN
    CASE
      WHEN p_rc=1 THEN
        OPEN p_refcur FOR select sysdate from dual;
      WHEN p_rc=2 THEN
        OPEN p_refcur FOR select user from dual;
    END CASE;
  END SP_RC;
END PKG_RC;
/



never ever ever use dynamic sql when static sql will do. never. 

3 stars input/output in compile-time   February 23, 2005 - 6am Central time zone
Reviewer: Harp from Austria
Hi Tom!
I just used some of the examples you gave about cursor which helped me much. One thing I would like 
to know is this: 
is there anyway I can know whether the parameters of a stored procedure is an input, output or 
input_output after preparing the sql-statement but before executing the statement (via odbc). If 
you have an example, I would appreciate it too. 

Thanks 
Harp 


 


Followup   February 23, 2005 - 9am Central time zone:

I don't program ODBC at all but

ALL_ARGUMENTS (dictionary view) contains that as well dbms_describe.describe_procedure will work. 

4 stars Where are package global variables and types stored?   March 3, 2005 - 5pm Central time zone
Reviewer: Andrew from Wellington, NZ
Just following up on your ALL_ARGUMENTS comment, I cannot find anywhere in any Oracle 
documentation: where can I get all the global variables, constants, and types for a given package, 
besides parsing user_source?  Surely there must be a data_dictionary view that the compiler & 
debugger uses?

Also, are there JDBC metadata classes that are equivalent to ALL_PROCEDURES and ALL_ARGUMENTS? 


Followup   March 3, 2005 - 5pm Central time zone:

surely ?  why surely?

no, there is not, they are not exposed in a dictionary views.


I don't know if there are jdbc metadata classes or not. 

5 stars how to solve the problem in a 9i rac env&#65311;   March 4, 2005 - 9am Central time zone
Reviewer: '.......' from east
hello ,tom
   This is very helpful!but can we use the sys_context function  in a 9i RAC environment
to bind variables like this?if can't, then how to return the ref cursor based on each non-ull 
parameter with variables binded? help me out:
if a is not null then
   vsql := vsql || ' and col1=:bind_a';
end if;
...
if a is not null and b is null then
   open vref_cursor for vsql using a;
end if;
if a is null and b is not null then
   open vref_cursor for vsql using b;
end if;
if a is not null and b is not null then
   open vref_cursor for vsql using a,b;
end if;
...
return vref_cursor;

--------------------------------------
if the inputs passed in are a, b,c,d , e...and ...?
Thanks

with regards,

'.......'
 


Followup   March 4, 2005 - 9am Central time zone:

sure you can -- rac won't affect this.

or ctl-f for " a non dynamic alternative " if you don't want to use them. 

5 stars help in a 9irac env   March 4, 2005 - 10am Central time zone
Reviewer: '............' from east
thanks ,tom
but there is a note in the Oracle9i SQL Reference( Part Number A96540-01) document:
SYS_CONTEXT returns session attributes. Therefore, you cannot use it in parallel queries or in a 
Real Application Clusters environment. and i am  confused and worrying about my pl/sql code won't 
work well when the database use a rac envionment.
here is the url:
http://www.lc.leidenuniv.nl/awcourse/oracle/server.920/a96540/functions122a.htm#1038178
 


Followup   March 4, 2005 - 11am Central time zone:

http://download-west.oracle.com/docs/cd/B14117_01/server.101/b10759/functions150.htm#sthref1809
they fixed that doc issue. 

5 stars   March 31, 2005 - 3am Central time zone
Reviewer: govindarajn from chennai INDIA
can i transfer the tables from  one user to another

ie the user A having 10 tables and user B having 0 tables.now i want TO copy all the tables to user 
.
WITHOUT EXPORT/IMPORT  UTILITY IS IT POSSIBLE TO COPY A TABLES FROM ONE USER TO ANOTHER? 


Followup   March 31, 2005 - 7am Central time zone:

connect b
create table my_copy_of_a_table as select * from a.table_name;

do that for each table. 

3 stars Do it on the application side   March 31, 2005 - 2pm Central time zone
Reviewer: A reader 
On the orignal post of a gerneric sql engine:  if using a java app server,  would it not be a 
simple solution build the query on the java side which make the conncationation of ?, ?, ? and bind 
varables very easy.  I do know your view on having SQL on the app server, but it seems it would 
make the code simplier and eaiser to read.  Performace should be the same, or quicker since there 
is no "context" component. 


Followup   March 31, 2005 - 3pm Central time zone:

but a context is a bind, and I'd rather have the SQL in the server -- where I can find it, fix it, 
tune it, correct it.

Also, I can reuse this routine all over the place, by any language/environment

And I don't have to convince the java coders that binding in SQL is important ;)  Still gotta get 
them to call the routine with binds, but that is easier than telling them they have to bind the 
dynamically constructed SQL.

Besides, the original poster might have wanted another plsql routine to have been the recipient. 

3 stars Do it on the application side   April 1, 2005 - 1pm Central time zone
Reviewer: A reader 
How about a java stored procedure? 


Followup   April 1, 2005 - 1pm Central time zone:

why? you are in the database, working with data.  there is no language better for doing SQL in 
Oracle than plsql. 

4 stars How to Extend this technique to Joining multiple Tables   April 11, 2005 - 11am Central time zone
Reviewer: A reader from Peoria IL
Tom,

This thread has great relevance to building search procedures for web applications.  A generic 
search in our application involves variable number of paramaters spanning multiple tables. 
Depending on what parameters are set, I may need to join different set of tables. Following is an 
illustrative example of a search function's signature:

CREATE OR REPLACE PACKAGE equipment
AS
  
  FUNCTION equipment_details (
           equipment_id_in equipments.equipment_id%TYPE DEFAULT NULL
          ,equipment_industry_in equipements.equipment_industry%TYPE DEFAULT NULL 
          ,equipmment_owner_id_in equipment_owners.equipment_owner_id%TYPE DEFAULT NULL
          ,equipment_dealer_id_in equipment_owner.dealer_id%TYPE DEFAULT NULL
          ,eqp_dealer_account_id_in eqp_owner_dealer_csutomers.dealer_account_id_in DEFAULT NULL
        :
        :
        :

       )
   RETURN config.RefCursorTyp;


END equipment;


Typically, some of these paramters result in one or more predicates against one table. I can use 
SYS_CONTEXT to add adequate predicates against one table.  Do you have an example (illustrating a 
generic approach to this situation)where I can also vary the table list and join predicates besides 
predicates filtering rows of a table?

The three approaches we have used so far are:

1. If then else logic to assemble a custom (dynamic) query in response to each permutation of the 
input paramaters.  I find this approach somewhat difficult to maintain and extend.  The code 
quickly gets unwildy with each additional search condition added leading to ever more permutations 
and somewhat tricky debugging as each possible search path now needing to be retested.

2. If then else logic to attach a specific predetermined query to the result set specific query (in 
some situations you can end up with 3-4 dozen pre-set permutations (queries)).

3. Join all the underlying tables (default query) and add table filtering perdicates (projections) 
depending on the parameters. While it is easier to maintain and extend,  I am sure joining all 10 
tables in response to every situation is not a a great idea. In most situations only a sub-set of 
tables should be joined.  

Any examples or inputs would be greatly appreaciated. 


Followup   April 11, 2005 - 11am Central time zone:

you would vary the table list?  Seems if you SELECT a column from a table, you would either

a) always select that column
b) never select that column

regardless of the predicates?  

I can see adding predicates that are SUBQUERIES, but I cannot envision where the predicates would 
drive the set of tables in the from clause for a search?


select * from emp;   -- no predicates.

select * from emp where job = sys_context() -- filter on job

select * from emp where job = sys_context()
and deptno in (select deptno from dept where dname = sys_context() ) -- filter on job and 
departement name

and so on...

 

5 stars   April 20, 2005 - 4pm Central time zone
Reviewer: A reader 
Could you please give an example of refcursor/context object with BULK COLLECT?
When I bulk collect into a collectin; I am getting more records than that are returned by the query 
(based on which ref cursor is opened).

Thanks in advance. 


Followup   April 20, 2005 - 9pm Central time zone:

how about you give me the example that fails.... 

5 stars   April 20, 2005 - 5pm Central time zone
Reviewer: A reader 
I have following collections at SQL level:
create or replace rec as object
(
job varchar2(20),
hdate date
);

create or replace type emp_tab 
as varray of rec(10);

-----------------

I have package like this, (i have not included package specififcation here):


create or replace package body pack1
is
type job_type is table of emp.job%type index by binary_integer;
type date_type is table of emp.hiredate%type index by binary_integer;

procedure p1( i_name  in  emp.ename%type,
              i_sal   in  emp.sal%type,
              o_rec   out emp_tab
        )
as

l_qry varchar2(4000) 'select job,hiredate from emp';

v_job job_type;
v_date date_type;

c1 ref cursor;

begin

 DBMS_SESSION.set_context ('dctx1', 'name',  (i_name));
 l_qry := l_qry || ' and ename = sys_context( ''dctx1'', ''name'' ) ';


open c1 for l_qry;

fetch c1 bulk collect into v_job,v_date;

o_rec := emp_tab(rec(null,null));

for i in v_job.first .. v_job.last
loop
 o_rec.extend;
 o_rec(i):=  rec(v_job(i),v_date(i));
end loop;

end p1;

end pack1;


Problem:
I have captured l_qry and it is giving correct result. But, when i bulk collect into collection it 
is fetching more records (duplicates). What am I doing wrong?

Please Help. 


Followup   April 20, 2005 - 9pm Central time zone:

give entire example from start to finish, that I can cut and paste and run

(and why not just

select rec( job, hiredate ) bulk collect into rec from emp where ....

and be done with it? 

5 stars   April 21, 2005 - 8am Central time zone
Reviewer: A reader 
Thanks for your response! But when I said 

select rec(job,date) into rec from ...

i am not getting any data, no records are fetched.

please help. 


Followup   April 21, 2005 - 11am Central time zone:

*give ENTIRE example*

something i can cut and paste and run

small, concise, yet 100% complete. 

5 stars   April 21, 2005 - 12pm Central time zone
Reviewer: A reader 
At SQL level:
------------------
create or replace rec as object
(
job varchar2(20),
hdate date
);

create or replace type emp_tab 
as varray of rec(10);

--------------------------------------


create or replace package pack1
is

type cur is ref cursor;

procedure p1( i_name  in  emp.ename%type,
              i_sal   in  emp.sal%type,
              o_rec   out emp_tab
            );
end pack1;

-----------------------


create or replace package body pack1
is
type job_type is table of emp.job%type index by binary_integer;
type date_type is table of emp.hiredate%type index by binary_integer;

procedure p1( i_name  in  emp.ename%type,
              i_sal   in  emp.sal%type,
              o_rec   out emp_tab
        )
as
--***as per your suggestion; i am bulk collecting here, but records are not retrieved
l_qry varchar2(4000) 'select rec(job,hiredate) into o_rec from emp';

v_job job_type;
v_date date_type;

c1 cur;

begin

 if i_name is not null then
   DBMS_SESSION.set_context ('dctx1', 'name',  (i_name));
   l_qry := l_qry || ' and ename = sys_context( ''dctx1'', ''name'' ) ';
 end if;

/*
-----------   l_qry has right output
open c1 for l_qry;

----***here i am getting duplicates
fetch c1 bulk collect into v_job,v_date;

o_rec := emp_tab(rec(null,null));

for i in v_job.first .. v_job.last
loop
 o_rec.extend;
 o_rec(i):=  rec(v_job(i),v_date(i));
end loop;
*/


end p1;

end pack1;

 


Followup   April 22, 2005 - 9am Central time zone:

you do realize that NONE of your statements actually "run" in sqlplus right?  missing create 
context, the varray define is wrong, the package is not "doing anything"

An example would be "here, run this as scott/tiger and it'll show you this"

as it is, I'm not really sure "what" we are supposed to be looking for.


Now, if you code the package body like this:

ops$tkyte@ORA9IR2> create or replace package body pack1
  2  is
  3  type job_type is table of emp.job%type index by binary_integer;
  4  type date_type is table of emp.hiredate%type index by binary_integer;
  5
  6  procedure p1( i_name  in  emp.ename%type,
  7                i_sal   in  emp.sal%type,
  8                o_rec   out emp_tab
  9          )
 10  as
 11     v_job job_type;
 12     v_date date_type;
 13     c1 cur;
 14     l_qry varchar2(4000) := 'select job, hiredate from emp where 1=1 ';
 15  begin
 16   if i_name is not null then
 17     DBMS_SESSION.set_context ('dctx1', 'name',  (i_name));
 18     l_qry := l_qry || ' and ename = sys_context( ''dctx1'', ''name'' ) ';
 19   end if;
 20
 21   open c1 for l_qry;
 22
 23    fetch c1 bulk collect into v_job,v_date;
 24    for i in 1 .. v_job.count
 25    loop
 26       dbms_output.put_line( v_job(i) || ', ' || v_date(i) );
 27    end loop;
 28  end p1;
 29
 30  end pack1;
 31  /
 
Package body created.
 
ops$tkyte@ORA9IR2> show err
No errors.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2     x emp_tab;
  3  begin
  4      pack1.p1( 'KING', null, x );
  5  end;
  6  /
PRESIDENT, 17-NOV-81
 
PL/SQL procedure successfully completed.


there are no duplicates (if you believe you are getting them, you will ned to supply a 100% 
complete, yet concise/small testcase that ANYONE can run from start to finish)


but you don't need all of those little tables:


ops$tkyte@ORA9IR2> create or replace package body pack1
  2  is
  3
  4  procedure p1( i_name  in  emp.ename%type,
  5                i_sal   in  emp.sal%type,
  6                o_rec   out emp_tab
  7          )
  8  as
  9     l_qry varchar2(4000) := 'select rec(job, hiredate) from emp where 1=1 ';
 10  begin
 11   if i_name is not null then
 12     DBMS_SESSION.set_context ('dctx1', 'name',  (i_name));
 13     l_qry := l_qry || ' and ename = sys_context( ''dctx1'', ''name'' ) ';
 14   end if;
 15
 16   execute immediate l_qry bulk collect into o_rec;
 17   for i in 1 .. o_rec.count
 18   loop
 19      dbms_output.put_line( o_rec(i).job || ', ' || o_rec(i).hdate );
 20   end loop;
 21  end p1;
 22
 23  end pack1;
 24  /
 
Package body created.
 
ops$tkyte@ORA9IR2> show err
No errors.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2     x emp_tab;
  3  begin
  4      pack1.p1( 'KING', null, x );
  5  end;
  6  /
PRESIDENT, 17-NOV-81
 
PL/SQL procedure successfully completed.
 

5 stars   April 22, 2005 - 9am Central time zone
Reviewer: A reader 
Thanks a lot for your example.

I did the way you suggested:
execute immediate l_qry bulk collect into o_rec;

This is the error I am getting:
ORA-00932: inconsistent datatypes: expected UDT got CHAR


Thanks in advance! 


Followup   April 22, 2005 - 10am Central time zone:

example or I just ignore it.

I gave you one -- that with your creates and such (once fixed) runs.  from start to finish.

you have to give me one from start to finish that doesn't run (and be AS SMALL AS POSSIBLE) 

5 stars XYZ   April 22, 2005 - 11am Central time zone
Reviewer: A reader 
How can I clear the values assigned to Session Context?

The reason I am asking this is if I don't use Session Context I am getting correct results, with it 
I am getting incorrect output.

Thanks in advance! 


Followup   April 22, 2005 - 11am Central time zone:

provide EXAMPLE

with or without a context set, should not matter -- the results are "deterministic" as they say. 

5 stars grant privilege for alter trigger   April 27, 2005 - 5am Central time zone
Reviewer: Ronald Chan from Fortress Hill, Hong Kong
It is possible to grant just alter trigger privilege to other users?
so that alter trigger is the only action this user can do. 


Followup   April 27, 2005 - 8am Central time zone:

http://www.oracle.com/technology/oramag/oracle/05-jan/o15asktom.html
see the last part of that page -- it is about selectively granting alter system on user_dump_dest, 
but the same technique applies. 

5 stars   May 5, 2005 - 11am Central time zone
Reviewer: Alex 
Tom,

Just curious, why does Oracle allow you to select..into in a cursor or ref cursor?  It's not really 
correct to do that right?  Does Oracle just ignore nonsense like this?

I've come across other nonsensical code in our app that apparently Oracle pays no attention to such 
as

select...from..where col1 is null or col1 is not null.
 
  


Followup   May 5, 2005 - 12pm Central time zone:

well, you can select CURSOR( select ...) from table, so the semantics are there for doing so, yes. 

4 stars connection pool and SYS_CONTEXT   May 23, 2005 - 2pm Central time zone
Reviewer: reader from USA
Is it advisable to use SYS_CONTEXT in connections pool environment? Just a dump question, as we are 
using the connection pools from websphere in high transactions OLTP system.

Thanks, 


Followup   May 23, 2005 - 7pm Central time zone:

as long as you explicity set the context values before using them and be aware that they could 
contain legacy values from prior "sessions" 

4 stars What is the impact on session cache   May 25, 2005 - 11am Central time zone
Reviewer: reader from USA
Thanks, I was looking for some Oracle document how the sys context internally behaving but could 
not find the detail one.
 
We have one query in stored procedure (person search) that is executed 3000 times a day by call 
center users. The user application is in Java and application server using connection pooling. 

The "Where" conditions are different each time, so developer built the dynamic query to accept 
different conditions. We found around 5000 different versions in sqlarea. Sometimes performance is 
also not good with this query. So we decided to tune this without changing business logic by 
binding all variables in this query (around 21 different variables) using sys_context. 

Now the question is what is the tradeoff between allowing hard parse every time verses setting 
sys_context. Is the query going to perform slow because there must be some overhead to set the sys 
context in the memory?

What it is the impact on the session memory when we set these many contexts? Do we need any special 
setting to avoid any memory issue due to this?  We are using session_cache_cursor =100. Oracle 
9.2.0.6 on sun Solaris, 8 cpus  with 24GB memory just for one database instance.   I am sorry for 
asking so many questions but our system in production and we do not have good handle on the sys 
context yet.
 


Followup   May 25, 2005 - 1pm Central time zone:

sys_context is treated as a bind variable (but no bind variable peeking)

I would not have "a single query" but rather -- N queries - using this technique:
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:1288401763279
that way, each individual query is eminently shareable, but also optimized to the question at hand. 

5 stars   May 25, 2005 - 6pm Central time zone
Reviewer: Lora from Ontario, Canada
Hello Tom,

Can you please comment on this approach?
It seems to me that the indexes will be used…

....

select  *
from    emp e
where   (b_ename is null    or e.ename = b_ename)
and     (b_hiredate is null or e.hiredate = b_hiredate)
and     (b_sal is null      or e.sal = b_sal);

...

Thanks!
Lora 


Followup   May 25, 2005 - 8pm Central time zone:

no, they won't be.  I would (do) use the approach above. 

4 stars   May 26, 2005 - 3am Central time zone
Reviewer: deepak from India
Just a query on similar line. When we submit a query oracle checks whether cusor is opened or not. 
Now I am not getting how oracle find out whether a cursor is open or not.

Genral flow is like ..
-------------------------------------------
Statement  
 Submitted 
 | 
 Is it in an open cursor?--------------YES----V    
 |                                            |   
 NO                                           |    
 |                                            | 
 Is SESSION_CACHED_CURSORS = Value            |   
 and cursor in           --------------YES----V   In these 3 cases we   
 Session Cursor cache?                        |   know that the cursor has  
 |                                            |   already been parsed, so  
 NO                                           |   re-parsing is 
 |                                            |   unnecessary. 
 Is HOLD_CURSOR=Y                             | 
 and cursor in           --------------YES----V       
 Held cursor cache?                           |        
 |                                            |        
 NO                                           |         
 |                                            |              ^ 
 OPEN A CURSOR                                |  CLIENT SIDE |        
 |                                            | -------------| 
 Statement is Hashed and compared             |  SERVER SIDE |  
 with the Hashed value in the sql area        |              V 
 |                                            V 
 Is it in sql area? --YES-(Soft Parse)--> --------- 
 |                                       |         | 
 NO                                      | EXECUTE | 
 |                                       |         | 
 PARSE STATEMENT ('Hard' Parse)---------> --------- 
is...
-------------------------------------------
Can you please tell me how oracle find out whether cursor is open or not. 


Followup   May 26, 2005 - 8am Central time zone:

well, really the DEVELOPER does "step one"

if (statement is not parsed)
then
   ask oracle to parse it
end if;

some environments like PLSQL, cache the cursors for us -- they have this logic in their 
implementation.

jdbc 3.0 can do this -- when you say "prepare statement", they first look in a client cache for the 
query.

So, this is a developer thing, not a server thing really. 

5 stars CURSOR and dynamic sql   May 26, 2005 - 6pm Central time zone
Reviewer: Michael Nelson from Utah
Instead of calling sys_context. What if you did the following.   I believe that it still uses the 
bind variables the way you explain eariler.  This allows for null or a value to be passed.

CREATE OR REPLACE PACKAGE BODY ALL_PRODS
AS

function my_procedure(P_sal IN NUMBER DEFAULT NULL,
                       p_hiredate_num in date default null,
                       p_ename in varchar2 default null)
 return  MASTER_PROD.m_prod_TAB PIPELINED
IS

  TYPE REF_CUR IS REF CURSOR;
  L_CURSOR REF_CUR;

  
  L_QUERY VARCHAR2(2000) DEFAULT 'SELECT PRODUCT_ID, NAME, SUPPLIER_NUM FROM prod_1 
    where ((p_sal is null) or (p_sal = sal))';
   and   ((p_ename is null) or (p_ename = name))';
   and   ((p_date is null) or (p_date= emp_date))';

 L_PROD_X MASTER_PROD.M_PROD;

BEGIN
  OPEN L_CURSOR FOR L_QUERY;
  LOOP
    FETCH L_CURSOR INTO L_PROD_X.PRODUCT_ID, L_PROD_X.NAME, L_PROD_X.SUPPLIER_NUM;
    DBMS_OUTPUT.PUT_LINE(L_QUERY);
    PIPE ROW (L_PROD_X);
...
... 


Followup   May 26, 2005 - 7pm Central time zone:

the goal:

have the best query for the inputs provided


the only way to accomplish that:

build a predicate that makes sense.


the optimizer, given:

    where ((p_sal is null) or (p_sal = sal))';
   and   ((p_ename is null) or (p_ename = name))';
   and   ((p_date is null) or (p_date= emp_date))';

is going to pick -- probably NO indexes here (it could pick one, but which one?  the one when p_sal 
is not null is wrong when p_sal is null and p_ename isn't)

you want to have predicates that represent what you are searching for

where x = :x
where y = :y
where z = :z
where x = :x and y = :y
where x = :x and z = :z
where y = :y and z = :z
where x = :x and y = :y and z = :z

so the optimizer can develop the plan that is right for each. 

5 stars Using Cursor   May 31, 2005 - 10am Central time zone
Reviewer: Kamal from Chennai,India
Hi Tom,

We are using a code where the SQL is stored in a table, we need to retrieve the SQL and then open a 
Cursor for that SQL. The Problem is we were not able to declare a Record for that cursor as it is 
fetched dynamically. How to declare a record such type of Ref cursor?

I tried this way using Cursor FOR Loop(as it automatically declare the record type)

declare
str_sql varchar2(2000) := 'select empno from emp';

begin
    
    for rec_emp in (str_sql) loop
        dbms_output.put_line('Empno :' || Empno);
    End loop;
    
End;

But still it is throwing error...can you throw some light on this issue.

Thanks
kamal 


Followup   May 31, 2005 - 6pm Central time zone:

you need to use dbms_sql for this, dbms_sql provides "api" based access to the columns.

You will not be using native dynamic sql for this, since you quite simply don't know at compile 
time how many columns, what their types are or anything.  

DBMS_SQL is going to be what you need.

http://asktom.oracle.com/~tkyte/flat/index.html
has examples of plsql dumping any query to a flat file for example 

5 stars   July 17, 2005 - 11pm Central time zone
Reviewer: nina 
Tom --

I am trying to create a dynamic where clause based on this example. I have the following procedure 
that takes in several parms, which can potentially be NULL. So, the proc looks like this:

v_inc_ecls_p comes in as a string, that can look like this, for example:


 and ( pebempl_ecls_code  like ('AM') or pebempl_ecls_code  like ('AS') or pebempl_ecls_code  like 
('S%') or pebempl_ecls_code  like ('S%'))

So here is the proc:

PROCEDURE p_main_process_1 
       (v_one_n IN VARCHAR2,
        v_inc_ecls_p IN VARCHAR2 default NULL) 
IS
TYPE rc IS REF CURSOR;
l_cursor rc;
l_query  VARCHAR2(4000)
DEFAULT 'SELECT   spriden_id,
         SUBSTR(spriden_last_name,
          1,
          20) last_name,
          pebempl_ecls_code
      FROM spriden, pebempl
      WHERE spriden_pidm = pebempl_pidm
          ORDER BY spriden_pidm';    

CURSOR l_template IS SELECT spriden_id, spriden_last_name,
                     pebempl_ecls_code
             FROM spriden, pebempl;
l_rec l_template%ROWTYPE;
                
BEGIN
  
  
 
 IF ( v_inc_ecls_p is NOT NULL ) THEN
           dbms_session.set_context( 'EMP_CTX', 'ENAME',
                                     '%'||v_inc_ecls_p||'%');
           l_query := l_query ||
                ' sys_context( ''EMP_CTX'', ''v_inc_ecls_p'' ) ';
 END IF;

 OPEN l_cursor FOR l_query;
   LOOP
         FETCH l_cursor INTO l_rec;
           EXIT WHEN l_cursor%NOTFOUND;
           
    INSERT INTO test_tbl (COL1, COL2)
    VALUES(l_rec.spriden_pidm,l_rec.pebempl_ecls_code);
   
         
       END LOOP;
   
       CLOSE l_cursor;
       
       COMMIT;
end p_main_process_1;


when I pass NULL to v_inc_ecls_p, it works fine, but when I try to pass that long string from the 
example above, I get ORA-00933 SQL Command not properly ended ...it shows that the error is here:

 l_query := l_query ||
                ' sys_context( ''EMP_CTX'', ''v_inc_ecls_p'' ) ';
 END IF;


I have no idea where. The thing is that v_inc_ecls_p might come in as a very long string as above, 
or as NULL or maybe a short string..Can I use the example shown here, or do I need to do something 
else...Please, advise..
 


Followup   July 18, 2005 - 7am Central time zone:

just print out the query - and when you see it, you'll see "why"


select * from t where sys_context( 'x', 'y', 'z' );


that query is not sensible (hopefully you agree).  That is the form of query however you are 
constructing.

in your case, if it is really a bunch of "or'ed likes" like that you will have a really hard time 
making shareable sql.  

4 stars Context vs Alternate way   July 21, 2005 - 5pm Central time zone
Reviewer: A reader 
Tom, 
 I have read your response to a question posed by Darko (ctrl-f  darko), "why not alternate way" 
and you said why to add an extra decode . Well if my requirement is if that variable is not null, 
then add it to where clause else do not bother . something like this.

CREATE OR REPLACE procedure context_vs_regular_variable(p_empno in integer,p_opt in integer default 
1)
as
  l_query varchar2(1000):='Select * from emp ';
  p_data  sys_refcursor;
  l_rec emp%rowtype;
Begin
    if p_empno is not null then
      if p_opt=1
      then
         l_query:=l_query||' Where empno=:vempno '; 
         open p_data for l_query using p_empno;
      else
         dbms_session.set_context('my_context','empno',p_empno);
         l_query:=l_query||' where empno = sys_context(''my_context'',''empno'') ';
         open p_data for l_query;
      end if;
    end if;
    fetch p_data into l_rec;
    dbms_output.put_line(l_rec.empno||'  '||l_rec.ename||'  '||l_rec.salary||'  '||l_rec.deptno);
end;
/

I am just pasing a value 1 or 2 to run it in a different way (if 1 is passed it will use regular 
approach, otherwise it will use "Sys_context" approach)

Can you tell me what's the difference between two approaches? there is same amount of coding and 
both use bind variables.
I have used contexts in some of my procedures and I am having hard time explaining it to my team. 

Thanks, 


Followup   July 21, 2005 - 6pm Central time zone:

the context is good when you don't know HOW MANY BINDS YOU HAVE during the open.

see the original example above, it is an example of when binding would be hard but sys_context'ing 
is not.  You might have 0, 1, 2 or 3 binds. 

5 stars Query   July 22, 2005 - 3am Central time zone
Reviewer: Tony from UK
Hi tom,

Can you please guide me how to impliment N+(N+1) logic in sql query

and looping in SQL.

Cheers
Tony 


Followup   July 22, 2005 - 8am Central time zone:

what is n+(n+1) logic? 

5 stars Query   July 25, 2005 - 8am Central time zone
Reviewer: Tony 
Tom,

The n (n+1) is nothing but in case if i have a table with 300 columns like 
l_no_1
l_no_2
l_no_3
l_no_4
...
...
etc, when i say select from first column to 290 columns i have to write all the 290 columns...
so in case if we can incriment the value in the select statment then it will be easy.

Tony
 


Followup   July 25, 2005 - 9am Central time zone:

then I would say you built the table wrong, it should go down the page, not across the page.

 

5 stars Better Practice   July 26, 2005 - 6am Central time zone
Reviewer: A Reader from India
Hi Tom,
There are a set of procedures used in the ETL process. The insert statements in these procedures 
insert millions of rows. All insert statements are written in procedures. My questions are:
1. If the inserts are stright forward ( direct ), is it OK to use them in procedures?
2. When using insert statements within a procedure, is it advisable to use cursors to insert them 
row wise?
3. In few procedures, cursors are used for direct inserts because, it has been assumed that in 
future there may be some processing required for each row before insert. Is this right to include 
it at this point (from performance point of view) when there is no processing required?
4. It is a common belief that it is not advisable to use direct inserts in DWH (unlike in OLTP 
systems) because millions of rows have to be inserted. Is this right?

Please clarify.
Thanks in advance. 


Followup   July 26, 2005 - 8am Central time zone:

1) yes, IF and ONLY IF you cannot do it in a single bulk SQL statement in the first place

2) does not make sense to me.  do not understand that comment

3) if you are saying "should I write code and do things slow by slow or use bulk SQL statements to 
do lots of stuff in a single sql statement", the answer is "do lots of stuff in a single sql 
statement"

4) You will have to define "direct" I think first, that has a specific connotation in Oracle 
(direct path inserts -- insert /*+ append */ into t select ......)

slow by slow processing (row by row processing) is not as good as

BIG BULKY SQL statement to do lots of stuff.


 

5 stars   July 26, 2005 - 9am Central time zone
Reviewer: A reader 
Tom, I have to read data from a DW and send to a third party app - one time shot.  The app that 
reads the data is a Java app but the result set of the query is nearly a billion rows.  

If this was PL/SQL, I could use a cursor with bulk collect but I'm not sure the best way to handle 
such a large result set from Java / JDBC perspective.  What is the best way to handle this large 
amount of data? 


Followup   July 26, 2005 - 10am Central time zone:

I've got a pro*c app you can use if you want (see 
http://asktom.oracle.com/~tkyte/flat/index.html

java won't be "fast", but if you are going to do it that way, set your prefetch (connect/statement 
method) to say 500. 

5 stars Query   July 26, 2005 - 12pm Central time zone
Reviewer: Tony from UK
Tom,

I thing  u r not getting the question...

I have a table with around 200+ columns... and all it contains the values for specific account ...

the table looks like this

acct_cd  tran_date l_acct_amt_1  l_acct_amt_2  l_acct_amt_3 .......... so on up to l_acct_amt_200

And each has some values for the each account with specific date..

and query will be like 

select acct_no,tran_date case when l_acct_amt_1>100 then l_acct_amt_1+0.15 end 'Bonus, case when 
l_acct_amt_2>100 then l_acct_amt_2+0.15 end 'Bonus,
case when l_acct_amt_3>100 then l_acct_amt_3+0.15 end 'Bonus
....
...
from table name

now plz guide me to minimize the code ...

Thx
Tony 


Followup   July 26, 2005 - 12pm Central time zone:

"u r" hmmmmmm, funny -- I'm not on a phone nor instant messaging with you.  Oh well.


Anyway, I understood completely and totally.  You have 200 columns when you should have 200 rows.

That is, you have data in record that should be cross record.


You have only one option right now -- list the columns you want.  There are no "shortcuts" in SQL 
for that (thankfully, more people would put cross record dimensions in record perhaps if there 
were)

 

5 stars   July 26, 2005 - 1pm Central time zone
Reviewer: A reader 
Tom, back to the DW unloading challenge...  Thanks for the Pro*C reference.  Even with the Pro*C, 
I'm concerned what the result set of a billion rows will do to the server side.  I can process it 
in batches on the client but is there anything I can do to minimize the server impact of a result 
set that large?  


Followup   July 26, 2005 - 1pm Central time zone:

fetching a billion rows via a full scan will be childs play for the database - it'll be gated by 
the client application (it'll only scan as fast as the client fetches).

Other than processing a full scan (no avoiding that), what are you worried about? 

3 stars Context Names Weird behaviour   August 1, 2005 - 5pm Central time zone
Reviewer: Maverick from USA
Tom, 

   I am facing a weird problem from Application Contexts. I have created  contexts for two 
packages. All the permissions were given.
  Context1: ABC_STATUSTOEXPEND_SEARCH_CTX
  Context2: ABC_EXPENDITURECODE_SEARCH_CTX

Package with context2 is working fine and could able to execute with different values. But context1 
was giving "invalid sys.dbms_session privileges.." error.
I had this problem before and changed the name to 
  ABC_STATTOEXP_SEARCH_CTX (shortened it), and it's working without any errors(??)
I thought there was a problem with length of the context name i was using. But look at Context2 , 
it has one more character than context1 and still did not give any problems.

Any ideas on why it did not like first name for Context1?

I thought of giving you more information like my code and tables etc..but i do not think that's any 
relevant here because, by just changing name everything is working  fine.

Thanks for any help you can provide me.
 


Followup   August 1, 2005 - 8pm Central time zone:

test case is where? 

5 stars Ref cursor   August 3, 2005 - 11am Central time zone
Reviewer: A reader from Anywhere
Tom,
    I am trying to use one cursor to do multiple tasks
    e.g If I have one cursor, I need to use the same cursor    and pass it to multiple functions 

create table test(id number);
insert into id values (1)
insert into id values (2)
insert into id values (3)

CREATE OR REPLACE PROCEDURE proc1 (p_cursor SYS_REFCURSOR) IS
    TYPE array_t IS TABLE OF VARCHAR2(4000) iNDEX BY BINARY_INTEGER;
    rec_array  array_t;

BEGIN
  FETCH p_cursor BULK COLLECT INTO rec_array;  
  
  FOR i IN rec_array.FIRST .. rec_array.LAST
  LOOP
    dbms_output.put_line('From proc1:'||rec_array(i));
  END LOOP;   
END proc1;

CREATE OR REPLACE PROCEDURE proc2 (p_cursor SYS_REFCURSOR) IS
    TYPE array_t IS TABLE OF VARCHAR2(4000) iNDEX BY BINARY_INTEGER;
    rec_array  array_t;

BEGIN
  FETCH p_cursor BULK COLLECT INTO rec_array;  
  
  FOR i IN rec_array.FIRST .. rec_array.LAST
  LOOP
    dbms_output.put_line('From proc2:'||rec_array(i));
  END LOOP;   
END proc2;


DECLARE
 rec_array SYS_REFCURSOR;
BEGIN
  OPEN rec_array FOR
  'SELECT id FROM test';

  proc1(rec_array);
  proc2(rec_array);
  close rec_array;

END;

 


Followup   August 3, 2005 - 12pm Central time zone:

you cannot, they would each have to get their "own" cursor.  You fetch a row once. 

5 stars Randy   August 19, 2005 - 7pm Central time zone
Reviewer: Randy from DALLAS, USA
I am a newbe, apart from the basic things from books, all the good things I learnt is from you TOM, 
I dont have previleges to creat CONTEXT so I tried out with "USING" I get this error, I dont see 
any mistake in my code, can you please help me resolve this problem thanks in advance.

SQL>   CREATE OR REPLACE TYPE emp_rec
  2     AS OBJECT
  3     ( empno number,
  4     sal number
  5    )
  6  /

Type created.

SQL>  CREATE OR REPLACE TYPE emp_table
  2  AS TABLE OF emp_rec
  3  /

Type created.

SQL> CREATE OR REPLACE FUNCTION emp_sal
  2  (p_deptno number,
  3  p_sal number
  4  )
  5  RETURN emp_table PIPELINED
  6  IS
  7   TYPE rc IS REF CURSOR;
  8    l_cursor rc;
  9    l_query  VARCHAR2(512)
 10                  DEFAULT ' SELECT
 11   empno,
 12   sal
 13  FROM
 14   emp
 15  WHERE
 16  1 = 1 ';
 17   cursor l_template IS SELECT empno,sal
 18   FROM emp;
 19   l_rec  l_template%rowtype;
 20  BEGIN
 21       IF  ( p_deptno IS NOT NULL ) THEN
 22            l_query := l_query ||
 23            ' AND deptno = : p_deptno ;' ;
 24     ELSE
 25            l_query := l_query ||
 26            ' AND sal > : p_sal ;' ;
 27     END IF;
 28  OPEN l_cursor FOR  l_query USING p_deptno, p_sal;
 29         LOOP
 30            FETCH l_cursor INTO l_rec;
 31            EXIT WHEN l_cursor%notfound;
 32       PIPE ROW(emp_rec(l_rec.empno, l_rec.sal));
 33         END LOOP;
 34        CLOSE l_cursor;
 35    RETURN;
 36    END;
 37  /

Function created.

SQL> select * from TABLE(seq_hours(20,1000));
select * from TABLE(seq_hours(20,1000))
                    *
ERROR at line 1:
ORA-00911: invalid character
ORA-06512: at "SCOTT.SEQ_HOURS", line 28
 


Followup   August 20, 2005 - 4pm Central time zone:

ctl-f for

Darko Egersdorfer

on this page to see how to do this without sys_context. 

5 stars RANDY   August 22, 2005 - 10am Central time zone
Reviewer: RANDY from USA
Thank you for the response, I got this thing working, I got other problem, but I am unsure whether 
this fits in this section or not, I am very sorry if I have put it in a wrong place. I declared a 
cursor on the pipelined table and the parameters to the function, are passed from some other cursor 
I am not returned with any results, I tested the pipelined table by giving literals manually its 
working fine. Thank you for the help TOM 


Followup   August 23, 2005 - 4am Central time zone:

need example. 

5 stars how much memory   August 22, 2005 - 3pm Central time zone
Reviewer: reader 
Do you have a query to find out how much memory all of the open cursors use for a session? Also, 
how  much memory each cursor uses? Thanks. 


4 stars Nested cursors using JOIN   August 31, 2005 - 11am Central time zone
Reviewer: Mirela from Romania
Hello Tom,

I also have a question regarding the cursors - is there a restriction saying that we can not use 
such a construct:

SELECT table1.A,
       cursor (select table2.B, table3.C 
               from table2
                    inner join table3 on table2.D = table3.D
               where table2.E = table1.E
               )
FROM table1;

I get an internal error (ORA-00600) when trying to run such an sql.

Thank you!
 


Followup   August 31, 2005 - 1pm Central time zone:

ora-600 = contact support 

4 stars Help with package design   September 13, 2005 - 8pm Central time zone
Reviewer: TH from USA
Tom,
 Currently we have some packages written and all of them have dbms_session.set_context() calls in 
them. We are going to hire bunch of junior level developers to help us write many such packages. We 
decided to wrap set_context and sys_context calls in another package so that these new developers 
would just make a call to these functions in that package[ reason: since they are jr. level 
developers and would take considerable amount of time learning about application contexts,we do not 
want them worry about how to call context specific functions].

But i have a problem generalizing set_context and sys_context [as they are specific to a package].
Do you happen to have any ideas on how to implement Context calls ?

Any thoughts about this approach is greatly appreciated.

our code typically looks like this

package [package name]
..
 procedure mysearch [parameter 1 in integer..parameter in integer]
 is.. 

 Begin
  [for each parameter in the procedure ]
   if (parameter1 is not null)
   then
    DBMS_SESSION.set_context ('Context_name1', 'p1_name', parameter1);

    l_qry := l_qry || ' and [column_name] = sys_context( ''Context_name1'', ''p1_name'' ) ';
   end if;   

END;

Thanks,
 


Followup   September 13, 2005 - 9pm Central time zone:

I'm not sure what the question is? 

3 stars   September 13, 2005 - 10pm Central time zone
Reviewer: A reader 
Ok, Question is, is it possible to make that piece of code into generic function so that any 
developer using it will just call like this
  if parameter1 is not null
  then
    set_and_call_context(parameter1,column1,data_type);
  end if;

and that is all they have to do.
my set_and_call_context function will accept those 3 parameters and build code as i mentioned 
before.

Can I do something like that? i am having a hard time creating a generic function like that.

Thanks  


Followup   September 14, 2005 - 8am Central time zone:

the context is bound to a particular procedure or package, your generic package would have to be 
named on every context you have.

Frankly, I fail to see how this is "easier" or "less scary" then dbms_session.set_context() would 
be. 

3 stars Little bit complex for novice developers   September 14, 2005 - 10am Central time zone
Reviewer: A reader 
Thanks for your response. I think it's difficult for their novice minds because, while constructing 
string they have to worry about double quotes and concatenation etc..and make sure they use 
different context names. they should know what's a context name and why we use it etc..

Not all ofthem are from Oracle background. they have different backgrounds and have different 
syntaxes in mind.

To avoid all these questions if there is a way to built a generic one, all they do is call that 
function.

Thought that would make their life easy. 


Followup   September 14, 2005 - 11am Central time zone:

ahh, I'm sort of getting it now....

How about this:

ops$tkyte@ORA9IR2> create or replace package ctx_set
  2  as
  3          function value( p_cname    in varchar2,
  4                      p_operator in varchar2,
  5                      p_val      in varchar2 ) return varchar2;
  6
  7          function value( p_cname    in varchar2,
  8                      p_operator in varchar2,
  9                      p_val      in number ) return varchar2;
 10
 11          function value( p_cname    in varchar2,
 12                      p_operator in varchar2,
 13                      p_val      in date ) return varchar2;
 14
 15  end;
 16  /
 
Package created.
 
ops$tkyte@ORA9IR2> create or replace context my_ctx using ctx_set;
 
Context created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace package body ctx_set
  2  as
  3          function value( p_cname    in varchar2,
  4                      p_operator in varchar2,
  5                      p_val      in varchar2 ) return varchar2
  6          is
  7          begin
  8                  if ( p_val is null )
  9                  then
 10                          return null;
 11                  end if;
 12                  dbms_session.set_context( 'my_ctx', p_cname, p_val );
 13                  return ' and ' || p_cname || ' ' || p_operator || ' '  || 'sys_context( 
''my_ctx'', '''|| p_cname || ''')';
 14          end;
 15
 16          function value( p_cname    in varchar2,
 17                      p_operator in varchar2,
 18                      p_val      in number ) return varchar2
 19          is
 20          begin
 21                  if ( p_val is null )
 22                  then
 23                          return null;
 24                  end if;
 25                  dbms_session.set_context( 'my_ctx', p_cname, p_val );
 26                  return ' and ' || p_cname || ' ' || p_operator || ' '  || 
'to_number(sys_context( ''my_ctx'', '''|| p_cname || '''))';
 27          end;
 28
 29          function value( p_cname    in varchar2,
 30                      p_operator in varchar2,
 31                      p_val      in date ) return varchar2
 32          is
 33          begin
 34                  if ( p_val is null )
 35                  then
 36                          return null;
 37                  end if;
 38                  dbms_session.set_context( 'my_ctx', p_cname, to_char(p_val,'yyyymmddhh24miss' 
) );
 39                  return ' and ' || p_cname || ' ' || p_operator || ' '  || 
'to_date(sys_context( ''my_ctx'', '''|| p_cname || '''),''yyyymmddhh24miss'')';
 40          end;
 41
 42  end;
 43  /
 
Package body created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> pause
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace
  2  procedure my_procedure( p_ename   in varchar2 default NULL,
  3                          p_hiredate  in date default NULL,
  4                          p_sal       in number default NULL)
  5  as
  6      type rc is REF CURSOR;
  7
  8      l_cursor rc;
  9      l_query  varchar2(512)
 10               default 'select * from emp where 1 = 1 ';
 11
 12      cursor l_template is select * from emp;
 13      l_rec  l_template%rowtype;
 14
 15  begin
 16          l_query := l_query || ctx_set.value( 'ename', 'like', '%'||upper(p_ename)||'%' );
 17          l_query := l_query || ctx_set.value( 'hiredate', '>', p_hiredate );
 18          l_query := l_query || ctx_set.value( 'sal', '>', p_sal );
 19      p( l_query );
 20      open l_cursor for l_query;
 21      loop
 22          fetch l_cursor into l_rec;
 23          exit when l_cursor%notfound;
 24
 25          dbms_output.put_line( l_rec.ename || ',' ||
 26                                l_rec.hiredate || ',' ||
 27                                l_rec.sal );
 28      end loop;
 29
 30      close l_cursor;
 31  end;
 32  /
 
Procedure created.


it behaves a tad different with respect to the LIKE i had on ename (since we add '%', it is never 
"null") -- something to be aware of.
 

4 stars Thanks Tom.. But..   September 14, 2005 - 12pm Central time zone
Reviewer: A reader 
I can use this code with some minor changes as per my requirement. Thanks for this one.
But i still have one question .

You have created context for ctx_set package. But the query we are executing [actually opening 
refcursor] is from called procedure. Won't this give error since Context is being used in this 
procedure,but actually created for another?


Thanks for all your help [again]
 


Followup   September 14, 2005 - 1pm Central time zone:

dbms_session is what checks that it is being called from the right place.

The calls to sys_context are available anywhere

That is, you can READ a context value from anything
You can only SET a context value from the single package 

5 stars   October 7, 2005 - 8am Central time zone
Reviewer: A reader 
Tom,
May i know the difference between using "SYS_CONTEXT" and :USING clause to
bind variables in dynamic Ref Cursor?
Thanks..

 


Followup   October 7, 2005 - 9am Central time zone:

the difference is minimal, they achieve the same effect.

if you know the number of binds at compile time, by all means using "using"

if you do not, the use of sys_context will allow  you to dynamically construct the query, 
referencing just the sys_context values you need - be it 1, 2, or 300 of them. 

3 stars Cursor   October 18, 2005 - 4pm Central time zone
Reviewer: Anne from MN, USA
Hi Tom,

I hope my problem is relevant to the existing thread. If not please let me know. I will try 
describe my problem as best as I can. I have the following requirement : 
hrs_cur : cursor for project-emp-hours in the current period.   
distinct project_array : for distinct project_id in hrs_cur
distinct emp_array : for  distinct emp_id in hrs_cur
distinct proj_emp_array : for distinct project_id, emp_id in hrs_cur.
FOr every row in the hrs_cur do processing and update the proj_array, emp_array.....

My question is : Is there a better way for building the emp, proj, and emp_proj arrays so that I do 
not have to duplicate the query for hrs_cur ? I want to avoid creating a view for hrs_cur. Thanks 
so much! 

My code is as follows : 
declare
   cursor hrs_cur is  
          SELECT agency_nr, customer_id, 
                 project_id ....... 
          from ....;

 TYPE proj_rec_type  IS RECORD (
          project_id      bps_project.project_id%TYPE 
         , proj_max        bps_project.max_bill_amt%TYPE
         , proj_bal    bps_project.max_bill_amt%TYPE
         );

    TYPE  proj_type  IS TABLE OF .....;
    TYPE  emp_type  IS TABLE OF .....;
    TYPE  proj_emp_type is TABLE OF  .... ;

    proj_array    proj_type;
    emp_array     emp_type;
    proj_emp_array  proj_emp_type;
begin

   ---Build proj_array
   for x in 
           (SELECT project_id, proj_bal
            from proj_table
            where project_id in
                  ( select distinct project_id
                    <from the query for hrs_cur>
                  )
   loop
      proj_array(x.project_id) := x.proj_bal;
   end loop;

   ---Build emp_qrray
   for x in 
           (SELECT emp_id, cost_to_date
            from emp_table
            where emp_id in
                  ( select distinct emp_id
                    <from the query for hrs_cur>
                  )
   loop
      emp_array(x.emp_id) := x.cost_to_date;
   end loop;


   --- Build proj_emp_array
   for x in 
           (SELECT project_id, emp_id, proj_emp_id
            from proj_emp table
            where (project_id, emp_id) in
                  (select distinct project_id, emp_id
                    <from the query for hrs_cur>
                  )
   loop
      proj_emp_array(x.project_id) := x.proj_emp_id;
   end loop;

   Open hrs_cur;
   fetch hrs_cur bulk collect into hrs_tab;
   Close hrs_cur;

   for i in 1 .. hrs_tab.count
   loop
       <do some processing and set the 'balances' in the emp_array, proj_array 
and emp_proj_array.....>
   end loop;         

exception
   when OTHERS then
      dbms_output.put_line (DBMS_UTILITY.FORMAT_ERROR_STACK);     
end;


 


Followup   October 18, 2005 - 5pm Central time zone:

UGH -- get rid of the when others without a raise right after it - just delete it, it is useless, 
actually less than useless!  the only thing it would/could do is HIDE the fact that an error 
occurred!!!  get rid of it.


since you never need distinct on a subquery like that......


but why not join emp -> proj_emp <- project

and where (project_id, emp_id) in (.....)


one query - three way join, one subquery...

 

3 stars cursor within cursor   October 25, 2005 - 1pm Central time zone
Reviewer: A reader 
Hi Tom,

I have 2 tables psum and atr

create table psum
(
action varchar2(4),
actiondate date,
count number
);

create table atr
(
timestamp date,
action varchar2(4),
description varchar2(2000)
);

Assuming the 2 tables are populated as follows:

insert into psum values('A301', '10-20-2005', 6000);
insert into psum values('A302', '10-20-2005', 400);
insert into psum values('A301', '10-21-2005', 2021);
insert into psum values('A302', '10-21-2005', 300);
insert into psum values('A301', '10-22-2005', 5664);
insert into psum values('A302', '10-22-2005', 100);
insert into psum values('A301', '10-23-2005', 7776);
insert into psum values('A302', '10-23-2005', 342);

insert into atr values ('10-20-2005', 'CC01', 'Action Dec 1');
insert into atr values ('10-20-2005', 'CC02', 'Action Dec 2');
insert into atr values ('10-20-2005', 'CC03', 'Action Dec 3');
insert into atr values ('10-21-2005', 'CC01', 'Action Dec 1');
insert into atr values ('10-21-2005', 'CC02', 'Action Dec 2');
insert into atr values ('10-22-2005', 'CC01', 'Action Dec 1');
insert into atr values ('10-22-2005', 'CC02', 'Action Dec 2');
insert into atr values ('10-23-2005', 'CC01', 'Action Dec 1');
insert into atr values ('10-23-2005', 'CC02', 'Action Dec 2');
insert into atr values ('10-23-2005', 'CC03', 'Action Dec 3');
insert into atr values ('10-23-2005', 'CC04', 'Action Dec 4');


Now I have a request for a report where the inputs will be a from-date and a to-date and the data 
should be returned from these 2 tables. But both these tables are like auditing tables and they 
dont have any column which is in common between these 2 tables. 

For eg., if the input from date is 10-20-2005 and input to date is 10-22-2005 then I need a report 
like

Date        Count from psum        Count from atr
10-20-2005    6400            3
10-21-2005    2321            2
10-22-2005    5764            2

What is the easiest way to achieve this. Please help.

Thanks.
 


Followup   October 26, 2005 - 8am Central time zone:

ops$tkyte@ORA10GR2> select dt, sum(cntp), sum(cnta)
  2    from ( select actiondate dt, sum(count) cntp, to_number(null) cnta
  3             from psum
  4                    where actiondate between to_date('01-20-2005') and to_date('10-22-2005')
  5                    group by actiondate
  6                    union all
  7                   select timestamp dt, null, count(*)
  8                     from atr
  9                    where timestamp between to_date('01-20-2005') and to_date('10-22-2005')
 10                    group by timestamp
 11             )
 12   group by dt;

DT          SUM(CNTP)  SUM(CNTA)
---------- ---------- ----------
10-20-2005       6400          3
10-21-2005       2321          2
10-22-2005       5764          2

 

3 stars Using "COMMIT" or "ROLLBACK" inside PLSQL   October 28, 2005 - 4pm Central time zone
Reviewer: Rahul Dutta from Delhi, India
Hi Tom,

From my point of view, we should not use "COMMIT" or "ROLLBACK" inside the PLSQL code, it should be 
passed from the application or from the interface from where we are executing it.
I'm falling short of Datapoints to clarify my point except one that is we can reduce the level of 
damage from erroneous calling of such Procedures.
Please let me know, if my point is correct and provide me some more Datapoints.

I also strongly feel that Issuing Commit is the responsibility of the Interface/application which 
calls that particular PLSQL code.

Thanks 

Rahul

P.S: I've also checked in Metalink that there is BUG # 3768052 for versions >= 9.0 but < 10.2 


Followup   October 29, 2005 - 10am Central time zone:

I agree with you 100% and have written as much in my books over and over.


The client is the only thing that knows WHAT the transaction is.  Perhaps the client thinks that 
updating the address and updating the phone number is the transaction - if you have a procedure to 
do each - and they each commit, well, the client cannot use them!


commits belong in the control of the client - the "master" of the session. 

1 stars no better   November 10, 2005 - 12am Central time zone
Reviewer: James from Australia
Well I've moved to using set_context and execute immediate with bulk collect and performance is 
worse.

Previously, to solve the problem of an unknown number of binds, I had a small procedure which 
returned an open cursor, which looked something like that:

procedure open_bound_cursor(
sql_statement    in    varchar2,
bind_array    in    owa.vc_arr,
bound_cur    in out    CurType)
is

bind_count number := bind_array.count; 

begin

if bind_count = 0 then
    open bound_cur for sql_statement;
elsif bind_count = 1 then
    open bound_cur for sql_statement
    using    bind_array(1);
elsif bind_count = 2 then
    open bound_cur for sql_statement
    using    bind_array(1),bind_array(2);

etc etc etc ...

So I would parse my parameters, call this procedure and loop through the cursor. I even had a 
subquery in the loop! I also in my new code moved the subquery into the bulk collected collection! 
Still no quicker!

Here are the original stats (1,000 searches)

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     6058      2.40       2.71          1         31          0           0
Execute   7071      9.18       9.95          0       9306          0           0
Fetch    53346    149.06     184.82       1148    4353386          0     3631670
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    66475    160.64     197.49       1149    4362723          0     3631670

Misses in library cache during parse: 216

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file sequential read                       964        1.89         20.20
  db file scattered read                         67        0.00          0.02
  latch free                                    174        0.05          0.94
  buffer busy waits                               4        0.13          0.13



Here is the "new and immproved" bulk collected, sql typed subquery, context set for the same 1,000 
searches:

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     5149      3.95       4.56         10       5052          0           0
Execute   7186      7.83       8.75          0       6855          0           0
Fetch    41553    172.91     192.33       2588    4490619          0     3623877
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    53888    184.69     205.66       2598    4502526          0     3623877

Misses in library cache during parse: 259
Misses in library cache during execute: 1

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file sequential read                      2070        1.16          2.31
  latch free                                    297        0.08          0.88
  db file scattered read                        205        0.01          0.03


More CPU, greater elapsed time, *very* dissapointed.

Regards,
James




 


Followup   November 11, 2005 - 11am Central time zone:

you need to give "a concrete example"

in the grand scheme of things - this does not look significantly different - and in fact since they 
did obviously different things - hard to compare. 

2 stars re:no better (new tkprofs)   November 10, 2005 - 12am Central time zone
Reviewer: James from Australia
There might have been a few too many other things running when I posted the first 2 trace files so 
here they are again (with a sql statement removed)

OLD WAY 

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     4067      1.60       2.15          0         18          0           0
Execute   5065      5.70       6.58          0       3901          0           0
Fetch    48617    116.50     132.53       1019    3145537          0     3335117
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    57749    123.80     141.27       1019    3149456          0     3335117

Misses in library cache during parse: 112

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  latch free                                    256        0.13          1.01
  db file sequential read                       683        0.28          0.63
  db file scattered read                        142        0.00          0.01
  buffer busy waits                               2        0.00          0.00




NEW WAY
OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     3261      1.75       2.11          1         27          0           0
Execute   5260      6.39       7.49          0       6276          0           0
Fetch    36897    141.81     171.66       1131    3186321          0     3333796
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    45418    149.95     181.27       1132    3192624          0     3333796

Misses in library cache during parse: 135

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file sequential read                       754        0.25          0.41
  latch free                                    203        0.22          1.92
  db file scattered read                        157        0.11          0.12
  buffer busy waits                               1        0.03          0.03

 


Followup   November 11, 2005 - 11am Central time zone:

so, did you stop to compare the plans and see if one of them changed or not.   

4 stars columns in refcursor   November 10, 2005 - 8am Central time zone
Reviewer: Oleg Oleander from Hungary
Dear Tom,

Until now, I was sure that the client has no way to "look into" the column count, column type, 
column name of the refcursor returned by the server. So the clinet must know the structure of the 
returned data in advance. But I saw a feature in TOAD that I cannot imagine how that works:

select Function(a,b,c) from dual;

returns a refcursor, than I right-click the returned one column row and I am able to view the data 
in the refursor.

How can that be? 

Also, is there a way to convert a refcursor to the simple SQL if I know the columns that are in the 
refcursor? (I'd need it for a workaround)
like:
select col1, col2 from (Cursor)

Thank you very much for your time.

Oleg. 


Followup   November 11, 2005 - 11am Central time zone:

if you return a strongly typed cursor, you can describe it before hand.


ops$tkyte@ORA10GR2> create or replace package my_pkg
  2  as
  3          type rc is ref cursor return emp%rowtype;
  4  end;
  5  /

Package created.

ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> create or replace function foo return my_pkg.rc
  2  as
  3  begin
  4          null;
  5  end;
  6  /

Function created.

ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> desc foo
FUNCTION foo RETURNS REF CURSOR
                                RECORD                  OUT
     EMPNO                      NUMBER(4)               OUT
     ENAME                      VARCHAR2(10)            OUT
     JOB                        VARCHAR2(9)             OUT
     MGR                        NUMBER(4)               OUT
     HIREDATE                   DATE                    OUT
     SAL                        NUMBER(7,2)             OUT
     COMM                       NUMBER(7,2)             OUT
     DEPTNO                     NUMBER(2)               OUT

ops$tkyte@ORA10GR2>



I don't know what you mean by the last part - to convert a ref cursor to sql??? doesn't make sense 
to me. 

5 stars REOPEN/REUSE REF CURSOR   November 10, 2005 - 12pm Central time zone
Reviewer: Laxman Kondal from Springfield, VA
Hi Tom

I want to reopen/reuse ref_cursor which comes in as parameter and I did this test to reopen but 
didn't work.

scott@ORCL10G> CREATE OR REPLACE PROCEDURE P
  2  AS
  3    TYPE ref_cur     IS  REF CURSOR;
  4    v_ref      ref_cur;
  5    c_ref      ref_cur;
  6    --
  7    rec_emp Emp%ROWTYPE;
  8    --
  9  BEGIN
 10      OPEN v_ref FOR SELECT * FROM Emp WHERE ROWNUM <5;
 11      --
 12      c_ref  := v_ref; -- i am not sure why this didn't work
 13      --
 14      LOOP
 15              FETCH v_ref INTO rec_emp;
 16                        EXIT WHEN v_ref%NOTFOUND;
 17                        DBMS_OUTPUT.PUT_LINE(rec_emp.ename);
 18      END LOOP;
 19      --
 20      CLOSE v_ref;
 21      --
 22      LOOP
 23              FETCH c_ref INTO rec_emp;
 24                        EXIT WHEN c_ref%NOTFOUND;
 25                        DBMS_OUTPUT.PUT_LINE(rec_emp.ename);
 26      END LOOP;
 27      --
 28      CLOSE c_ref;
 29      --
 30  END;
 31  /

Procedure created.

scott@ORCL10G> exec p
SMITH
ALLEN
WARD
JONES
BEGIN p; END;

*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at "SCOTT.P", line 23
ORA-06512: at line 1


scott@ORCL10G>

Is there any way or work around to reuse this ref cursor.

Thanks and regards.
 


Followup   November 11, 2005 - 11am Central time zone:

you cannot, you would need two result sets, ref cursors are "pointers", they both pointed to the 
same result set (that is the "ref" in "ref cursor") 

5 stars REOPEN/REUSE REF_CURSOR   November 11, 2005 - 10am Central time zone
Reviewer: Laxman Kondal from Springfield, VA
Hi Tom

I could do this as work around:

scott@ORCL10G> CREATE OR REPLACE PROCEDURE P
  2  AS
  3    --
  4    TYPE ref_cur IS REF CURSOR;
  5    v_result    ref_cur;
  6    --
  7    rec1 Emp%ROWTYPE;
  8    --
  9    TYPE emp_array IS TABLE OF Emp%ROWTYPE INDEX BY BINARY_INTEGER;
 10    --
 11    emp_data  emp_array;
 12    --
 13  BEGIN
 14      --
 15      OPEN v_result FOR SELECT * FROM Emp WHERE ROWNUM < 5;
 16     --
 17     FETCH v_result BULK COLLECT INTO emp_data;
 18     CLOSE v_result;
 19     --
 20     FOR i IN 1..emp_data.COUNT LOOP
 21             rec1 := emp_data(i);
 22             DBMS_OUTPUT.PUT_LINE('FirstTime '||
 23                                   rec1.empno||'-'||
 24                                                       rec1.ename);
 25     END LOOP;
 26     --
 27     FOR i IN 1..emp_data.COUNT LOOP
 28             rec1 := emp_data(i);
 29             DBMS_OUTPUT.PUT_LINE('SecondTime '||
 30                                   rec1.empno||'-'||
 31                                                       rec1.ename||'-'||
 32                                                       rec1.mgr);
 33     END LOOP;
 34     --
 35     --
 36     DBMS_OUTPUT.PUT_LINE('Since you fetched ref cursor once its invalid as empty to fetch any 
thing');
 37     --
 38     LOOP
 39             FETCH v_result INTO rec1;
 40                     EXIT WHEN v_result%NOTFOUND;
 41                       DBMS_OUTPUT.PUT_LINE(rec1.empno);
 42     END LOOP;
 43     --
 44
 45  END;
 46  /

Procedure created.

scott@ORCL10G> exec p
FirstTime 7369-SMITH
FirstTime 7499-ALLEN
FirstTime 7521-WARD
FirstTime 7566-JONES
SecondTime 7369-SMITH-7902
SecondTime 7499-ALLEN-7698
SecondTime 7521-WARD-7698
SecondTime 7566-JONES-7839
Since you fetched ref cursor once its invalid as empty to fetch any thing
BEGIN p; END;

*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at "SCOTT.P", line 39
ORA-06512: at line 1


scott@ORCL10G>


Is this the right way to work around reuse of ref cursor?

Thanks and regards 


Followup   November 12, 2005 - 8am Central time zone:

no - not in general, because in general result sets are not of trival size.  I don't want thousands 
of records in an index by table.

The "work around" to me is not to process the same data twice, that is somewhat inefficient. 

4 stars columns in sys_refcursor (9.2.0.3)   November 12, 2005 - 3am Central time zone
Reviewer: Oleg Oleander from Hungary
Dear Tom,

Thank you for the your answer. 

I use sys_refcursor as below:

SQL> create or replace function foo return sys_refcursor
  2   as
  3   begin
  4           null;
  5   end;
  6  /

A függvény létrejött.

SQL> desc foo
FUNCTION foo RETURNS REF CURSOR

What sql sqlplus is submitting for a desc commnad?

As for the second part:

Is there a way to use a cursor like a view in an sql statement. Like I could join it for exapmle. 
Like:

select t.col1, t.col2, c.col1 from sometable t, foo c;

I know that it is a bit silly, since one could make all necessary joins and selects in the 
refcursor's sql itself,
or one could return a table type instead of a refcursor, but i am just interested.  

Thank you for your help again.

Best regards,

Oleg 


Followup   November 12, 2005 - 10am Central time zone:

then no one can tell you what that returns UNTIL RUNTIME, when they run it, they get a cursor back 
and they can describe that cursor.


sqlplus isn't using sql to describe, it is using an API to describe things, a function call.  



You can use functions in SQL - but not functions that return ref cursors (result sets), you use 
pipelined functions - functions that return "piped data", much like a table - a row at a time.


 

4 stars how to describe the returned refcursor?   November 12, 2005 - 1pm Central time zone
Reviewer: Oleg Oleander from Hungary
Dear Tom,

1: Would you please tell me the package name with which I can - or sqlplus can - describe the 
returned refcursor? 

2: I understand your explanation, but I was curios that I may miss something.

Thank you very much. Your site is great!

Oleg. 


Followup   November 12, 2005 - 1pm Central time zone:

1) plsql cannot (not yet) - CLIENTS can (vb, java, C, whatever).  SQLPlus does right before you 
PRINT the ref cursor - but there is no "desc ref_cursor" command or anything like that.

 

4 stars still, TOAD does it somehow   November 13, 2005 - 7pm Central time zone
Reviewer: Oleg Oleander from Hungary
I am still curious how TOAD can do this (describing the returned ref_cursor). Or is they using some 
undocumented api?

Thank you 


Followup   November 14, 2005 - 7am Central time zone:

toad is a C program (or some client language)

Toad is using the same API (oci ultimately) that SQLPlus is using. 

For the same reason that sqlplus can figure out what the column names are, their types, their 
lengths (it described the cursor), toad can.

Nothing special or magic, toad is a client program and as stated - client programs can procedurally 
process a ref cursor after describing it.

 

4 stars Interesting, but could I use as dynamic return-cursor from function ?   November 17, 2005 - 12pm Central time zone