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).

August 20, 2001 - 5am Central time zone
Reviewer: Helena Markova from Bratislava, Slovakia
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.

August 30, 2001 - 5am Central time zone
Reviewer: Srinivas Atreya from Hyderabad, India
Simply Brilliant, never knew about the context stuff...--:)
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)
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!
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.
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.
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.
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.
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.
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.
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.

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.
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.
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".
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.
That's what I meant...
April 4, 2002 - 4pm Central time zone
Reviewer: Andre Whittick Nasser from Brazil
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 !
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';
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?
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.

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!
Dynamic Attribute
July 29, 2002 - 4am Central time zone
Reviewer: Robert
Brilliant, thanks Tom. It all seems so obvious now!
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)
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.
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.
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)
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.
short-circuited or
September 3, 2002 - 3pm Central time zone
Reviewer: A reader
thanks so much tom
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.
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'')';
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.
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.
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
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)
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 .....;
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.
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.
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,
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?
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.
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.
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?
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'))||'%';
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.
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.
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
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.
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.
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.
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
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.
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.
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.
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!)
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"
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
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.
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.
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;

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

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)

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

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.
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;
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;
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;
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
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.

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)

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!

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

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
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.
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.
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)
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.
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)
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?
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
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
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!
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.
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
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
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.
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.
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.
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 :)
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!

September 4, 2003 - 11am Central time zone
Reviewer: A reader
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.

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"
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.
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.

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.
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.
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.
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.
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"
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.

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.
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.

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.

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.

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

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.
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
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.
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)

March 5, 2004 - 8am Central time zone
Reviewer: Mihail Bratu
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
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.
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"

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.
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"
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.
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.
Wonder
March 30, 2004 - 5am Central time zone
Reviewer: Arangaperumal from Chennai,India
I really wonder about the techniques you are
using.
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.
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.
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.
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.
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.
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.
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)
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
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.

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.
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.

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)
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.
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.

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.
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"
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.
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.
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.
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.
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...)
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.
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....
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?
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.
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.
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!)
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.
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.
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.
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.
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!
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)
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.
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;
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.
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.
..
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.
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.
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?
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.
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?
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'' ) )';
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!
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
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.

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.
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.
cursor
November 22, 2004 - 7am Central time zone
Reviewer: bhagirath pesaru from india
very good thanks
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.
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
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.
Just Beautiful!
December 4, 2004 - 2am Central time zone
Reviewer: TH from Manma, BAH
Thanks..
Just Beautiful!
TH, BAH
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.
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"?)
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.
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.
Ishan Bansal
December 10, 2004 - 6am Central time zone
Reviewer: A reader
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
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.
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.
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.
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
Thanks
December 21, 2004 - 8pm Central time zone
Reviewer: Ramesh
Hi Tom,
Thanks for the clarification.
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.
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.
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)
Thanks
December 27, 2004 - 12am Central time zone
Reviewer: Abubaker Khered
Thanks
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.
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.
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
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
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.
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;
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)
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.
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.
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.
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.
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.
how to solve the problem in a 9i rac env?
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.
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

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.
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.
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.
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...

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....

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?

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.

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.

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)
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.
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.

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.
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"
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.

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.

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.
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.
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

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.
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.
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?
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.
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.

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.
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)

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?
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?
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.
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.
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.
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.
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
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?

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.
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.
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

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.
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...
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
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.
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.
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.
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.
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")
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.
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.
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.
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.
Interesting, but could I use as dynamic return-cursor from function ?
November 17, 2005 - 12pm Central time zone
|