Lots of help, I was adrift on reference cursors
Harrison, August 12, 2001 - 10:28 am UTC
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).
Helena Markova, August 20, 2001 - 5:23 am UTC
Most advanced
andrew, August 28, 2001 - 12:38 pm UTC
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.
Srinivas Atreya, August 30, 2001 - 5:25 am UTC
Simply Brilliant, never knew about the context stuff...--:)
Is there a way to avoid giving the select multiple number of times
Nag, November 06, 2001 - 2:46 pm UTC
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
November 06, 2001 - 3:26 pm UTC
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
William, November 07, 2001 - 9:03 am UTC
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
November 07, 2001 - 6:30 pm UTC
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
Jeroen, November 08, 2001 - 10:52 pm UTC
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;
/
November 09, 2001 - 10:15 am UTC
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
Frank, November 09, 2001 - 8:07 am UTC
Great example.
Is it possible to reset the entire context any other way then looping through list_context output?
November 09, 2001 - 10:26 am UTC
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
Niloufar, December 10, 2001 - 6:42 pm UTC
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?
Tim Glasgow, March 14, 2002 - 5:26 am UTC
I expect that this can be applied to execute immediate also and it does not necessarily only apply to ref_cursors.
March 14, 2002 - 8:25 am UTC
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
Tim Glasgow, March 14, 2002 - 9:12 am UTC
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
March 14, 2002 - 10:21 am UTC
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
Tim Glasgow, March 14, 2002 - 10:47 am UTC
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
March 14, 2002 - 10:53 am UTC
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
Tim Glasgow, March 15, 2002 - 9:37 am UTC
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 15, 2002 - 2:15 pm UTC
Tanel Hiir, March 28, 2002 - 6:51 am UTC
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?
March 28, 2002 - 5:06 pm UTC
They are only taking about DBMS_RLS there.
Excellent technique -- now I have a ton of tuning work to do!!!
Keith Miller, April 03, 2002 - 5:07 pm UTC
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?
April 03, 2002 - 6:28 pm UTC
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...
Andre Whittick Nasser, April 04, 2002 - 10:38 am UTC
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!
April 04, 2002 - 1:14 pm UTC
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
Andre Whittick Nasser, April 04, 2002 - 2:15 pm UTC
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 !
April 04, 2002 - 3:28 pm UTC
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...
Andre Whittick Nasser, April 04, 2002 - 4:56 pm UTC
Cached execution plans...
Andre Whittick Nasser, April 04, 2002 - 5:14 pm UTC
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
James Turner, April 05, 2002 - 9:29 am UTC
Seems like you could achieve the same results using package variables. What advantage is there to the application context approach over this alternative?
April 05, 2002 - 9:44 am UTC
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?
Robert Boyle, July 25, 2002 - 10:35 am UTC
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
July 25, 2002 - 10:26 pm UTC
Not sure what you are asking here?
Dynamic Attribute
Robert, July 26, 2002 - 5:10 am UTC
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
July 26, 2002 - 7:01 am UTC
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.
Robert, July 26, 2002 - 11:12 am UTC
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
July 28, 2002 - 3:00 pm UTC
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
Robert, July 29, 2002 - 4:07 am UTC
Brilliant, thanks Tom. It all seems so obvious now!
question on the followup for -- a non dynamic alternative
john, August 12, 2002 - 12:13 pm UTC
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')
August 12, 2002 - 1:50 pm UTC
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?
Darko Egersdorfer, August 13, 2002 - 7:54 am UTC
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)
August 13, 2002 - 5:07 pm UTC
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
john, August 13, 2002 - 11:56 am UTC
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
August 13, 2002 - 5:25 pm UTC
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
Bill Coulam, August 13, 2002 - 2:10 pm UTC
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
August 20, 2002 - 4:18 pm UTC
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
A reader, September 03, 2002 - 1:17 pm UTC
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?
September 03, 2002 - 2:34 pm UTC
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
A reader, September 03, 2002 - 3:53 pm UTC
thanks so much tom
Privilege problem when calling DBMS_SESSION
Mike Stacey-Gee, September 04, 2002 - 7:30 am UTC
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;
September 04, 2002 - 8:49 am UTC
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>
<b>
See, until the context is bound to the "A" schema - the procedure owned by "A" cannot set it.</b>
using the same context field value in two queries
A reader, September 09, 2002 - 6:40 pm UTC
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
September 09, 2002 - 8:07 pm UTC
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
ramu, September 30, 2002 - 2:55 pm UTC
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;
October 01, 2002 - 9:28 am UTC
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
Mo, December 24, 2002 - 3:28 pm UTC
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,
December 24, 2002 - 4:26 pm UTC
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
mo, December 24, 2002 - 4:50 pm UTC
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,
December 24, 2002 - 5:53 pm UTC
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
Bala, December 26, 2002 - 4:10 am UTC
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?
December 26, 2002 - 11:49 am UTC
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
mo, January 13, 2003 - 3:14 pm UTC
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;
January 13, 2003 - 4:10 pm UTC
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
mo, January 13, 2003 - 4:53 pm UTC
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,
January 13, 2003 - 7:12 pm UTC
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
mo, January 15, 2003 - 5:06 pm UTC
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,
January 16, 2003 - 8:09 am UTC
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
mo, January 16, 2003 - 9:23 am UTC
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
mo, January 22, 2003 - 10:03 am UTC
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
mo, January 22, 2003 - 10:47 am UTC
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,
January 22, 2003 - 11:02 am UTC
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
Sujata, January 27, 2003 - 4:47 pm UTC
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???
Paul, March 11, 2003 - 8:19 am UTC
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
March 11, 2003 - 8:54 am UTC
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
Paul, March 11, 2003 - 10:08 am UTC
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.
March 11, 2003 - 10:29 am UTC
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
Paul, March 11, 2003 - 12:45 pm UTC
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!
March 11, 2003 - 1:00 pm UTC
what is your optimizer_index_cost_adj/index_caching set to.
More feedback
Paul, March 12, 2003 - 4:39 am UTC
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
March 12, 2003 - 7:54 am UTC
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
Paul, March 12, 2003 - 8:14 am UTC
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
March 12, 2003 - 8:27 am UTC
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 ...
Paul, March 13, 2003 - 9:45 am UTC
... 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?
March 14, 2003 - 5:13 pm UTC
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!!
Paul, March 19, 2003 - 9:30 am UTC
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
March 19, 2003 - 10:49 am UTC
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?
Sachin, April 23, 2003 - 4:36 pm UTC
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.
April 23, 2003 - 8:15 pm UTC
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...
Sachin, April 23, 2003 - 5:29 pm UTC
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).
April 23, 2003 - 8:15 pm UTC
dbms_sql
SET_CONTEXT.VALUE LIMIT
Sachin, April 24, 2003 - 4:26 pm UTC
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
mo, May 08, 2003 - 4:51 pm UTC
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,
May 09, 2003 - 1:03 pm UTC
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
mo, May 09, 2003 - 6:50 pm UTC
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
May 09, 2003 - 7:14 pm UTC
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
darren, May 12, 2003 - 1:43 pm UTC
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
Matt, May 14, 2003 - 1:21 am UTC
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.
May 14, 2003 - 7:00 am UTC
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.
Matt, May 14, 2003 - 8:02 pm UTC
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
May 14, 2003 - 11:50 pm UTC
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
mo, May 19, 2003 - 6:03 pm UTC
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
May 20, 2003 - 7:26 am UTC
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
mo, May 19, 2003 - 6:20 pm UTC
Tom:
if i have
for x in ( select * from table)
LOOP
---HERE can i get how many records were retrieved
...
....
END LOOP;
Thank you,
May 20, 2003 - 7:29 am UTC
add rownum to the query or count them.
Great Information
Deanna, May 23, 2003 - 6:52 am UTC
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 - 9:00 am UTC
Deanna, May 23, 2003 - 5:56 pm UTC
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 ?
May 24, 2003 - 10:12 am UTC
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
A reader, May 25, 2003 - 7:37 am UTC
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?)
May 25, 2003 - 10:01 am UTC
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)
Deanna, May 25, 2003 - 7:48 am UTC
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 - 10:02 am UTC
A reader, May 25, 2003 - 11:22 am UTC
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).
May 25, 2003 - 11:39 am UTC
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
mo, May 25, 2003 - 9:42 pm UTC
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?
May 26, 2003 - 9:19 am UTC
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
A reader, May 26, 2003 - 12:15 am UTC
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
Sachin, May 27, 2003 - 5:49 pm UTC
(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?
May 27, 2003 - 6:00 pm UTC
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
Sachin, May 27, 2003 - 6:38 pm UTC
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 || ''' )' || '%';
....
....
May 27, 2003 - 6:43 pm UTC
up to you -- whatever makes more sense codewise to you
Cursor in 8.1.7
Randy, May 31, 2003 - 7:45 pm UTC
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!
May 31, 2003 - 9:23 pm UTC
you will have to provide a test case.
I've never observed what you say -- never.
Randy, May 31, 2003 - 9:49 pm UTC
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.
June 01, 2003 - 9:08 am UTC
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)
Randy, June 01, 2003 - 9:59 am UTC
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!
Randy, June 02, 2003 - 12:11 pm UTC
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
June 02, 2003 - 12:36 pm UTC
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
Randy, June 02, 2003 - 1:39 pm UTC
Tom,
Thanks for all your help. Your example was exactly what I was looking for!!
Randy
Something Global inside triggers
Sachin, June 05, 2003 - 11:42 am UTC
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.
June 05, 2003 - 1:30 pm UTC
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
Sachin, June 05, 2003 - 4:58 pm UTC
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
June 05, 2003 - 6:35 pm UTC
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
mo, June 06, 2003 - 5:00 pm UTC
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?
June 06, 2003 - 7:59 pm UTC
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
mo, June 07, 2003 - 1:59 pm UTC
Tom:
Thanks, but is COM software part of oracle database? or it is some extra tool that you have to get?
June 07, 2003 - 2:30 pm UTC
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
Sachin, June 12, 2003 - 11:11 am UTC
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.
June 12, 2003 - 11:21 am UTC
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!!!
lou, June 12, 2003 - 11:47 am UTC
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
June 12, 2003 - 1:04 pm UTC
like doesn't prevent an index?
not sure what you are asking for here?
related to original question
sam, June 17, 2003 - 11:48 am UTC
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
June 17, 2003 - 12:54 pm UTC
I did not put this earlier
sam, June 17, 2003 - 1:34 pm UTC
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
Sachin, June 25, 2003 - 3:56 pm UTC
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
June 25, 2003 - 8:44 pm UTC
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
A reader, July 01, 2003 - 5:42 pm UTC
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
Maharaj ki Jai Ho, July 01, 2003 - 11:12 pm UTC
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
July 02, 2003 - 8:02 am UTC
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
ramesh, July 29, 2003 - 9:09 am UTC
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
July 29, 2003 - 11:09 am UTC
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
ramesh, July 30, 2003 - 9:28 am UTC
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
July 30, 2003 - 10:10 am UTC
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
ramesh, July 30, 2003 - 10:31 am UTC
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
July 30, 2003 - 10:56 am UTC
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
Steve, August 22, 2003 - 1:35 pm UTC
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.
August 22, 2003 - 8:06 pm UTC
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
Mohan K, August 25, 2003 - 4:53 am UTC
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
August 25, 2003 - 6:41 am UTC
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
A reader, August 25, 2003 - 10:37 pm UTC
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!
A reader, September 04, 2003 - 11:45 am UTC
9.2.0.4 and sys_context
Arun Gupta, September 21, 2003 - 12:09 pm UTC
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...
September 21, 2003 - 1:03 pm UTC
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.
Arun Gupta, September 21, 2003 - 10:20 pm UTC
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...
September 22, 2003 - 7:26 am UTC
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?
Jap Boon Churn, October 01, 2003 - 3:24 am UTC
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.
October 01, 2003 - 8:36 am UTC
tkprof will show us what the issue is. lets see the tkprof of the two queries.
9.2.0.4 and sys_context
Arun Gupta, October 10, 2003 - 1:09 pm UTC
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.
A reader, October 29, 2003 - 10:31 am UTC
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 /
October 29, 2003 - 11:24 am UTC
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
mo, December 03, 2003 - 4:35 pm UTC
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
December 03, 2003 - 6:54 pm UTC
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
mo, December 04, 2003 - 10:53 am UTC
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,
December 04, 2003 - 11:14 am UTC
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
mo, December 15, 2003 - 9:59 am UTC
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,
December 15, 2003 - 10:13 am UTC
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
mo, December 15, 2003 - 11:35 am UTC
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,
December 15, 2003 - 3:48 pm UTC
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
mo, December 17, 2003 - 11:22 am UTC
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;
December 18, 2003 - 8:38 am UTC
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.
reader, December 29, 2003 - 5:17 am UTC
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.
December 29, 2003 - 10:32 am UTC
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
reader, December 30, 2003 - 5:40 am UTC
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.
December 30, 2003 - 10:21 am UTC
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.
Valar, January 22, 2004 - 11:34 am UTC
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
January 22, 2004 - 7:39 pm UTC
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.
Valar, January 26, 2004 - 5:29 pm UTC
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
January 26, 2004 - 5:56 pm UTC
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.
Valar, January 27, 2004 - 9:10 am UTC
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
Valar, January 28, 2004 - 2:58 pm UTC
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>
January 29, 2004 - 7:37 am UTC
see
</code>
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:440419921146 <code>
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
Valar, January 29, 2004 - 10:57 pm UTC
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??
dwl, February 20, 2004 - 7:01 am UTC
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
February 20, 2004 - 9:59 am UTC
both use binds fully.
Confused
dwl, February 24, 2004 - 6:13 am UTC
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!
February 24, 2004 - 8:18 am UTC
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)
Mihail Bratu, March 05, 2004 - 8:02 am UTC
thanks but....
dwl, March 09, 2004 - 4:50 am UTC
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:
</code>
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:4473634500618, <code>
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
March 09, 2004 - 11:40 am UTC
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?
A reader, March 24, 2004 - 3:30 pm UTC
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!
March 24, 2004 - 4:26 pm UTC
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
A reader, March 25, 2004 - 7:12 pm UTC
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.
March 25, 2004 - 9:37 pm UTC
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"
A reader, March 26, 2004 - 8:09 am UTC
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.
March 26, 2004 - 9:33 am UTC
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
Fan from Germany, March 26, 2004 - 4:56 pm UTC
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.
March 26, 2004 - 6:53 pm UTC
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
Susan, March 29, 2004 - 1:31 pm UTC
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.
March 29, 2004 - 1:56 pm UTC
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.
Jim Nasby, March 29, 2004 - 3:52 pm UTC
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.
March 29, 2004 - 4:24 pm UTC
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
Arangaperumal, March 30, 2004 - 5:59 am UTC
I really wonder about the techniques you are
using.
dynamic where
mary w, April 14, 2004 - 3:58 pm UTC
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;
/
April 14, 2004 - 4:19 pm UTC
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
mary w, April 14, 2004 - 5:07 pm UTC
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;
/
April 15, 2004 - 7:39 am UTC
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
mary w, April 15, 2004 - 9:33 am UTC
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.
April 15, 2004 - 9:43 am UTC
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?
Vera, April 15, 2004 - 9:57 pm UTC
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.
April 16, 2004 - 7:16 am UTC
(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
Vera, April 16, 2004 - 12:20 am UTC
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"?
April 16, 2004 - 7:20 am UTC
the functions are the primary culprit here.
Dynamic Query
mary W, April 19, 2004 - 12:05 pm UTC
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
April 19, 2004 - 12:30 pm UTC
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
Mary W, April 19, 2004 - 12:44 pm UTC
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.
April 19, 2004 - 3:03 pm UTC
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!
Mary Wiest, April 19, 2004 - 3:10 pm UTC
Thank you so much, Tom!
I will be using bind variables all the time now!
--mw
Callable statement and bind variables
Mary W, April 19, 2004 - 3:26 pm UTC
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(?,?,?,?,?,:?)}";
April 19, 2004 - 7:28 pm UTC
I would think it would -- ? is how to bind. not sure what :? would do.
? is all you need.
A reader, April 27, 2004 - 9:17 pm UTC
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
Oleg Oleander, May 05, 2004 - 4:54 am UTC
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?
May 05, 2004 - 7:54 am UTC
yes, they are different queries.
a Trailing blank is enough to make two queries different.
Max, May 12, 2004 - 10:29 am UTC
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
May 12, 2004 - 6:53 pm UTC
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
Oleg Oleander, May 14, 2004 - 7:43 pm UTC
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
May 15, 2004 - 12:29 pm UTC
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
Oleg, May 17, 2004 - 5:03 pm UTC
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
May 18, 2004 - 7:35 am UTC
not understanding why an index view "could not be used", it would not preclude indexes or anything.
Oleg, May 18, 2004 - 8:12 am UTC
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
May 18, 2004 - 3:01 pm UTC
sorry -- meant the INLINE view, don't see why that cannot be used with an index.
DBMS_SESSION.SET_CONTEXT parameter limit
dxl, May 24, 2004 - 10:57 am UTC
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
May 24, 2004 - 11:17 am UTC
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
dxl, May 24, 2004 - 11:56 am UTC
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?
May 24, 2004 - 1:01 pm UTC
"_" 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
A reader, June 14, 2004 - 3:20 pm UTC
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,
June 15, 2004 - 8:02 am UTC
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
A reader, June 16, 2004 - 9:51 am UTC
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.
June 16, 2004 - 12:59 pm UTC
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
A reader, June 16, 2004 - 1:53 pm UTC
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.
June 16, 2004 - 3:38 pm UTC
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?
Arn, June 17, 2004 - 3:13 am UTC
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.
June 17, 2004 - 9:58 am UTC
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?
Mike Burford, June 29, 2004 - 5:11 pm UTC
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
June 29, 2004 - 6:57 pm UTC
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
Mike Burford, June 30, 2004 - 2:42 pm UTC
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
June 30, 2004 - 3:06 pm UTC
i've no idea what I'm looking at here....
Baffled by optimizer
Mike Burford, June 30, 2004 - 4:53 pm UTC
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
June 30, 2004 - 8:05 pm UTC
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
Qaisar, July 05, 2004 - 12:13 pm UTC
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.
July 05, 2004 - 1:40 pm UTC
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
Qaisar, July 05, 2004 - 6:34 pm UTC
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?
July 05, 2004 - 8:27 pm UTC
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
venkat, July 06, 2004 - 9:00 am UTC
Why a trigger is allowed to have the same name as the table??
Can two different schema objects have the same name??
July 06, 2004 - 9:05 am UTC
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
Qaisar, July 06, 2004 - 12:28 pm UTC
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"
July 06, 2004 - 2:14 pm UTC
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
Qaisar, July 06, 2004 - 5:21 pm UTC
Hi Tom,
You are executing this on 9i, I have 8i and getting the error.
July 06, 2004 - 7:51 pm UTC
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
Maq, July 06, 2004 - 5:26 pm UTC
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.
July 06, 2004 - 7:53 pm UTC
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
A reader, July 12, 2004 - 1:36 pm UTC
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
July 12, 2004 - 9:00 pm UTC
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!
Jamie, July 13, 2004 - 1:00 pm UTC
This was a fantastic article. I was easy to follow and helped my project out enormously. Thanks again!
question about using context
James Su, July 14, 2004 - 5:21 pm UTC
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.
July 15, 2004 - 11:47 am UTC
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
James Su, July 15, 2004 - 10:30 am UTC
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
A reader, July 29, 2004 - 2:10 pm UTC
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!
July 29, 2004 - 2:20 pm UTC
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
Doug, August 11, 2004 - 4:32 pm UTC
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
August 12, 2004 - 8:27 am UTC
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
dxl, August 12, 2004 - 6:16 am UTC
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
August 12, 2004 - 9:11 am UTC
how's about a test case I too can run -- tables, data, et.al.
..
dxl, August 12, 2004 - 9:55 am UTC
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??
August 12, 2004 - 10:34 am UTC
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
<b>the workaround is fairly simple and not needed in 9i and up</b>
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<b>||'', </b>rec_Cur.Attribute<b>||''</b>, null);
30 END LOOP;
alternatively, just assign the variable to itself, that'll null terminate it as well.
thanks!
dxl, August 12, 2004 - 12:01 pm UTC
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
August 12, 2004 - 12:36 pm UTC
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
Doug, August 12, 2004 - 4:45 pm UTC
The client is Java using a JDBC connection.
Thanks Tom
August 12, 2004 - 4:53 pm UTC
they work just as functionally with weak as with strongly typed -- why do you need a strongly typed cursor?
My production solution
Michael, August 20, 2004 - 2:42 am UTC
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?
August 20, 2004 - 10:56 am UTC
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 ...
Michael, August 25, 2004 - 7:00 am UTC
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
Alex, August 28, 2004 - 5:04 pm UTC
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.
August 28, 2004 - 5:11 pm UTC
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 ?
robert, September 02, 2004 - 10:20 am UTC
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
September 02, 2004 - 10:52 am UTC
benchmark it and let us know!
Seems No Difference to me....
robert, September 02, 2004 - 11:12 am UTC
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
koola, October 12, 2004 - 12:44 pm UTC
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
October 12, 2004 - 2:08 pm UTC
<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.
A reader, November 15, 2004 - 4:38 pm UTC
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
November 15, 2004 - 9:17 pm UTC
a sys_refcursor is just a predefined type, saves you have having to define your own.
would be the same otherwise.
Very helpful, but ....
David, November 17, 2004 - 4:39 am UTC
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.
November 17, 2004 - 10:36 am UTC
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 /
<b>tkprof says...</b>
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)
<b>so, it would optimize the c3=nvl(:c3,c3) but not all of the combinations</b>
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
bhagirath pesaru, November 22, 2004 - 7:01 am UTC
very good thanks
Using bind variables
Sanjaya Balasuriya, November 29, 2004 - 8:16 pm UTC
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
November 29, 2004 - 8:23 pm UTC
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 ...
Michael, November 30, 2004 - 3:27 am UTC
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?
November 30, 2004 - 7:40 am UTC
it is very much almost the same as the decode suggestion above by Darko Egersdorfer from South Africa
Doubt on Cursor
TH, December 02, 2004 - 4:21 am UTC
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
December 02, 2004 - 7:43 am UTC
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!
TH, December 04, 2004 - 2:28 am UTC
Thanks..
Just Beautiful!
TH, BAH
Passing a ResultSet as Ref Cursor
A reader, December 04, 2004 - 4:16 pm UTC
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
A reader, December 05, 2004 - 1:29 pm UTC
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)?
December 05, 2004 - 7:03 pm UTC
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!
Siobhan, December 09, 2004 - 2:23 pm UTC
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
December 09, 2004 - 3:30 pm UTC
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
Siobhan, December 10, 2004 - 4:18 am UTC
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
December 10, 2004 - 10:57 am UTC
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
A reader, December 10, 2004 - 6:13 am UTC
Most helpful!
Siobhan, December 13, 2004 - 5:26 am UTC
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
Ramesh, December 17, 2004 - 3:23 am UTC
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.
December 17, 2004 - 7:59 am UTC
no idea what a ".net interface" is.
or how it could possible affect a stored procedure returning a ref cursor.
Damn Good
Ramesh, December 17, 2004 - 4:58 am UTC
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
Ramesh, December 21, 2004 - 3:07 pm UTC
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;
December 21, 2004 - 3:22 pm UTC
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
Ramesh, December 21, 2004 - 7:03 pm UTC
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.
December 21, 2004 - 7:28 pm UTC
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
Ramesh, December 21, 2004 - 8:54 pm UTC
Hi Tom,
Thanks for the clarification.
Cursor
Vithal, December 22, 2004 - 2:48 am UTC
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
December 22, 2004 - 9:32 am UTC
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
Abubaker Khered, December 25, 2004 - 1:57 am UTC
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
December 25, 2004 - 9:09 am UTC
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?
Abubaker Khered, December 26, 2004 - 1:47 am UTC
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
December 26, 2004 - 12:39 pm UTC
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
Abubaker Khered, December 27, 2004 - 12:46 am UTC
Thanks
Interval Sum
Vithal, December 29, 2004 - 8:19 am UTC
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..
December 29, 2004 - 10:24 am UTC
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
Vithal, December 30, 2004 - 5:22 am UTC
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...
December 30, 2004 - 10:49 am UTC
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
A reader, December 30, 2004 - 9:11 am UTC
Hi Tom,
Still not clear can u please give me another example.
December 30, 2004 - 11:05 am UTC
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
Vithal, January 05, 2005 - 5:16 am UTC
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
January 05, 2005 - 9:53 am UTC
search this site for
pivot
Viable Alternative?
Kyle, January 27, 2005 - 6:04 pm UTC
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
January 28, 2005 - 7:14 am UTC
ctl-f for "Darko" on this page.
that would be a cleaner way to do this, no array needed.
Viable Alternative
Kyle, January 28, 2005 - 9:46 am UTC
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
January 28, 2005 - 2:27 pm UTC
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
Kyle, January 28, 2005 - 9:54 am UTC
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
January 28, 2005 - 2:27 pm UTC
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
Kyle, January 31, 2005 - 3:42 pm UTC
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
January 31, 2005 - 4:04 pm UTC
yup, good point.
Great discussion
Martin, February 03, 2005 - 3:17 am UTC
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
February 03, 2005 - 1:27 pm UTC
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
JOJEDA, February 03, 2005 - 2:41 pm UTC
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,
February 04, 2005 - 1:22 am UTC
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
Harp, February 23, 2005 - 6:48 am UTC
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
February 23, 2005 - 9:20 am UTC
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?
Andrew, March 03, 2005 - 5:33 pm UTC
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?
March 03, 2005 - 5:46 pm UTC
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 04, 2005 - 9:00 am UTC
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,
'.......'
March 04, 2005 - 9:34 am UTC
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 04, 2005 - 10:14 am UTC
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:</code>
http://www.lc.leidenuniv.nl/awcourse/oracle/server.920/a96540/functions122a.htm#1038178 <code>
March 04, 2005 - 11:02 am UTC
govindarajn, March 31, 2005 - 3:01 am UTC
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?
March 31, 2005 - 7:40 am UTC
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
A reader, March 31, 2005 - 2:07 pm UTC
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.
March 31, 2005 - 3:08 pm UTC
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
A reader, April 01, 2005 - 1:51 pm UTC
How about a java stored procedure?
April 01, 2005 - 1:55 pm UTC
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
A reader, April 11, 2005 - 11:15 am UTC
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.
April 11, 2005 - 11:20 am UTC
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...
A reader, April 20, 2005 - 4:46 pm UTC
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.
April 20, 2005 - 9:15 pm UTC
how about you give me the example that fails....
A reader, April 20, 2005 - 5:06 pm UTC
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.
April 20, 2005 - 9:17 pm UTC
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?
A reader, April 21, 2005 - 8:22 am UTC
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.
April 21, 2005 - 11:53 am UTC
*give ENTIRE example*
something i can cut and paste and run
small, concise, yet 100% complete.
A reader, April 21, 2005 - 12:47 pm UTC
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;
April 22, 2005 - 9:12 am UTC
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.
A reader, April 22, 2005 - 9:33 am UTC
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!
April 22, 2005 - 10:45 am UTC
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
A reader, April 22, 2005 - 11:01 am UTC
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!
April 22, 2005 - 11:10 am UTC
provide EXAMPLE
with or without a context set, should not matter -- the results are "deterministic" as they say.
grant privilege for alter trigger
Ronald Chan, April 27, 2005 - 5:11 am UTC
It is possible to grant just alter trigger privilege to other users?
so that alter trigger is the only action this user can do.
Alex, May 05, 2005 - 11:46 am UTC
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.
May 05, 2005 - 12:59 pm UTC
well, you can select CURSOR( select ...) from table, so the semantics are there for doing so, yes.
connection pool and SYS_CONTEXT
reader, May 23, 2005 - 2:25 pm UTC
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,
May 23, 2005 - 7:07 pm UTC
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
reader, May 25, 2005 - 11:24 am UTC
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.
Lora, May 25, 2005 - 6:05 pm UTC
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
May 25, 2005 - 8:06 pm UTC
no, they won't be. I would (do) use the approach above.
deepak, May 26, 2005 - 3:45 am UTC
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.
May 26, 2005 - 8:46 am UTC
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
Michael Nelson, May 26, 2005 - 6:42 pm UTC
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);
...
...
May 26, 2005 - 7:47 pm UTC
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
Kamal, May 31, 2005 - 10:51 am UTC
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
May 31, 2005 - 6:30 pm UTC
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.
</code>
http://asktom.oracle.com/~tkyte/flat/index.html <code>
has examples of plsql dumping any query to a flat file for example
nina, July 17, 2005 - 11:43 pm UTC
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..
July 18, 2005 - 7:53 am UTC
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
A reader, July 21, 2005 - 5:15 pm UTC
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,
July 21, 2005 - 6:09 pm UTC
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
Tony, July 22, 2005 - 3:34 am UTC
Hi tom,
Can you please guide me how to impliment N+(N+1) logic in sql query
and looping in SQL.
Cheers
Tony
July 22, 2005 - 8:53 am UTC
what is n+(n+1) logic?
Query
Tony, July 25, 2005 - 8:45 am UTC
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
July 25, 2005 - 9:08 am UTC
then I would say you built the table wrong, it should go down the page, not across the page.
Better Practice
A Reader, July 26, 2005 - 6:39 am UTC
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.
July 26, 2005 - 8:16 am UTC
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.
A reader, July 26, 2005 - 9:46 am UTC
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?
July 26, 2005 - 10:00 am UTC
I've got a pro*c app you can use if you want (see </code>
http://asktom.oracle.com/~tkyte/flat/index.html <code>
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
Tony, July 26, 2005 - 12:24 pm UTC
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
July 26, 2005 - 12:35 pm UTC
"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)
A reader, July 26, 2005 - 1:07 pm UTC
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?
July 26, 2005 - 1:10 pm UTC
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
Maverick, August 01, 2005 - 5:38 pm UTC
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.
August 01, 2005 - 8:45 pm UTC
test case is where?
Ref cursor
A reader, August 03, 2005 - 11:47 am UTC
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;
August 03, 2005 - 12:10 pm UTC
you cannot, they would each have to get their "own" cursor. You fetch a row once.
Randy
Randy, August 19, 2005 - 7:12 pm UTC
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
August 20, 2005 - 4:58 pm UTC
ctl-f for
Darko Egersdorfer
on this page to see how to do this without sys_context.
RANDY
RANDY, August 22, 2005 - 10:49 am UTC
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
August 23, 2005 - 4:03 am UTC
need example.
how much memory
reader, August 22, 2005 - 3:35 pm UTC
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
Mirela, August 31, 2005 - 11:30 am UTC
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!
August 31, 2005 - 1:59 pm UTC
ora-600 = contact support
Help with package design
TH, September 13, 2005 - 8:51 pm UTC
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,
September 13, 2005 - 9:01 pm UTC
I'm not sure what the question is?
A reader, September 13, 2005 - 10:48 pm UTC
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
September 14, 2005 - 8:05 am UTC
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
A reader, September 14, 2005 - 10:45 am UTC
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.
September 14, 2005 - 11:46 am UTC
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..
A reader, September 14, 2005 - 12:47 pm UTC
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]
September 14, 2005 - 1:34 pm UTC
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
A reader, October 07, 2005 - 8:14 am UTC
Tom,
May i know the difference between using "SYS_CONTEXT" and :USING clause to
bind variables in dynamic Ref Cursor?
Thanks..
October 07, 2005 - 9:16 am UTC
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
Anne, October 18, 2005 - 4:16 pm UTC
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;
October 18, 2005 - 5:30 pm UTC
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
A reader, October 25, 2005 - 1:10 pm UTC
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.
October 26, 2005 - 8:53 am UTC
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
Rahul Dutta, October 28, 2005 - 4:14 pm UTC
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
October 29, 2005 - 10:53 am UTC
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
James, November 10, 2005 - 12:16 am UTC
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
November 11, 2005 - 11:34 am UTC
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)
James, November 10, 2005 - 12:46 am UTC
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
November 11, 2005 - 11:35 am UTC
so, did you stop to compare the plans and see if one of them changed or not.
columns in refcursor
Oleg Oleander, November 10, 2005 - 8:33 am UTC
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.
November 11, 2005 - 11:49 am UTC
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
Laxman Kondal, November 10, 2005 - 12:55 pm UTC
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.
November 11, 2005 - 11:56 am UTC
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
Laxman Kondal, November 11, 2005 - 10:23 am UTC
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
November 12, 2005 - 8:38 am UTC
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)
Oleg Oleander, November 12, 2005 - 3:46 am UTC
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
November 12, 2005 - 10:56 am UTC
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?
Oleg Oleander, November 12, 2005 - 1:20 pm UTC
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.
November 12, 2005 - 1:39 pm UTC
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
Oleg Oleander, November 13, 2005 - 7:07 pm UTC
I am still curious how TOAD can do this (describing the returned ref_cursor). Or is they using some undocumented api?
Thank you
November 14, 2005 - 7:49 am UTC
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 ?
Stein Andersen, November 17, 2005 - 12:39 pm UTC
Is there possible to build a REFERENCE_CURSOR, such that not only the WHERE-Part, but also the SELECT-Part is dynamic.
And further is it possible to return such a CURSOR back from a function, currently the function is called from IIS trough ADO.
Have now created oracle functions for generating and sorting a "temp-table", used as cache for paging/sorting from application. The functions are generic so the SQL's (used as subqery) is currently received as a parameter, and executed using EXECUTE IMMEDIATE into the "temp-table". SQL will have different number of attributes in the select-part depending on use.
Currently the function(s) are only returning "number of rows", actual rows are fetched using seperat SQL. Wants them to return a cursor for the records on each page if possible.
getting "invalid cursor" error
Dhamo, November 26, 2005 - 7:59 am UTC
Dear Tom,
I have the following function, that works fine
with 9i databases & fails with "invalid cursor" err in 10G.
#######
Create or Replace FUNCTION fun RETURN INTEGER IS
FLAG integer default 0;
CNT integer default 0;
CURSOR Test_C IS
SELECT OWNER, TABLE_NAME
FROM ALL_CONSTRAINTS
where OWNER NOT IN ('SYS', 'SYSTEM');
BEGIN
FOR KEY IN Test_C
LOOP
<<PROCESS_NEXT>>
CNT := CNT + 1;
IF (FLAG = 1) THEN
BEGIN
FETCH Test_C into KEY; --- This works fine in 9i, but fails in 10G with "invalid cursor" err
FLAG := 0;
END;
END IF;
IF ( CNT = 3 ) THEN -- if this condition received
BEGIN
FLAG := 1;
GOTO PROCESS_NEXT; --- i wanted to go back and fetch for the next record in the cursor.
END;
END IF;
-- additional code ....
END LOOP;
RETURN 0;
End;
/
########
I'm receiving "invalid cursor" error while running in 10G
eventhough funciton is created successfully.
######
SQL> select fun from dual;
select fun from dual
*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at "TEST.FUN", line 19
######
Could you pls help me what's Ora 10G is expecting here.
Thanks so much for your time.
-Dhamdaran
November 26, 2005 - 12:38 pm UTC
it should never have worked - that is an amazing piece of code there.
You have an IMPLICIT cursor that you are fetching from explicitly. I'm sorry there was a bug in 9i that allowed this code to accidently appear to execute properly - but it really needs to be rewritten.
that code is somewhat scary - lots of things wrong with it - goto not withstanding, the fetch test_c into key is the scariest part of it all.
consider:
ops$tkyte@ORA9IR2> create table t ( a int );
Table created.
ops$tkyte@ORA9IR2> insert into t values ( 1 );
1 row created.
ops$tkyte@ORA9IR2> insert into t values ( 2 );
1 row created.
ops$tkyte@ORA9IR2> insert into t values ( 3 );
1 row created.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
2 cursor c is select * from t;
3 begin
4 for x in c
5 loop
6 dbms_output.put_line( x.a );
7 fetch c into x;
8 dbms_output.put_line( x.a );
9 end loop;
10 end;
11 /
1
2
3
3
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> insert into t values ( 4 );
1 row created.
ops$tkyte@ORA9IR2> declare
2 cursor c is select * from t;
3 begin
4 for x in c
5 loop
6 dbms_output.put_line( x.a );
7 fetch c into x;
8 dbms_output.put_line( x.a );
9 end loop;
10 end;
11 /
1
2
3
4
PL/SQL procedure successfully completed.
1,2,3,3 <<<=====!!!!
fortunately for us, 10g has fixed this bad bug:
ops$tkyte@ORA10GR2> declare
2 cursor c is select * from t;
3 begin
4 for x in c
5 loop
6 dbms_output.put_line( x.a );
7 fetch c into x;
8 dbms_output.put_line( x.a );
9 end loop;
10 end;
11 /
1
declare
*
ERROR at line 1:
ORA-01001: invalid cursor
ORA-06512: at line 7
You cannot explicitly process an implicit cursor.
Thanks
Dhamo, November 28, 2005 - 12:08 am UTC
Dear Tom,
Thanks for explaining the bug with clear examples.
It's very much helpful to understand the differences
between 9i & 10g.
Many thanks,
Dhamodaran.
PLEASE HELP
A reader, January 04, 2006 - 5:52 pm UTC
please see and help me getting the output
begin
for a in (select dname || ' has '||count(*) ||' employees '
from emp e,dept d
where e.deptno=d.deptno
group by dname)
loop
dbms_output.put_line(a.dname || ' has '||a.count(*) ||' employees ');
end LOOP;
end;
thanks
January 05, 2006 - 9:24 am UTC
for a in ( select <expression> AS YOUR_NAME
from ... )
loop
dbms_output.put_line( a.YOUR_NAME );
you are selecting an expression - name it.
reader
A reader, January 05, 2006 - 12:19 pm UTC
Hi Tom
like we can measure the performance of a sql statement with the help of an explain plan ..how can we measure the performance of a procedure or function or a cursor.....
thanks..
January 05, 2006 - 1:18 pm UTC
you cannot measure the performance of a sql statement with an explain plan. You can only see what sort of execution will take place at run time.
A cursor is simply SQL - so you would use sql_trace to measure the performance of SQL (sql_trace it, tkprof the trace file, read the report)
you can use dbms_profiler (search this site for examples) in a similar manner for your plsql.
reader
A reader, January 06, 2006 - 7:45 pm UTC
Hi Tom ,
How are you..i recently got a test on cursor ..two of the questions i wanted to share with you are ....
question 1
-----------
which of the following statements correctly identify the differences between strong and weak ref cursors
1)
you can use weak ref cursor to declare a cursor vaiable THAT CAN FETCH ROWS FROM ANY SQL STATEMENT
2)
a strong ref cursor offers strong performance and should be used whenever possible to improve program efficiency
3)
using weak ref cursor means you have to deal more often with runtime errors as oppossed to compile time error for strongly typed ref cursors
4)
you must close a cursor variable based on strong ref cursor twice because it persists (strongly) past the first close
5)
a cursor variable based on strong ref cursor fetches data only into a record whose structure is compatible with return clause in the ref cursor
for this above question i gave the answers as 2,3,5(which i thought were correct)..but the answers were 1,2,5...could you please expalin why is that..like why 1 is correct and 3 is NOT CORRECT.
question 2
-----------
which of the following scenerios call for the use of ref cursor?
a)
various application components including an oracle form application and several stored procedures all need to work with the same resultset
b)
the colums to be queried and the tables from which to be queried are not known until run time
c)
you have six different queries,all exectly same exept for the different order by clause. you need to write a program to display the data from each query
d)
you need to reduce the networkj traffic between client and the server oracle programs
e)
you want to let the user supply the where clause to apply to otherwise standardized query
the answer is a,c,d but i gave as a d e may i know why e should not be there as answer
January 07, 2006 - 10:08 am UTC
the <b>correct</b> answer for question 1 is 1, 3, 5.
1 - a weakly typed cursor variable can fetch from arbitrary sql statements - the number and types of the selected columns may vary. a strongly typed ref cursor can only fetch from queries that have the same number of columns and that are type compatible.
ops$tkyte@ORA10GR2> create or replace package types
2 as
3 type weak is ref cursor;
4 type strong is ref cursor return all_objects%rowtype;
5 end;
6 /
Package created.
ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> declare
2 x types.weak;
3 begin
4 open x for select cursor(select count(*) from dual), 1 from dual;
5 close x;
6 open x for select 1, sysdate, 'hello world' from dual;
7 close x;
8 end;
9 /
PL/SQL procedure successfully completed.
ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> declare
2 x types.strong;
3 begin
4 open x for select cursor(select count(*) from dual), 1 from dual;
5 end;
6 /
open x for select cursor(select count(*) from dual), 1 from dual;
*
ERROR at line 4:
ORA-06550: line 4, column 13:
PLS-00382: expression is of wrong type
ORA-06550: line 4, column 2:
PL/SQL: SQL Statement ignored
ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> declare
2 x types.strong;
3 begin
4 open x for select * from all_objects;
5 end;
6 /
PL/SQL procedure successfully completed.
2 - <b>does not apply</b>, it is not part of the answer. Weak and Strong ref cursors perform identicially. Strong ref cursors have some compile time advantages in the area of TYPE CHECKING, but at run time - - they are "the same"
ops$tkyte@ORA10GR2> create or replace package types
2 as
3 type weak is ref cursor;
4 type strong is ref cursor return all_objects%rowtype;
5 end;
6 /
Package created.
ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> create or replace procedure weak_cur
2 as
3 l_cur types.weak;
4 l_rec all_objects%rowtype;
5 type array is table of all_objects%rowtype index by binary_integer;
6 l_array array;
7 begin
8 open l_cur for select * from all_objects;
9 loop
10 fetch l_cur into l_rec;
11 exit when l_cur%notfound;
12 end loop;
13 close l_cur;
14 open l_cur for select * from all_objects;
15 loop
16 fetch l_cur bulk collect into l_array limit 100;
17 exit when l_cur%notfound;
18 end loop;
19 close l_cur;
20 end;
21 /
Procedure created.
ops$tkyte@ORA10GR2> create or replace procedure strong_cur
2 as
3 l_cur types.strong;
4 l_rec all_objects%rowtype;
5 type array is table of all_objects%rowtype index by binary_integer;
6 l_array array;
7 begin
8 open l_cur for select * from all_objects;
9 loop
10 fetch l_cur into l_rec;
11 exit when l_cur%notfound;
12 end loop;
13 close l_cur;
14 open l_cur for select * from all_objects;
15 loop
16 fetch l_cur bulk collect into l_array limit 100;
17 exit when l_cur%notfound;
18 end loop;
19 close l_cur;
20 end;
21 /
Procedure created.
ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> exec runStats_pkg.rs_start
PL/SQL procedure successfully completed.
ops$tkyte@ORA10GR2> exec weak_cur
PL/SQL procedure successfully completed.
ops$tkyte@ORA10GR2> exec runStats_pkg.rs_middle
PL/SQL procedure successfully completed.
ops$tkyte@ORA10GR2> exec strong_cur
PL/SQL procedure successfully completed.
ops$tkyte@ORA10GR2> exec runStats_pkg.rs_stop(100)
Run1 ran in 628 hsecs
Run2 ran in 628 hsecs
run 1 ran in 100% of the time
Name Run1 Run2 Diff
STAT...redo size 2,840 2,964 124
STAT...session pga memory 0 196,608 196,608
Run1 latches total versus runs -- difference and pct
Run1 Run2 Diff Pct
608,845 608,706 -139 100.02%
PL/SQL procedure successfully completed.
3) <b>is correct, weakly typed variables in general (any language) have this attribute</b>. strongly types variables catch more "errors" at compile time. Consider:
ops$tkyte@ORA10GR2> create or replace procedure weak_demo
2 as
3 x types.weak;
4 l_rec all_users%rowtype;
5 begin
6 open x for select * from all_objects;
7 fetch x into l_rec;
8 close x;
9 end;
10 /
Procedure created.
ops$tkyte@ORA10GR2> exec weak_demo
BEGIN weak_demo; END;
*
ERROR at line 1:
ORA-06504: PL/SQL: Return types of Result Set variables or query do not match
ORA-06512: at "OPS$TKYTE.WEAK_DEMO", line 7
ORA-06512: at line 1
ops$tkyte@ORA10GR2> create or replace procedure strong_demo
2 as
3 x types.strong;
4 l_rec all_users%rowtype;
5 begin
6 open x for select * from all_objects;
7 fetch x into l_rec;
8 close x;
9 end;
10 /
Warning: Procedure created with compilation errors.
ops$tkyte@ORA10GR2> show errors
Errors for PROCEDURE STRONG_DEMO:
LINE/COL ERROR
-------- -----------------------------------------------------------------
7/2 PL/SQL: SQL Statement ignored
7/2 PLS-00394: wrong number of values in the INTO list of a FETCH
statement
<b>note that "weak" compiled - but does not run. "strong" won't even compile as the compiler can "tell" 'this will never work'</b>
4) <b>does not apply, it is just false</b>
5) <b>is true as demonstrated above</b>. The key is "COMPATIBLE" - it need not be IDENTICAL. you can fetch a date/number column into a varchar2 - assuming it is long enough...
--------------------------------------------------------------------
question 2
The answer is ambigous. Subjective. I would say that technically <b>NONE OF THEM MAKE YOU USE A REF CURSOR</b>. You might choose to do so - but it is not mandatory.
If the question were "which of the following scenarios would likely cause you to <b>consider</b> using a ref cursor" - my answer would be:
a, b, c, e
If the question is "which of the following scenarios make you use a ref cursor", the answer is:
<this space intentionally left blank>
a) I could just as easily use a VIEW. A ref cursor could well be useful. (a) could be choosen.
b) I might use a ref cursor, I might use dbms_sql. (b) could be choosen (surprising you say (b) was not an answer??)
l_query := 'select ' || p_columns_passed_in || ' from t where ...';
open l_cursor for l_query;
c) I might use a ref cursor, I might use decode - say I wanted to order by any of columns C1, C2, C3
for x in ( select * from t where ....
order by decode( p_order_by, 1, c1 ),
decode( p_order_by, 2, c2 ),
decode( p_order_by, 3, c3 ) )
loop
or I could:
open l_cursor for 'select * from t where ... order by c' || p_order_by;
or I could:
if ( p_order_by = 1 ) then
open l_cursor for select * from t where ... order by c1;
elsif ( p_order_by = 2 ) then
open l_cursor for select * from t where ... order by c2;
......
C may or may not be choosen.
d) I don't see how D applies here. D is not one of my choices.
e) E could certainly apply!! You can dynamically construct a query and open it using a ref cursor.
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:1288401763279
So, I believe this test to be "not such a good one" if they picked the answers you say they did.
Reuse of cursors
Jenny, March 23, 2006 - 4:03 am UTC
Hello Tom,
I need to iterate over two ref cursor, one loop inside the other. My problem is that I don't know how to reuse the second cursor in PL/SQL. Right now I'm fetching it again, a very time consuming query. Can I repoint cur2 instead of closing it and reopening it=
Could you please help me?
--cur1 and cur2 are ref cursors
cur1 := getFirstCursor;
--iterate over first cursor
loop
fetch cur1 into var1;
exit when cur1%NOTFOUND;
--Always the same result from
--getSecondCursor for the same
--var1 data
cur2 := getSecondCursor (var1);
loop
--do stuff with data from cur1 and cur2
exit when cur2%NOTFOUND;
end loop;
close cur2;
end loop;
close cur1;
I've simplified the procedure, I hope I haven't left out something important.
March 23, 2006 - 10:50 am UTC
the second cursor seems to be parameterized by the output of the first (var1).
So, the second cursor is NOT being "reused", it likely returns different data each and every time!
But the answer here is clear - you didn't mean to use getFirstCursor or getSecondCursor
You meant to use "getMeTheBestCursor" which "just joins" - you would never do what you are doing - unless you work for a hardware manufacturer and wanted to sell lots more hardware.
You do NOT want to do the functional equivalent of:
for x in ( select * from t1 )
loop
for y in ( select * from t1 where t2.fkey = x.key )
loop
process...
You want to simply do:
for x in ( select * from t1, t2 where t1.key = t2.fkey )
loop
....
JUST JOIN.
reuse of cursor
Jenny, March 23, 2006 - 5:17 am UTC
An error in the above: getSecondCursor returns the same result independent of any data from cur1. Otherwise it wouldn't be possible to reuse cur2, of course.
Sorry about that
March 23, 2006 - 10:56 am UTC
just join.
just join.
just JOIN. it is what databases do BEST.
There are plsql tables (like arrays), but I would vote for just join first and foremost, you didn't mean to do two queries
ORA-00932: inconsistent datatypes: expected UDT got CHAR
Hitesh Bajaj, March 24, 2006 - 1:01 pm UTC
Hi Tom,
Please help me to resolve the error as I have been struggling to overcome this for quite some time.
Create or replace procedure AWR_REPORT AS
begin
declare
min_snap number;
next_snap number;
myval_sequence number;
Path VARCHAR2(100) ;
filename VARCHAR2(100) := 'Awr_report.txt';
report_filename VARCHAR2(100);
err_num NUMBER;
err_msg VARCHAR2(500);
Output_file utl_file.file_type;
p_dbid NUMBER;
p_instance_number NUMBER;
p_report_options NUMBER := 8;
awr_output AWRRPT_HTML_TYPE_TABLE;
begin
begin
Select value into path from v$parameter where name = 'utl_file_dir';
output_file := utl_file.fopen (path,filename, 'a');
snap_file := utl_file.fopen (path,snapfilename, 'a');
EXCEPTION
When NO_DATA_FOUND then
err_num := SQLCODE;
err_msg := Substr(SQLERRM,1,500);
if (err_msg = 'ORA-01403: no data found') then
err_msg := 'Grant Privilege on V$parameter missing to the Schema Owner';
end if;
utl_file.put_line (output_file,'Date : ' || to_Char(sysdate,'dd/mm/yyyy hh24:mi:ss') || ' Error generated while creating and opening a file with an error message ' || err_msg);
utl_file.fclose(output_file);
return;
end;
begin
for x in (
Select snap_id,dbid,instance_number from dba_hist_snapshot where snap_id >
(1) order by snap_id) loop
If min_snap is NULL then
min_snap := x.snap_id;
else
next_snap := x.snap_id;
end if;
p_dbid := x.dbid;
p_instance_number := x.instance_number;
if min_snap is NOT NULL AND next_snap is NOT NULL then
exit;
end if;
end loop;
If min_snap is NOT NULL AND next_snap is NOT NULL then
report_fileName := Path ||'/'||min_snap||'_'||next_snap||'.lst';
Select output into awr_output from table(dbms_workload_repository.awr_report_html(p_dbid,p_instance_number,min_snap,next_snap,p_report_options));
utl_file.put_line (output_file,'Date : ' || to_Char(sysdate,'dd/mm/yyyy hh24:mi:ss') || ' AWR Report for the Snapshots ' || min_snap || '-' || next_snap || ' has been published.');
utl_file.fclose(output_file);
return;
else
utl_file.put_line (output_file,'Date : ' || to_Char(sysdate,'dd/mm/yyyy hh24:mi:ss') || ' Could not get the required snapshots to build the Statspack/awr-report ');
utl_file.fclose(output_file);
return;
end if;
end;
end;
end;
/
ERROR at line 1:
ORA-06512: at line 1
ORA-00932: inconsistent datatypes: expected UDT got CHAR
ORA-06512: at "SCOTT.AWR_REPORT", line 56
ORA-06512: at line 1
I even checked with making the string "Select output..." a dynamic string and using execute immediate to execute it but the same error message appears.
Please help!
2) If the output gets generated as .html file it gets dumped in the $ORACLE_HOME/bin folder. Is there any way to get this file created an user defined destination folder.
Thanks in advance
Thanks
Hitesh Bajaj
March 24, 2006 - 4:09 pm UTC
well, I didn't even get THAT FAR. since this code doesn't have snap_file and snapfilename declared.
So, I declare them, figure out I need to grant on dbms_workload_repository and such (test cases = small, concise, yet 100% complete. Debugging is straight forward, the first goal is to whittle the problem down to the bare bones).
that table function is using a table of strings, meaning, each row it returns will return A STRING. You need to LOOP OVER it and retrieve STRINGS. It does NOT return a collection.
for x in (Select output from
table(dbms_workload_repository.awr_report_html(p_dbid,p_instance_number,min_snap,
next_snap,p_report_options)) )
loop
-- process here;
null;
end loop;
You will have the STRINGS x.output in the loop
It did work out.
Hitesh Bajaj, March 26, 2006 - 6:58 am UTC
Hi Tom,
Thanks a lot as it finally worked out based on your suggestions. Now the file AWR_REPORT_2309_2310.html is getting created at the desired location by executing the procedure:
Create or replace procedure AWR_REPORT AS
begin
declare
min_snap number;
next_snap number;
myval_sequence number;
Path VARCHAR2(100) ;
filename VARCHAR2(100) := 'Awr_report.txt';
Output_file utl_file.file_type;
report_filename VARCHAR2(100);
report_output utl_file.file_type;
err_num NUMBER;
err_msg VARCHAR2(500);
p_dbid NUMBER;
p_instance_number NUMBER;
p_report_options NUMBER := 8;
begin
begin
Select value into path from v$parameter where name = 'utl_file_dir';
output_file := utl_file.fopen (path,filename, 'a');
EXCEPTION
When NO_DATA_FOUND then
err_num := SQLCODE;
err_msg := Substr(SQLERRM,1,500);
if (err_msg = 'ORA-01403: no data found') then
err_msg := 'Grant Privilege on V$parameter missing to the Schema Owner';
end if;
utl_file.put_line (output_file,'Date : ' || to_Char(sysdate,'dd/mm/yyyy hh24:mi:ss') || ' Error generated while creating and opening a file with an error message ' || err_msg);
utl_file.fclose(output_file);
return;
end;
begin
for x in (
Select snap_id,dbid,instance_number from dba_hist_snapshot where snap_id >
(Select NVL(MAX(end_snap_id),0) from perf_log_analysis) order by snap_id) loop
If min_snap is NULL then
min_snap := x.snap_id;
else
next_snap := x.snap_id;
end if;
p_dbid := x.dbid;
p_instance_number := x.instance_number;
if min_snap is NOT NULL AND next_snap is NOT NULL then
exit;
end if;
end loop;
If min_snap is NOT NULL AND next_snap is NOT NULL then
Select monitoring_seq.nextval into myval_sequence from dual;
report_fileName := 'AWR_REPORT_' || min_snap||'_'||next_snap||'.html';
report_output := utl_file.fopen (path,report_filename,'a');
for y in (Select output from table(dbms_workload_repository.awr_report_html(p_dbid,p_instance_number,min_snap,next_snap,p_report_options))) loop
utl_file.put_line(report_output,y.output);
end loop;
Insert into perf_log_analysis Values (myval_sequence,min_snap,next_snap,sysdate,Path || '/' || report_fileName,'Y');
commit;
utl_file.fclose(report_output);
utl_file.put_line (output_file,'Date : ' || to_Char(sysdate,'dd/mm/yyyy hh24:mi:ss') || ' AWR Report for the Snapshots ' || min_snap || '-' || next_snap || ' has been published.');
utl_file.fclose(output_file);
return;
else
utl_file.put_line (output_file,'Date : ' || to_Char(sysdate,'dd/mm/yyyy hh24:mi:ss') || ' Could not get the required snapshots to build the Statspack/awr-report ');
utl_file.fclose(output_file);
return;
end if;
end;
end;
end;
/
Tom, please help me in getting this file e-mailed to me as an attachment or in the desired HTML format. Is the procedure mentioned at your site using </code>
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:1739411218448 <code>
will work, but I am confused as it is not taking any input as filename.
Please help!
March 26, 2006 - 8:32 am UTC
that shows exactly how to email html as the email body??? Not sure what more you would need.
Please help
Hitesh Bajaj, March 28, 2006 - 10:29 am UTC
Hi tom,
The procedure mentioed above creates a file whose extension is .html
This is the AWR report which normally gets generated when we choose the type of content to be HTML across two snapshots. Now this file is dumpled under the directory mentioned by UTL_FILE.
Now I want the contents of the file to be mailed as HTML body or could be as an attatchment.
I had looked at the procedure but am not sure where to pass the contents of the file, to the IN parameters.
Can you please help me a bit more, since I am new to PL/SQL stuff.
Thanks in advance!
March 28, 2006 - 4:11 pm UTC
don't write it to the file system - use the sending of html email as it was programmed and supplied for you
or, write it yourself.
How to use SYS_CONTEXT when you have a local variable besides procedure parameters?
Sinan Topuz, April 12, 2006 - 5:49 pm UTC
Tom,
I have the following sample tables and a procedure and I am trying to use BIND variables here.
create table dept( dept_code varchar2(10)
, dept_name varchar2(50) );
create table country (cnt_code varchar2(10)
,cnt_name varchar2(50) );
create table emp (emp_code varchar2(10)
,emp_name varchar2(30)
,status varchar2(1) default 'A'
,dept_code varchar2(10)
,cnt_code varchar2(10)
,mgr_code varchar2(10) );
CREATE OR REPLACE PROCEDURE emp_list
(pdept in varchar2 DEFAULT NULL
,pmngr in varchar2 DEFAULT NULL)
is
l_str varchar2(1000);
TYPE cv_typ IS REF CURSOR;
cv cv_typ;
BEGIN
FOR cr_country in (SELECT cnt_code
, cnt_name
FROM country )
LOOP
l_str := 'SELECT emp_code, emp_name FROM emp WHERE status = ''A'' ';
-- IF pdept IS NOT NULL THEN
l_str := l_str || ' AND dept_code = :b_dept ';
-- END IF;
-- IF pmngr IS NOT NULL THEN
l_str := l_str || ' AND mngr_code = :b_mngr ';
-- END IF;
l_str := l_str || ' AND cnt_code = :b_cnt ';
LOOP
OPEN cv FOR l_str
USING pdept, pmngr, cr_country.cnt_code ;
/* When I uncomment the lines things started to get interesting. Without trying to figure out the combinations of IF statements that I could use while opening the REF_CUROSRHHow would I determine what I will put after USING clause with SYS_CONTEXT?
There is a special case here (at least for me) that WHERE clause is also having a LOCAL variable "cr_country.cnt_code".
Is it possible to use bind variables here? I looked at the SYS_CONTEXT discussions, but I as far as I have read I could not
see any example including a local variable in the WHERE clause.
*/
EXIT WHEN cv%NOTFOUND;
-- will do something here
NULL;
END LOOP;
CLOSE cv;
END LOOP;
EXCEPTION WHEN OTHERS THEN
CLOSE cv;
END;
Thanks for your time and help in advance!
April 12, 2006 - 7:59 pm UTC
Thanks Tom. I tried the solution and it worked well.
Sinan Topuz, April 17, 2006 - 5:56 pm UTC
A reader, April 20, 2006 - 5:39 pm UTC
Tom,
Whats the difference between just spooling a file by doing a simple select x,y,z joining four tables and doing the same using a cursor, looping around and printing them into the file? I am sure the cursor is not necessary.
April 21, 2006 - 7:09 am UTC
not only not necessary, but a ton of code with lots of potential bugs.
that, and it'll run slower in all probability (than spool) unless you use a nice fast truly compiled language (like C) and good bulk fetch sizes.
but don't write your own:
</code>
http://asktom.oracle.com/~tkyte/flat/index.html <code>
query
raj, April 21, 2006 - 1:17 pm UTC
Tom,
How to find out " minimum value of column 1 for each value of column2 "?
I am scratching my head too...appreciate if you help me out
April 21, 2006 - 3:30 pm UTC
sounds like
select min(c1), c2 from t group by c2
doesn't it.
Ram, April 21, 2006 - 5:07 pm UTC
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:
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!
--------------------------------------
Hi Tom,
I agree with parsing over load. But in case where there are NO inserts
meaning simple select... with different where clause...may vary depending on input values
to stored proc. would it not be efficient to use "execute immediate".
thanks
Ram
Harrsiburg, PA
April 21, 2006 - 5:24 pm UTC
I don't care if you do insert, delete, update, select, merge - they are all JUST SQL
SQL is SQL
a hard parse is very very very bad
a soft parse on the other hand is just very very bad.
execute immediate isn't what you mean, I think you meant "open" to dynamically open a ref cursor
(you should have seen this coming since the entire example for the original question was about a SELECT!)
Conditional Cursors
Mark, April 25, 2006 - 10:42 am UTC
Hi Tom,
Oracle 8.1.7.4 STANDARD
What is the best way to do conditional cursors?
What I mean is, I have two cursors - a, b. I want to use an input or lookup value to decide which one I open.
I need the cursor naming and record var naming to be the same so the rest of the code works as written.
The difference between the two cursors is one column that is grouped upon.
Otherwise, the code runs exactly the same.
Sort of like this (but this does not work):
If v_choice = 'N' then
OPEN a(v_in);
Else
OPEN b(v_in);
End if;
loop
FETCH a into rec;
EXIT when a%notfound;
.
. DO WORK
.
END lOOP a;
CLOSE a;
This obviously does not work because my FETCH, EXIT, END LOOP and CLOSE reference 'a' cursor, and would fail if v_choice = 'Y', indicating to use 'b' cursor.
I'd really just like to swap out the cursors right at the beginning so the rest of the code flows.
Any ideas?
Trying to avoid dynamic SQL also.
Thanks in advance and truly a great resource here.
April 25, 2006 - 10:58 am UTC
read about ref cursors.
if ( condition )
then
open ref_cursor for select ...
else
open ref_cursor for select something else ...
end if;
loop
fetch ref_cursor into rec;
exit when ref_cursor%notfound;
....
Conditional Cursors
Mark, April 25, 2006 - 4:39 pm UTC
Perfect! Thanks!
Search on complex model
Faisal, May 03, 2006 - 5:54 am UTC
Hi Tom,
I have to provide a search capability to my users. The data model consist of 25 tables (Fully Normalized). In normal situation, we query from top-bottom for example, select parent and all its related child records. I have to develope a complex query mechanisum where user almost can search any information even, bottom-to-top. So far I have created views in order to denormailze the information. What should be my approch here?
May 03, 2006 - 7:07 am UTC
difference between dbms_application_info.set_client_info and dbms_session.set_context
A reader, May 08, 2006 - 1:39 pm UTC
Tom, can you give me basic difference between dbms_application_info.set_client_info and dbms_session.set_context? I was told both can be used for same purpose [passing parameters and bind them to query, dynamically for different sessions]
If so, why we need to use dbms_session.set_context over other [dbms_application..]
Thanks,
May 08, 2006 - 1:40 pm UTC
set client info may set one string into the column client_info in v$session.
that string is 64 characters long and there is ONE of them.
set context can set many more strings in a private namespace.
and the strings can be longer.
setting NULL in set_context
Rajeswari, May 24, 2006 - 9:15 am UTC
Tom,
I am using syscontext in the view. I used NVL to display all the records from the table - only for testing not in real. Used bold only to highlight.
Why it is not returning any records if I set NULL value to the context?
create table emp(empno number,empname varchar2(30), jdt date);
alter table emp add constraint emp_pk primary key(empno);
insert into emp values(1001,'User1',sysdate);
insert into emp values(1002,'User2',sysdate-45);
insert into emp values(1003,'User3',sysdate-145);
insert into emp values(1004,'User4',sysdate-345);
commit;
scott@ORA9.2> select * from emp;
EMPNO EMPNAME JDT
---------- ------------------------------ ---------
1001 User1 24-MAY-06
1002 User2 09-APR-06
1003 User3 30-DEC-05
1004 User4 13-JUN-05
create context empctx using emppkg;
create or replace package emppkg as
type refcur is ref cursor;
procedure emplist(emp_joining_dt in date, cemp out refcur);
end;
/
create or replace package body emppkg as
procedure emplist(emp_joining_dt date,cemp out refcur) as
begin
dbms_session.set_context('EMPCTX','JOINING_DT',emp_joining_dt);
if (sys_context('EMPCTX','JOINING_DT') IS NULL) THEN
dbms_output.put_line('Context is null');
END IF;
open cemp for select empno,empname,jdt from empview;
end emplist;
end;
/
create or replace view empview as
select empno,empname,jdt from emp
where jdt <= NVL(sys_context('EMPCTX','JOINING_DT'),jdt)
/
scott@ORA9.2> var ret refcursor
scott@ORA9.2> exec emppkg.emplist(sysdate,:ret);
PL/SQL procedure successfully completed.
scott@ORA9.2> print :ret
EMPNO EMPNAME JDT
---------- ------------------------------ ---------
1002 User2 09-APR-06
1003 User3 30-DEC-05
1004 User4 13-JUN-05
scott@ORA9.2> exec emppkg.emplist(null,:ret);
Context is null
EMPCTX.JOINING_DT =
PL/SQL procedure successfully completed.
scott@ORA9.2> print :ret
no rows selected
scott@ORA9.2> select empno,empname,jdt from emp
2 where jdt <= NVL(null,jdt);
EMPNO EMPNAME JDT
---------- ------------------------------ ---------
1001 User1 24-MAY-06
1002 User2 09-APR-06
1003 User3 30-DEC-05
1004 User4 13-JUN-05
May 25, 2006 - 6:57 am UTC
implicit conversion. did you note the care I took to avoid them in the example above??
nvl( STRING, DATE )
is really
nvl( STRING, to_char(DATE) );
and when you
where jdt <= nvl( STRING, to_char(DATE) )
it will really be:
where jdt <= to_date( nvl(STRING, to_char(DATE,'dd-mon-rr')), 'dd-mon-rr' )
assuming the default date mask.... hence, you are having an implicit conversion take place, stripping off the TIME component and none of the jdt's are as of midnight (unless you run the example at midnight), so none of them are less than or equal to midnight.
ops$tkyte@ORA10GR2> create or replace package body emppkg as
2 procedure emplist(emp_joining_dt date,cemp out refcur) as
3 begin
4 <b>dbms_session.set_context('EMPCTX','JOINING_DT',to_char(emp_joining_dt,'yyyymmddhh24miss'));
5</b>
6 if (sys_context('EMPCTX','JOINING_DT') IS NULL) THEN
7 dbms_output.put_line('Context is null');
8 END IF;
9
10 open cemp for select empno,empname,jdt from empview;
11
12 end emplist;
13 end;
14 /
Package body created.
ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> create or replace view empview as
2 select empno,empname,jdt ,
3 NVL(sys_context('EMPCTX','JOINING_DT'),jdt) sctx
4 from emp<b>
5 where jdt <= to_date( NVL(sys_context('EMPCTX','JOINING_DT'),
6 to_char(jdt,'yyyymmddhh24miss')), 'yyyymmddhh24miss' )</b>
7 /
View created.
Function Returning Multiple Values
Surendra, May 25, 2006 - 2:38 am UTC
Dear Tom,
Thanks for all the wonderful answers.
Consider folllowing situation where I want to return multiple values from a function.
CREATE OR REPLACE TYPE CALCType AS OBJECT
(
VAL1 BINARY_FLOAT
, VAL2 BINARY_FLOAT
, VAL3 BINARY_FLOAT
, VAL4 BINARY_FLOAT
, VAL5 BINARY_FLOAT
)
/
CREATE OR REPLACE FUNCTION CALC (
ID VARCHAR2
) RETURN CALCType
IS
L_VAL1 BINARY_FLOAT;
L_VAL2 BINARY_FLOAT;
L_VAL3 BINARY_FLOAT;
L_VAL4 BINARY_FLOAT;
L_VAL5 BINARY_FLOAT;
BEGIN
L_VAL1 := 111.56;
L_VAL2 := 222.56;
L_VAL3 := 333.56;
L_VAL4 := 444.56;
L_VAL5 := 555.56;
RETURN CALCType(L_VAL1, L_VAL2, L_VAL3, L_VAL4, L_VAL5);
END CALC;
/
I can receive individual values as
SELECT CALC('1').VAL1, CALC('1').VAL2,CALC('1').VAL3, CALC('1').VAL4, CALC('1').VAL5 FROM DUAL;
However it may execute the function those many times.
Is it any way where I execute the function only once and still get individual values?
The function is required to be called in a Query along with other columns.
OR any other way to achive this?
Regards,
May 25, 2006 - 1:40 pm UTC
....
16
17 dbms_application_info.set_client_info( userenv('client_info')+1 );
18 RETURN CALCType(L_VAL1, L_VAL2, L_VAL3, L_VAL4, L_VAL5);
19
20 END CALC;
21 /
Function created.
ops$tkyte@ORA10GR2> exec dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
ops$tkyte@ORA10GR2> select x.data.val1, x.data.val2, x.data.val3, x.data.val4, x.data.val5
2 from (select (select calc('1') from dual) data from dual) x
3 /
DATA.VAL1 DATA.VAL2 DATA.VAL3 DATA.VAL4 DATA.VAL5
---------- ---------- ---------- ---------- ----------
1.116E+002 2.226E+002 3.336E+002 4.446E+002 5.556E+002
ops$tkyte@ORA10GR2> select userenv('client_info') from dual;
USERENV('CLIENT_INFO')
----------------------------------------------------------------
1
ops$tkyte@ORA10GR2> SELECT CALC('1').VAL1, CALC('1').VAL2,CALC('1').VAL3, CALC('1').VAL4,
2 CALC('1').VAL5 FROM DUAL;
CALC('1').VAL1 CALC('1').VAL2 CALC('1').VAL3 CALC('1').VAL4 CALC('1').VAL5
-------------- -------------- -------------- -------------- --------------
1.116E+002 2.226E+002 3.336E+002 4.446E+002 5.556E+002
ops$tkyte@ORA10GR2> select userenv('client_info') from dual;
USERENV('CLIENT_INFO')
----------------------------------------------------------------
6
setting NULL in set_context
Rajeswari, May 30, 2006 - 4:48 am UTC
Thanks a lot Tom for reminding about implicit conversion. I seen your syscontext example with the explicit conversion for date but somehow it slipped from my mind when I tested with NULL value.
Thank you again for detail clarification.
PL?SQL STORED PROCEDURES
balu, June 03, 2006 - 2:27 am UTC
can any one please send me EXAMPLES OF "PL/SQL STORED PROCEDURES
,.....USING MULTIPLE CURSORS WITH PARAMETERS PASSING"
......................................................................................................................
and ......... EXAMPLES FOR PL/SQL STORED PROCEDURE
USING TWO CURSORS WITH ONE PARAMETER PASSING
June 03, 2006 - 8:43 am UTC
something wrong with your keyboard?
There is nothing special, magic, or unqiue about "using more than one cursor and passing parameters". In fact, that is very "basic"
create procedure p( p_x in number, p_y in number )
as
begin
for c1 in (select * from t where x = p_x)
loop
...
end loop;
for c2 in (select * from t where y = p_y)
loop
...
end loop;
end;
/
you can certainly take that and make it be "two cursors with one parameter" I hope..
Reference Cusor
Tim, June 12, 2006 - 12:58 pm UTC
Your example at the top of this thread was very helpful in allowing me to get this working. The Oracle docs are very detailed - but just were not pushing me in the right direction. Your example was just what I needed. Thanks.
The reason I want to use this technique is my client/server application uses a query window where the user can choose which fields to enter. Currently - this means the client application is dynamically creating the SQL statement and including the user selected parameters as strings - ie - no bind variables - each SQL issued (most likely) different from every other SQL. As an additional side benefit - it will also be great to get the SQL on the server rather than on the client - as our client application gets pushed out to thousands of users across the country - so when we have to "tweak" some SQL - the change is easy - but getting the change to the users is hard. When we get the SQL on the server - we will be able to push the change into production much easier.
Now, using the examples you have provided, I can (slowly) convert this over to putting the SQL in a packaged procedure (in an Oracle 9.2.0.4 server) using a reference cursor and making use of the SYS_CONTEXT technique to input the appropriate user chosen values. I would now expect a limited number of unique SQL statements. As you had pointed out - we will not get the mathematically maximum number of combinations/permutations (whatever) because users tend to have similar needs and in reality will be mostly choosing similar types of fields to query on.
Hence my question. We have done user activity monitoring and can show (using one particular query window as an example) that about 80% of the time - the users are actually performing two queries (distinct sets of criteria fields chosen). So, although the technique we can now use will support this - I am wondering about the "soft parse" aspect of the PLSQL executed dynamic query. I am not clear on "how bad" is the soft parse - which if I understand what you have written above - will occur for each invocation of the dynamic SQL - no caching possible - soft parse each time.
Would it be worth our while to code a conditional in the packaged procedure such that if we see the fields coming in which match one of the two queries (which our monitoring shows is 80% of the time) - we branch to logic which executes one of two non-dynamic sql using binds. The rest of the time - we go to the dynamic SQL using the sys_context - which would cover all of the other possibilities.
Or - at this point (where many queries are not using binds) - would I be going after a fine point and missing the larger point? ie - Should I at this time concentrate on converting the "old-style" queries which are dynamic client-based and not using binds - to using server-based packaged procedures using dynamic SQL with sys_context for variable substitution - and at this time not worry about the "soft parse". Then after all of this is converted over - then go back and pull the most common queries into separate conditional logic which would use non-dynamic SQL with binds? Or even at that point - would this still be too fine of a detail to bother with?
To add another wrinkle - we have two applications which are both using a single Oracle instance. I am working on one application which is an in-house client/server application - we have control over this and can (within reason) do what we want. The other application (PeopleSoft financials v8.8) is a 4-tier application which we are quite restricted in what can be done - both from a technical point of view and from a business decision point of view. Currently neither of these use binds (from what our DBA has told me - I can only verify the app I work on) for the query windows in which the user has a selection of fields from which to base their query on. So - by fixing the one application - how much will that really help us if the other application is still not using binds? Is that a case where we should perhaps consider fixing the application we can - and perhaps using "cursor_sharing = force" for the other application? Other application being n-tier uses connection pooling such that all connections come into the Oracle database under a single user name - maybe a system logon trigger looking for that particular user name would alter the session and force the cursors to match?
June 13, 2006 - 10:43 am UTC
if you are using a ref cursor - ref cursors are always parsed upon open - regardless of whether they are "static" or "dynamic"
the reason - you can call this procedure 50 times, get 50 cursors and have them all open simultaneously - they are all just "pointers to separate cursor work areas"
so, static or dynamic - a ref cursor is going to cause a parse.
things that I want to know about cursors, to write a better tuned code
Ravi, June 21, 2006 - 4:28 pm UTC
1)how does it keep track of which record to be shown next
2)does the query in the cursor gets executed for each fetched row
3)if it is executed once for each record that it returns
does query waits until it finds the
entire result set and returns the next record
of the previously returned record
4)how does it work when we use BILK COLLECT
can you please explain the above, this will help me a lot in writing a better code
June 22, 2006 - 11:44 am UTC
suggest you get access to "Effective Oracle by Design" if you can as I spent many many pages on stuff just like this in chapters on "effective sql" and "effective plsql"
it is bigger than a breadbox - a cursor is just a pointer to a result set, result sets are processed in MANY different ways.
utility of clause where 1 = 1
A reader, June 27, 2006 - 10:49 am UTC
Dear Tom,
I am sure that I have read in asktom site something about the utility or not in using where 1 = 1 in a query. Unfortunately I didn't find it.
Could you please let me know in few words if it is recommended to add a where clause like
where 1 = 1.
Franckly speaking, I see no interests.
Thanks in advance
Line Number
Mark, July 05, 2006 - 10:47 am UTC
Hi Tom,
Does oracle expose to the environment what line number it is executing in pl/sql code?
I'd like to use it in debugging with dbms_application_info calls...otherwise, I have to explicitly enter the number.
Regards,
Mark
July 08, 2006 - 8:00 am UTC
Re: Above (Mark)
Matthew Lange, July 06, 2006 - 3:41 pm UTC
Hi Mark,
IIRC, you can find what query is being run, but after that I'm not sure.
You can check certain oracle views to determine the hash of the statement being run, then you can join that with another view to determine which statement is being run.
I believe I read that on oracle.com.
FOR x IN dynamic sql
Branka, July 18, 2006 - 5:13 pm UTC
Can I pass dinamyc select into statement
FOR x IN (dynamic_select)
LOOP
BEGIN
SELECT APPLICATION_SERIAL_NUMBER into v_app
FROM APPLICATION
WHERE rowid = x.rid
FOR UPDATE nowait;
exit;
exception
when RESOUCE_BUSY then
null;
end;
end loop;
July 19, 2006 - 8:57 am UTC
no, dynamic sql mandates the use of explicit cursors via "open", "fetch" and "close"
FOR x IN dynamic sql
Branka, July 20, 2006 - 7:31 am UTC
Thank you.
Dynamic SQL for Workflow processing
partha, July 20, 2006 - 6:16 pm UTC
Background:
The application allows for the user to define the processing / triggering of activities. Let's say for example the dept and emp and loc tables. The screen looks like:
Columns: XXXXXXXXX Condition: XXXXX Value: XXXXXXXXXX
Action: XXXXXXXXXXXXXXXXXXXXXXX Action Value: XXXXXXXXX
Columns will have all the columns from dept and emp
Conditions is a select list, "=", "<>", "LIKE" etc.
Value: is either from a select for = and <> and a free text for LIKE
Action: is from a select list like, "Change Department to", "Change Manager" etc, and Action Value could be either from a select list or a free text.
This will allow the user to put in any kind of processing.
Current Solution:
For the conditions processing, I store the values for the columns || conditions || value and stored it in a table called "where condition", had a function which used a dynamic sql and concatenated the "where condition" and return a value of true or false, based on that executed one more dynamic sql which did the "action processing" which is equivalent to some update statements.
Issue:
This leads to the issue of not using bind variables and sql injection. because the condition processing was a simple concatenation. "select 1 from emp a, dept b where a.deptno = b.deptno" || where_condition_from_table ;
Proposed Alternate:
To use sys_context and set the values as mentioned in the example at the beginning of the page, while storing the condition processing and store that as the where_condition in the table and then concatenate the where condition in the dynamic sql. For example, If the condition selected is:
Columns: "DEPTNO" condition: "=" value: "10"
Then create the where condition as
if mv_columns="DEPTNO" then
mv_where := mv_where || ' and deptno ' || condition_from_table || '
sys_context( ''MY_CTX'', '' || value_from_table
|| ''' ) ';
elsif mv_columns="DNAME" then
mv_where := mv_where || ' and dname = ' || condition_from_table || '
sys_context( ''MY_CTX'', '' || value_from_table
|| ''' ) ';
end if ;
Question:
(a) Is this approach correct
(b) Will this eliminate the possibility of sql injection and use bind variables
(c) Is there an alternate way to do the above ?
ps:
Not sure if this belongs to a separate question or is part of a followup / clarification, if it needs to be asked when you are taking up new questions, ignore it and I will raise it as a separate question later. Thanks
July 22, 2006 - 5:30 pm UTC
condition_from _table is subject to sql injection still - ANYTIME you concatenate in user supplied data and compile it - you are subject to that.
have you considered using the workflow package that comes with the database? I mean - you already "own" workflow? it comes with...
Partha, July 22, 2006 - 11:51 pm UTC
Sorry, should have been more clear. The value_from_table and condition_from_table is basically my code which is calculated based conditions selected through a select list.
In my user screen I have: Select condition: <Is / Is Not / Begins with / Does not Begin with>
case (condition)
when 'Is' then
condition_into_table := ' = ' ;
when 'Is Not' then
condition_into_table := ' <> ' ;
when 'Begins with' then
condition_into_table := ' LIKE '''%' || p_value_accepted || '%' || '''' ;
....
end case ;
I guess you mean that, p_value_accepted can be subject to sql injection? Will this still be a problem if it is wrapped through the sys_context? Is their any conventional solution for this issue. We are currently working on this and is in the design stage and haven't explored workflow yet. Since this is a small module and was required fast, not sure if we will go to workflow immediately but I'll have a look. Would appreciate if you can let me know any other way. Thanks
July 23, 2006 - 8:57 am UTC
anything the end user gives you that you DO NOT BIND is subject to sql injection.
That sort of defines the problem space, look as what you are doing and ask yourself "am I binding the user input or am I concatenating the user input into the string"
If you are concatenating, you have sql injection worries.
sys_context is a method of binding, if you pop the user input into sys_context it is "bound"
"was required fast" - laughing out loud. You decided writing, debugging and maintaining code was going to be "fast".