Home>Question Details



Bhavani -- Thanks for the question regarding "Bind Variables", version 8.1.6

Submitted on 13-Feb-2003 9:28 Central time zone
Last updated 29-Oct-2008 13:37

You Asked

Hi Tom,

I tried to use Bind Variables in my Script.  But that script is taking more time than 
expected.  Please have a look at the below script.


area@DBWH> truncate table t1;

Table truncated.

Elapsed: 00:00:00.50
area@DBWH> declare
  2  begin
  3   for i in 1..1000000
  4   loop
  5    insert into t1 values(i);
  6   end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

Elapsed: 00:10:625.30
area@DBWH> truncate table t1;

Table truncated.

Elapsed: 00:00:07.70
area@DBWH> declare
  2  
  3  begin
  4   for i in 1..1000000
  5   loop
  6    execute immediate 'insert into t1 values(:x)' using i;
  7   end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

Elapsed: 00:12:731.50
area@DBWH> 

If I use Bind variables it is taking, 00:12:731.50 whereas without bind variables it is 
taking 00:10:625.30.

Can you please explain where I am wrong?  Thanks for your time.

Bhavani 

and we said...

hey -- it is a TRICK question.

why?  Because BOTH are using bind variables.  What you have just proven is that static 
SQL in plsql is much much more efficient then using dynamic sql to do the SAME!!!

The beauty, they sheer beauty of PLSQL is that the things you have to be concerned with 
in other languages like

o parse once -- execute many
o use bind variables

is done AUTOMAGICALLY and TRANSPARENTLY.  let me demonstrate with your example using 
sql_trace and tkprof.  I run:



ops$tkyte@ORA920> create table t ( x int );

Table created.

ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> alter session set sql_trace=true;

Session altered.

Elapsed: 00:00:00.06
ops$tkyte@ORA920> begin
  2          for i in 1 .. 100000
  3          loop
  4                  insert /* STATIC SQL */ into t values ( i );
  5          end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:13.52
ops$tkyte@ORA920> begin
  2          for i in 1 .. 100000
  3          loop
  4                  execute immediate
  5                  'insert /* DYNAMIC SQL */ into t values ( :i )'
  6                   using i;
  7          end loop;
  8  end;
  9  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:37.84

now that is interesting but TKPROF is fascinating:

INSERT /* STATIC SQL */ into t values ( :b1 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          1          0           0
Execute 100000      6.56       7.12          0        260     103411      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   100001      6.57       7.12          0        261     103411      100000
********************************************************************************

not that our query was rewritten.  ALL PLSQL VARIABLES IN SQL ARE BINDS!!!  Look at the 
parse to execute numbers there -- parse once, execute many, use binds.  All done, all 
magic, all transparent.

Looking at dynamic sql:


insert /* DYNAMIC SQL */ into t values ( :i )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse   100000      4.56       4.40          0          0          0           0
Execute 100000     10.97      11.04          0     100168     302764      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200000     15.53      15.44          0     100168     302764      100000

look at that -- terrible.  parse counts like crazy.  way over parsed.  at least we 
bound -- but the price we paid for parsing is extremely high.  They were soft parses but 
a parse is a parse and something to be avoided when possible.



 

Reviews    
5 stars tkprof   February 13, 2003 - 10am Central time zone
Reviewer: mo 
Tom:

I replicated your examples and set sql_trace=true but I do not see the tkprof output as you show. 
Any ideas why?

Thanks 


Followup   February 13, 2003 - 10am Central time zone:

umm, did you run tkprof -- the command line tool that turns the trace file that was generated in 
the user dump destination on the server into a readable file?


see chapter 10 of "the book".  I go into how to use tkprof in depth. 

4 stars For Bhavani   February 13, 2003 - 10am Central time zone
Reviewer: Paul from UK
If you really want to see the effect of not using bind variables use this:

begin
 for i in 1..1000000 loop
  execute immediate 'insert into t1 values('||i||')';
  end loop;
 end;
/

Unfortunately, I don't have the time (unlike mo, apparently ;-)) 


2 stars Bind Variable Peeking feature   May 21, 2003 - 8am Central time zone
Reviewer: Vikas from INDIA
Hi Tom,

Can you please explain me the concept of Peeking of User-Defined Bind Variables which is a new 
feature for 9.0.1

It would be nice if you can come up with a demo example.

Thanks
Vikas 
 


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

basically -- it means when the query is first hard parsed, the optimizer will peek at the binds 
in order to determine how to optimize the query.  Stress the word hard there, it is not every 
parse, only on the hard parse.

So, setup a test like this:

drop table t;

create table t
as
select 1 id, a.* from all_objects a;

update t set id = 99 where rownum = 1;

create index t_idx on t(id);

analyze table t compute statistics
for table
for all indexes
for all indexed columns size 2;

massively skewed data.  index on t(id) should be used if id is NOT 1... So, we run these queries 
against this data.  Note that we run 2 queries -- and each one twice.  the one tagged N_WAS_1 is 
hard parsed with the bind set to the value of 1.  the one taggedt with N_WAS_99 is hard parsed with 
the bind set to the value of 99

variable n number;

exec :n := 1;

alter session set sql_trace=true;
set termout off

select * from t n_was_1 where id = :n;

exec :n := 99;

select * from t n_was_99 where id = :n;


and now we execute (using soft parses) the SAME queries -- but with the inputs flipped flopped:

select * from t n_was_1 where id = :n;

exec :n := 1;

select * from t n_was_99 where id = :n;

exit


Now, I ran tkprof with AGGREGATE=NO so we would see each of the 2 queries 2 times:

select * from t n_was_1 where id = :n

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     2004      0.41       0.39        124       2404          0       30045
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2006      0.41       0.39        124       2404          0       30045

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 126

Rows     Row Source Operation
-------  ---------------------------------------------------
  30045  TABLE ACCESS FULL T (cr=2404 r=124 w=0 time=174004 us)

That is what you expect when the bind was set to 1 during the hard parse.  A full tablescan.  On 
the other hand:

********************************************************************************
select * from t n_was_99 where id = :n

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          3          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 126

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T (cr=3 r=0 w=0 time=116 us)
      1   INDEX RANGE SCAN T_IDX (cr=2 r=0 w=0 time=73 us)(object id 35628)

that is the plan you expect if the bind is 99 -- index range scan.  But watch out:

********************************************************************************
select * from t n_was_1 where id = :n

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.01        124        428          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01        124        428          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 126

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL T (cr=428 r=124 w=0 time=19046 us)

here the bind variable is in fact 99 if you remember from the test case -- we can see that by the 
very number of rows.  The PLAN WAS FIXED DURING THE HARD PARSE BASED ON THE INITIAL INPUTS

********************************************************************************
select * from t n_was_99 where id = :n

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     2004      0.60       0.58        206       4445          0       30045
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2006      0.60       0.58        206       4445          0       30045

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 126

Rows     Row Source Operation
-------  ---------------------------------------------------
  30045  TABLE ACCESS BY INDEX ROWID OBJ#(35627) (cr=4445 r=206 w=0 time=338282 us)
  30045   INDEX RANGE SCAN OBJ#(35628) (cr=2059 r=0 w=0 time=154266 us)(object id 35628)

same here -- now the index range scan isn't appropriate


So where does this come into play, when is this a "good thing" (this example shows it can be a 
confusing thing.  Just think, you start up on monday morning and the first query you submit uses a 
bind of :N := 1, full scan plan that week.  You start up next monday and the first query you submit 
using a bind of :N := 99 -- index range scan plan that week).

Well, suppose you use LIKE lots -- this could be very beneficial.  Your application always uses 
LIKE without leading '%' -- meaning an index would be helpful generally.  Here, the optimizer will 
parse


select * from t where t like :x

as if :x had a value, was a literal.  That would lead it towards the index.  Consider:

create table t
as
select a.* from all_objects a;
create index t_idx on t(object_name);

analyze table t compute statistics
for table for all indexes for all indexed columns
/

variable x varchar2(50)
@trace
set termout off
exec :x := '%';
select * from t x_was_percent where object_name like :x;
exec :x := 'Y%';
select * from t x_was_NOT_PCT where object_name like :x;
exit


tkprof shows us this time:

select *
from
 t x_was_percent where object_name like :x


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     2005      0.41       0.41        182       2391          0       30046
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2007      0.41       0.41        182       2391          0       30046

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 126

Rows     Row Source Operation
-------  ---------------------------------------------------
  30046  TABLE ACCESS FULL T (cr=2391 r=182 w=0 time=189486 us)


select *
from
 t x_was_NOT_PCT where object_name like :x


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        1      0.00       0.00          0          2          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          2          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 126

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS BY INDEX ROWID OBJ#(35635) (cr=2 r=0 w=0 time=70 us)
      0   INDEX RANGE SCAN OBJ#(35636) (cr=2 r=0 w=0 time=64 us)(object id 35636)


but since you never parse with :x := '%' and the index should be used here, this bind variable 
"peeking" works to your advantage
 

4 stars   May 21, 2003 - 10am Central time zone
Reviewer: Sagi from India
Hi Tom,

In the demo for the reply to Bhavani's question, you said

"They were soft parses but a parse is a parse and something to be avoided when possible."

So the parses in the output of tkprof is soft parses? If yes then how do we see hard parses out of 
the tkprof. 

It would be great, if you could give a demo with tkprof output that explains soft and hard parses.

Regards,
Sagi.

 


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

I covered tkprof is great detail in my book "Expert one on one Oracle" but in a tkprof -- it is 
trivial to see the hard/soft parses.

you will see a parse count (soft+hard)

you will see a "misses in library cache" and this will generally be 0 or 1.  If it is 0, all of the 
parses were soft.  If it is not zero, that is the number of hard parses it underwent. 

4 stars "Bind Variables", version 8.1.6   May 21, 2003 - 10am Central time zone
Reviewer: Raj 
Absolutely proven! 


4 stars   May 21, 2003 - 11am Central time zone
Reviewer: Sagi from India
Thanx tom.

As usual you are very great.

Regards,
Sagi. 


5 stars How come my explain plans are different ?   October 16, 2003 - 11pm Central time zone
Reviewer: Ma$e 
Hi Tom:

I have a few Questions w.r.t to the explain plans generated by the tkprof.

How come your sample displays more info in the row source section than what I get ? And what do 
those numbers mean ?

Also: How come I don't see the "row source" section for the last sql statement ?

What is the difference between the explain plans in the  "row source operation"  and the "explain 
plan section".
Which one should we use to determine the explain plan used at run-time.


I'm using V 9.2.0.1.0 EE


Here are my results

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)
********************************************************************************

select * 
from
 t n_was_1 where id = :n


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     2065      0.25       0.55        180       2471          0       30956
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2067      0.25       0.55        180       2471          0       30956

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)

Rows     Row Source Operation
-------  ---------------------------------------------------
  30956  TABLE ACCESS FULL T 


Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
  30956   TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T'

********************************************************************************
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)
********************************************************************************

select * 
from
 t n_was_99 where id = :n


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          3          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T 
      1   INDEX RANGE SCAN T_IDX (object id 34487)


Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T'

********************************************************************************

select * 
from
 t n_was_1 where id = :n


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.17        163        440          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.17        163        440          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL T 


Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T'

********************************************************************************
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)
********************************************************************************

select * 
from
 t n_was_99 where id = :n


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     2065      0.36       3.05        179       4593          0       30956
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2067      0.36       3.05        179       4593          0       30956

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 82  (SCOTT)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T'




********************************************************************************


Thanks
Ma$e

 


Followup   October 17, 2003 - 9am Central time zone:

9202 added:

INDEX RANGE SCAN OBJ#(35636) (cr=2 r=0 w=0 time=64 us)

which shows the consistent reads (2), physical reads (0), physical writes (0) and time (65) 
millionths of a second.  so, in 9202, you'll start seeing this additional, useful data.


As for not seeing it for the last statement -- the STAT records (with the rowsources) are only put 
into the trace file when the cursors are closed -- so you must not have exited sqlplus or whatever 
tool you were using, the last cursor, still open.


I would suggest NOT USING explain=u/p with tkprof.  It

a) makes it run lots slower
b) shows the plan that would be used in the environment that tkprof had when it connected


that last point means that if the application had some some alter session or used a different 
schema, or if some other index existed at runtime or if stats had been gathered -- whatever, the 
EXPLAIN PLAN will be not what was used -- but rather what would be used right now, in that session. 


just don't use explain= anymore. 

5 stars Excellent...I guess a good excuse to upgrade the test server   October 17, 2003 - 12pm Central time zone
Reviewer: Ma$e 
Hi Tom:

Just curious. Is this because the tkprof exposes this additional information in the report or is it 
now being collected by the trace utility in 9.2.2 ?

Thanks for your response.

Ma$e 


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

tkprof is just reporting whats in the trace file:

STAT #1 id=1 cnt=1 pid=0 pos=1 obj=222 op='TABLE ACCESS FULL OBJ#(222) (cr=3 r=0 w=0 time=103 us)'




thats the stat record from a 9203 trace -- the cr/r/w/time wont be there before. 

5 stars Binds not good for predicates on columns with skewed data   October 17, 2003 - 1pm Central time zone
Reviewer: Ma$e 
Hi Tom:

Thanks..

Ma$e 


Followup   October 17, 2003 - 2pm Central time zone:

let me phrase that differently.


binds -- mandatory on systems that parse many queries/second (transactional, no full scans going on 
there)

binds -- not mandatory on systems that have many seconds between parses (data warehouses, queries 
run for minutes or hours)

forget skewed for a moment -- the type of system will dictate it.  why?  you will either ALWAYS use 
an index or NEVER use an index in the system that parses queries/second (you are transactional 
there, everything should be short). 

5 stars Following up on the original answer   October 17, 2003 - 2pm Central time zone
Reviewer: Meyer from Baltimore, MD
Think about a standard lookup on table with a 
fname varchar2(50) column.

IN pl/sql, when you need to use a parameter passed into the procedure with wildcards "%" for starts 
with, ends with, or contains search against the varchar2 column.

1)Does the wildcard affect parsing and if yes how, do you ever consider instr? What is your 
preferred method to code this basic thing.

2)Do the benefits of not parsing change w/ html-db regions w (:F4950_P9_DISPLAYID) vs pl/sql 
packages and standard local variables. In html-db I am talking about a sql-query regions or 
pl/sql-returning-sql-query regions.

3)In your books in pl/sql you sometimes use format ":name" with "using"...is this for example or 
are do you get a benefit that you would not get using a standard local pl/sql variable that turns 
into a bind? 

Thanks,
Meyer 


Followup   October 17, 2003 - 2pm Central time zone:

1) a %...% search on a varchar2 field with like is pretty much going to be "full scan city"

instr = full scan city as well.

Now, if it was a sentence in a varchar2 field and you were looking for a WORD in the sentence -- I 
would 

create index t_idx on t(fname) indextype is ctxsys.context;

a Text index.  then I can "where contains( fname, l_word ) > 0" rather then where fname like 
l_word;

2) when you have SQL in PLSQL -- all plsql variables ARE bind variables.  It is the same either way 
-- both are bound into the statement.

3) when doing dynamic sql:

   execute immediate 'update t set c = :name' using l_variable;

it is the only way to use binds with dynamic sql.  I only "sometimes" use that construct when doing 
dynamic sql and I want to bind. 

5 stars Thank You   October 17, 2003 - 2pm Central time zone
Reviewer: Meyer from Baltimore, MD


4 stars Why was peeking introduced in the first place   October 29, 2003 - 12am Central time zone
Reviewer: Bala from Kuala Lumpur
Tom,
I don't really understand how Peeking really helps queries chose the best plan? as you have 
demonstrated above it could work the other way round many times.
When we really need to run a query that needs to use histograms(typical dw queries) we could have 
used hard variables(where execution time is many many times the parsing or optimization time). I 
still can not make out how it helps. And one last thing, how can we switch off peeking in 9i? 


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

in a DW, you would generally not use binds since you run few queries that take long times.

in OLTP, you would always use binds since you run tons of queries, all that should complete very 
quickly, tons of times.


bind variable peeking is for OLTP, not for DW.  You cannot switch it off -- you do not want to 
switch it off (to what end would switching it off be?).  For the environment it was designed for, 
it does its job.  the concept is -- you hard parse a query for the first time with some binds.  in 
OLTP those binds are representative for the system - that query should (or should not) use index 
"X".  This will make it so.  That query will use index "X" all of the time.  Elsewhere in your 
system is a similar query -- it is hard parsed for the first time with its representative inputs 
and could come to a different (yet correct) query plan since the inputs are "different" 

5 stars Confused with your response.   November 1, 2003 - 11pm Central time zone
Reviewer: Ma$e 
Hi Tom:

Now I'm really confused.

In your response:

"...you start up on monday morning and the first query you submit uses a bind of :N := 1, full scan 
plan that week. 
 You start up next monday and the first query you submit using a bind of :N := 99 -- index range 
scan plan that week)."

This could spell disaster for our OLTP application where we have used bind vars and the data is 
skewed and if the first SQL that executes is resolved to using a plan that does not bode well with 
the next execution of the same query with different values for the bind vars.

Later on you comment:
"binds -- mandatory on systems that parse many queries/second (transactional, no full scans going 
on there)

binds -- not mandatory on systems that have many seconds between parses (data warehouses, queries 
run for minutes or hours)

forget skewed for a moment -- the type of system will dictate it.  why?  you will either ALWAYS use 
an index or NEVER use an index in the system that parses queries/second (you are transactional 
there, everything should be short)."

I'm having problem digesting the last part of the above response "ALWAYS .... or NEVER". But in 
your example of bind var peeking you proved that you want to encourage use indexes sometimes and 
fts other times.

Can you please clarify my confusion or lack of understanding.

Thanks
Ma$e 


Followup   November 2, 2003 - 10am Central time zone:

well, it won't in reality -- will it.

You see -- your OLTP application, it will NEVER NEVER use ":n = 1" will it?  (if it did, it would 
be the kiss of death for it would it not?)

You see -- your application is going to run a query at "point X in the code".  THAT query should 
either

a) use an index or
b) not use an index

each and every time at that point in the code.  Bind variable peeking is the tool that permits 
this. Without bind variable peeking the query would always either full scan or index range scan 
regardless of where in the code it was!  With bind variable peeking -- the query will be able to 
use one or the other plan -- depending on WHO (what part of the code) is executing it.

This is really what you want.  Really -- especially -- ESPECIALLY in oltp!  Without bind variable 
peeking, skewed data would almost certainly imply "full scan" as the number of distinct values 
would be insufifcient for the optimizer to range scan.  Only by looking at representative inputs 
for your query -- based on where in the code it executes -- will you get the right answer!




That is what I meant by that always or never.  If you have a piece of code in your OLTP system that 
one time the query is executed uses an index range scan (few rows) and then later would want to 
full scan (tons of rows) -- you have totally unpredicable response patterns/usage there.  Some 
users get immediate responses -- others get responsed in minutes.  Something really wrong there -- 
therefore it would either ALWAYS range scan there (interactive) or ALWAYS full scan (batch report). 

4 stars Beginning to make some sense....   November 3, 2003 - 3am Central time zone
Reviewer: Ma$e 
Thanks Tom.

It seems I am having a total eclipse in comprehension.


"each and every time at that point in the code.  Bind variable peeking is the 
tool that permits this. Without bind variable peeking the query would always 
either full scan or index range scan regardless of where in the code it was!"

But I thought that the peeking is only performed when the query is hard parsed. I think we agree on 
that.

Also: Since the app will always generate the same sql syntax at the same point in the code 
(irrespective of who ran the code and how it got there) except for the value of the bind, the 
peeking will only be done the first time (at which point the plan will be generated) and not 
subsequently.

You are not implying that instead of using bind in my code, we use "cursor_sharing=force" ? I don't 
think so.

I'm in synch with everything you said in the last response (I think).

Thanks for your patience and dedication.

If I get over this issue, I'll owe you a drink/dinner at the next OOW.

Ma$e

 


Followup   November 3, 2003 - 7am Central time zone:


what I'm saying is -- ok, you have a piece of code.  It does a poplist, populates some list box.  

that piece of code will either ALWAYS index range scan (most likely) or NEVER.  The design of your 
interactive OLTP application is such that it is ALWAYS going to use inputs that permit an efficient 
index range scan -- or, it is not.  It is the very nature of your application here. 

5 stars bind variable   December 6, 2003 - 7pm Central time zone
Reviewer: Dillip K. Praharaj from CA USA
Oracle uses the literal value for hard parsing a SQL because of _optim_ppek_user_binds set to true 
in Oracle 9i.

So it means that the plan could be different than what we see in the explain plan, how can I make 
sure that the plan it show by explain plan is the one it has hard parsed ?

What I do is replace the binds with the literals for generating the plan.

Tom, any other easy way to do this ?
 


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

explain plan is a guestimate.  I use sql_trace + tkprof -- it shows reality.  there are many times 
that explain plan will be "tricked".


ops$tkyte@ORA920> create table t as select 99999 x, a.* from all_objects a;
 
Table created.
 
ops$tkyte@ORA920> update t set x = 1 where rownum = 1;
 
1 row updated.
 
ops$tkyte@ORA920> create index t_idx on t(x);
 
Index created.
 
ops$tkyte@ORA920> exec dbms_stats.gather_table_stats( user, 'T', method_opt=>'for all indexed 
columns',cascade=>true);
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA920>
ops$tkyte@ORA920> variable x number
ops$tkyte@ORA920> exec :x := 1
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t where x = :x;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=54 Card=15851 Bytes=1585100)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=54 Card=15851 Bytes=1585100)
 
 
 
ops$tkyte@ORA920> set autotrace off
ops$tkyte@ORA920>
ops$tkyte@ORA920> alter session set sql_trace=true;
 
Session altered.
 
ops$tkyte@ORA920> declare
  2          l_x number := 1;
  3          cursor c is select * from t where x = l_x;
  4  begin
  5          open c;
  6          close c;
  7  end;
  8  /
 
PL/SQL procedure successfully completed.
 
and tkprof says....

                                                                                                    
           
SELECT *
from
 t where x = :b1
                                                                                                    
           
                                                                                                    
           
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        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           0
                                                                                                    
           
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 549     (recursive depth: 1)
                                                                                                    
           
Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS BY INDEX ROWID T (cr=0 r=0 w=0 time=0 us)
      0   INDEX RANGE SCAN T_IDX (cr=0 r=0 w=0 time=0 us)(object id 58964)
 

3 stars Thank you   December 7, 2003 - 1pm Central time zone
Reviewer: wonder from China
Thank you , Tom 


4 stars Bind variable   December 12, 2003 - 8pm Central time zone
Reviewer: Dillip K. Praharaj from CA USA
Thanks Tom. 

How about generating the plan from the v$SQL_PLAN ?  

 


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

yes, that is another option -- same PLSQL block needed (to open/close cursor to get the plan 
generated)

if you have my new book "Effective Oracle By Design", i demonstrate in there how to use dbms_xplan 
easily to get a nicely formatted query plan out of there by creating a view on v$sql_plan that 
makes it look just like a plan table with a statement id.... 

5 stars how to reset an execution plan   January 31, 2004 - 9am Central time zone
Reviewer: A reader 
Hi

Recently we had some problem with our database, basically it was because one query used a very bad 
plan because of bind peeking, I suspect when it was first hard parsed it deduced an adequate plan 
for that moment but since it's cached afterwards all the queries afterwards were using that plan (a 
partition full scan). Normally this query should do index range scan. FYI we have cursor_sharing = 
FORCE

I realized that something had to be done so I flushed the shared pool and performance came back as 
good as before.

My question is, is there a way to flush a query execution plan instead of flushing the whole shared 
pool? 


Followup   January 31, 2004 - 10am Central time zone:

in your case - sounds like you don't want binds on this query -- sounds like you want a plan per 
query here.

but, you've got a system where the developers bound NOTHING, so you are using cursor_sharing=force 
as a crutch.

sigh.

perhaps you want to alter your session and set cursor sharing=exact before doing this particular 
query -- or, if you can -- use the cursor sharing hint to go exact for this query.

 

4 stars Ur Admirer   February 18, 2004 - 2am Central time zone
Reviewer: Sagi 
In  reply you said 

INSERT /* STATIC SQL */ into t values ( :b1 )

Parses once and executes 100000 

Whereas when using EXECUTE IMMEDIATE
 
insert /* DYNAMIC SQL */ into t values ( :i )

Its Parsed 100000 and Exuted 100000  times. But i am not able to understand why so many soft parses 
too.

If you see the statement both look the same except the text /* changes here */ and both are using 
bind variable.

I would appreciate your explanation.

Regards,
Sagi 


Followup   February 18, 2004 - 8pm Central time zone:

if you use

alter session set sql_trace=true;

begin
   for i in 1 .. 10000
   loop
      insert /* static */ into t values ( i );
      execute immediate 'insert /* dynamic */ into t values ( :x )' using i;
   end loop;
end;
/

you will see what I mean.  static sql in plsql is "cached for you as part of the language".  

dynamic sql -- cannot be.  if you use NDS (native dynamic sql) as above -- it will 

o open
o parse
o bind
o execute
o close

each time.  with dbms_sql, you can move the open/parse to outside the loop, bind/execute inside the 
loop and close after...
 

4 stars   February 19, 2004 - 3am Central time zone
Reviewer: A reader 
Thanx.

Ur awesome.

Regards, 


5 stars Bind in plsql   May 31, 2004 - 4am Central time zone
Reviewer: Raaghid from India
We have 2 production server, (1 is 8.1.5 and other is 9.2)

After reading your book, I am very particular in checking whether all the packages using bind 
variable or not.

We found that, the packages are not using the bind variable (None of the packages. Oh my God). 
In one of the package (Developed in Power builder), it has been disabled using the command 
available in the GUI. 
(Power builder, initial setting disable bind=1... intentionally done...)
Hence now we have created the BIG plan (like a new project), for chaning to bind (by changing the 
logic)

Thank you very much for your advise regarding bind, which helped immensely.

But at the same time, I find that, in all the PL/SQL procedures/triggers used for those packages, 
are
ALWAYS WITH BIND. (tested via sql$area)

Example:
--------
INSERT INTO REPLICATE_PRODUCT_TRAN(DOC_NO,DOC_REF_NO) VALUES (:b1 ,:b2);


So I came to the conculsion that, PL/SQL procedure always uses BIND variable by default. 
(irrespecive of how programmers writes coding, except when he hardcode with value)

IS MY CONCLUSIION TRUE. please confirm and explain.



 


Followup   May 31, 2004 - 1pm Central time zone:

that is one of the the features that makes PLSQL superior as a database development language over 
any other (just one, but a big one)

Unlike most other languages -- you have to go way out of your way to NOT use binds inside of plsql 
(you still have to get the coders to bind to their plsql calls but at least once you are in there, 
everything is automagically bound for you) 

5 stars bind in pl sql (contd)   May 31, 2004 - 6am Central time zone
Reviewer: Raaghid from India
Other one is running in oracle 9.0.1.1.1 (I mentioned wrongly above) 


5 stars Your Feedback to Bala   June 3, 2004 - 4pm Central time zone
Reviewer: David 
I've been trying to be careful to include bind variable in my dynamic sql. Your comments regarding 
NOT using bind variables with a decision support systems makes sense and is very helpful . . . Is 
it actually detrimental to use bind variables with a data warehouse system. . . will the absence of 
values be detrimental to the optimizer collecting column statistics. 


Followup   June 3, 2004 - 7pm Central time zone:

it can be -- in a DW, you execute queries that take minutes, not the same queries hundreds of times 
per second.
 

4 stars More on bind variables in Descision Support environment   June 4, 2004 - 7am Central time zone
Reviewer: A P Clarke from London, England
I've just joined a DSS project as PL/SQL developer-cum-deputy DBA.  This is my first real exposure 
to DW (beyond some ETL tuning).  

One of my Java colleagues asked me to look at a query.  I remarked on the use of literals rather 
than bind variables.  He said:
(1)   Because the query was dynamically generated by JDBC it was jolly hard to use bind variables 
(because the number of predicates in the WHERE clause was variable and hence the number of bind 
variables is not predictable).
(2)   Furthermore it was a bad idea to use bind variables in DSS because each query needs a 
different explain plan.
(3)   Besides we could always use the CURSOR_SHARING parameter.

My Java isn't up to answering point (1).

Point (2) is fair enough, but...  The system is intended to be used by dozens, eventually hundreds 
of concurrent users.  The requirements state that most queries are supposed to return answers 
within six seconds.  How much impact will constant hard parsing have on the scaleability of the 
system?

Point (3) seems to contradict point (2) and besides CURSOR_SHARING is A Bad Thing, right?

Cheers, APC
 


Followup   June 4, 2004 - 8am Central time zone:

1) it is just as jolly easy, NAY easier, to bind such a query.


not only that, but it is more secure as well.

In order to glue the query together, java programmer must have something like 2 or 3 arrays:

o array of "column names"
o array of "operators" like "=", "<", "like" and so on
o array of "operands"

and perhaps an array of conjunctors (AND/OR's)


they have code such as:

   query = "select .... from t where 1=1";
   for( int i = 0; i < names.length(); i++ )
      query = query + "and " + names(i) + operation(i) + 
              "'" + method_to_quote_strings( operands(i) ) + "'";

   parse, execute, fetch statement

well, to bind, lets see, what would they have to do??


   query = "select .... from t where 1=1";
   for( int i = 0; i < names.length(); i++ )
      query = query + "and " + names(i) + operation(i) + "?";

   parse Q

   for( int i = 0; i < operands.length(); i++ )
      Q.setVal( i, operands(i) );

   execute, fetch statement


no messy "quote this string" and no SQL Injection (have java programmer search google for "sql 
injection", or search this site)


I'm tired of java (VB, etc etc etc) programmers saying "oh, binding is sooo hard"

a) no it is not
b) it is so important
c) it is actually *easier* since you don't have quotes to be concerned with
d) it is more secure

2) sometimes, it depends.  If you are a true DW, this is true (gigabytes/terabytes of data, queries 
that are run once and never again).  Asktom is a DSS system right?  I use *binds* all over the 
place.  Why?  I do a couple of transactions (searches) per second.  My system would *fall over* if 
I did not bind.


I'll bet this application is more like "pick a table, pick some columns from table, apply 
predicate" and you would find that the end users are picking the same table, with the same columns 
and doing the same sorts of predicates against them.



3) cursor sharing is a *poor crutch* that can be used to help an ailing application not totally 
kill a database whilst the coders are fixing their bug.


cursor sharing is a "bad thing" (tm) if you have to use it, as it implies you have some serious 
application issues that need to be corrected.  cursor sharing negatively impacts applications that 
don't need it, cursor sharing changes the behaviour of your queries (select substr( c, 1, 5 ) no 
longer is known to return 5 characters as it is select substr(c,:x,:y) -- applications "break").




It is seriously funny that "binding is hard".....  binding is trivial.  not-binding is actually 
HARDER, and less secure.  (i'll keep driving home the less secure, maybe a security person will 
pick up on that and make a rule out lawing literal substitution in all code!)

 

5 stars response to A P Clarke..   June 4, 2004 - 8am Central time zone
Reviewer: bob from PA
For #1.. 
If the select clause is always the same, and it just has a variety of differing where clauses based 
on the presence/absence of some filter criteria, do it in pl/sql like this, and just let his/her 
java call your packaged procedure which gives him a refcursor/resultset.

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:1288401763279
You are the pl/sql developer, so this should be your domain.
Let the java guy go build gui front ends or slick web pages. :)

 


4 stars Ressponse to Bob   June 4, 2004 - 10am Central time zone
Reviewer: A P Clarke from London, England
Yeah, that was my initial reaction too.  The problem is, the project had already been running a 
year when I joined, so there's a lot of stuff built.  Also, there's enough PL/SQL work to keep me 
more than busy for the foreseeable future without writing a Java-PL/SQL interface as well.  So...

I have made my concerns known to the interested parties.  We are about to enter a prototype phase 
with a handful of users.  This is top of my list for investigating if (when!) query response time 
becomes an issue that needs addressing.

Thanks also to Tom for his usual lucid reply.

Take care y'all.  

APC 


4 stars Bind variables and CBO   June 15, 2004 - 10pm Central time zone
Reviewer: A reader 
Queries with bind variables look like
column = ?
and the ? is passed in at run-time 

But the CBO needs the actual value of the bind variable to generate a intelligent execution plan.

How do these 2 concepts reconcile with each other? To generate a good plan, CBO needs the actual 
value, but then that would mean that a parse is required!

Thanks 


Followup   June 16, 2004 - 12pm Central time zone:

the optimizer rarely needs that information, rarely. 

In a system that executes hundreds of queries per second/minute -- you would not want the plan for 
a given query to sometimes be "full scan" and something "index range scan" (end users would not 
like your system if it were so).  These are so called "transactional" systems.  They are the "most 
popular" (prevalant).  Not using binds in these systems is *killer*.  

In a system that executes a query in hundreds of seconds/minutes (data warehouse), you may well 
want a plan to change like that.

So, in transactional (read: most all) systems, bind variables are mandatory.
In a DW, they are not mandatory for the "big" queries. 

4 stars Bind variable peeking   June 16, 2004 - 1pm Central time zone
Reviewer: A reader 
I have heard this phrase "bind variable peeking" associated with newer Oracle versions. How does 
that apply here? Does the optimizer get a sneak preview of the bind variable's value and then 
adjust the plan accordingly, on the fly? Is this less expensive than re-parsing?

I am not sure I understand when you say "the optimizer rarely needs this information". 

Lets say the very first time a query was parsed with a bind variable on a indexed column, the 
optimizer generated a index scan, subsequently 100s of executions of the query happened to bind the 
variable to a value that returned 90% of the data i.e. the indexed column is not very selective. 
So, wouldnt histograms and stuff kick in here, all of which need to know the actual value of the 
bind variable? 

If you say that CBO rarely needs this information, by "this" I assume you mean the actual values of 
the bind variables, then why bother analyzing the tables to determine the range of values in a 
column, frequency, density, etc?

Thanks 


Followup   June 16, 2004 - 3pm Central time zone:

bind variable peeking kicks in at hard parse time. the optimizer peeks at the bind variable value 
and optimizes the query once, using that value as if it were a literal.

the concept is - you are a transactional application (you are using binds).  the query will either 
always index scan or full scan.  the bind variable you use will tell it which one is most 
appropriate.


many systems do not *need* to have histograms.  data warehouses, yes, but in most cases histograms 
are in fact not needed.
 

4 stars   June 16, 2004 - 7pm Central time zone
Reviewer: A reader 
"bind variable peeking kicks in at hard parse time. the optimizer peeks at the bind variable value 
and optimizes the query once, using that value as if it were a literal"

right, but the query would be hard parsed only the first time it is encountered (and assuming it is 
still in the shared pool of course). So, as I said, if the first encounter of the query passed in  
a non-representative or poor selectivity value for the column, the CBO would choose a index scan 
and all subsequnt executions of the query are doomed because they pass in a value for which a FTS 
would be  faster (or vice versa)

"the query will either always index scan or full scan.  the bind variable you use will tell it 
which one is most appropriate"

ok, this is where i still dont get it. You say that "the bind variable you use will tell it whether 
to index scan or full scan". Doesnt this imply that it needs to look at the actual value of the 
bind variable and hard parse it every time? And doesnt that then defeat the purpose of using bind 
variables? What am I missing? 

Most systems nowadays are not pure OLTP or pure DSS in the original meaning of the words. So, the 
query plan would have to change depending on the actual value of the bind variable being passed in. 
How does Oracle handle this? Or does it randomly decide to hard parse a query every now and then? 


Followup   June 17, 2004 - 7am Central time zone:

and I'm saying, in your transactional application -- it either always would full scan or always 
would index scan at that point in the code.  the binds, the first binds for that unique piece of 
sql, would tell it which to do.  that is the logic, that is the thought behind this.  The inputs at 
this point in the code will be more or less consistent.

say you have a table of records to process.
1,000,000,000 records.
25 codes.
99% of the records are "processed=Y"
the other 1% of the records are spread over the remaining 24 codes.

At the point in the application you go to pull unprocessed records, index range scan (you would 
never use Y at that point in the code, you are using one or more of the other 24)

At the point in the application you go to report on processed records, full scan (you are doing 
processed=Y, probably not even binding at this point).


At that point in the code -- you are either pure oltp or dss.  I understand the "system" as a whole 
is not, but at that point in the code -- it is. 

4 stars   June 17, 2004 - 9am Central time zone
Reviewer: A reader 
Your points are very well taken and I understand it, but you are thinking too much from a 
application point of view. i.e. all SQLs are embedded in code and cast in stone. 

What I was trying to point out was situations where all the SQLs are coming from a BI-type 
front-end and ALWAYS use bind variables (i.e. where processed=:b1 in your example, ALWAYS). 

In such a case, after the database is started, if the first instance of this query binds in 'Y', 
the CBO will choose a FTS and rightly so. But most subsequent invocations of this query bind in one 
of the other 24 codes, CBO will just pick up the cached execution plan and FTS and the queries will 
perform horribly!

Thanks 


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

Now you are in the DW situation though, and in those cases, the use of bind variables must be 
weighed.  And oh, how i WISH all BI front ends "always" used bind variables (with an option not to) 

5 stars While using partitioning, should you use bind variables?   July 19, 2004 - 2pm Central time zone
Reviewer: P from GA
partition elimination and joins can occur only if the optimizer knows the filtering predicate in 
advance. the value of bind variables are not known until its time to execute making the process of 
partition elimination or joins impossible. 

tom what is your opinion about this? 


Followup   July 19, 2004 - 2pm Central time zone:

hogwash (it is not about opinions either -- this stuff is "factual", nothing opinionated about it)

totally wrong.  bzzztt....

ops$tkyte@ORA9IR2> CREATE TABLE t
  2  (
  3    temp_date  date,
  4    x          int,
  5    y int
  6  )
  7  PARTITION BY RANGE (temp_date)
  8  subpartition by hash(x)
  9  (
 10    PARTITION part1 VALUES LESS THAN (to_date('13-mar-2003','dd-mon-yyyy')) tablespace users,
 11    PARTITION part2 VALUES LESS THAN (to_date('14-mar-2003','dd-mon-yyyy')) tablespace tools,
 12    PARTITION junk VALUES LESS THAN (MAXVALUE)
 13  )
 14  ;
 
Table created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable x varchar2(25);
ops$tkyte@ORA9IR2> delete from plan_table;
 
4 rows deleted.
 
ops$tkyte@ORA9IR2> explain plan for
  2  select * from t where temp_date = to_date(:x,'dd-mon-yyyy');
 
Explained.
 
ops$tkyte@ORA9IR2> select * from table(dbms_xplan.display);
 
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
---------------------
 
--------------------------------------------------------------------------------------
| Id  | Operation              |  Name | Rows  | Bytes | Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |       |     1 |    35 |     2 |       |       |
|   1 |  PARTITION RANGE SINGLE|       |       |       |       |   KEY |   KEY |
|   2 |   PARTITION HASH ALL   |       |       |       |       |     1 |     1 |
|*  3 |    TABLE ACCESS FULL   | T     |     1 |    35 |     2 |   KEY |   KEY |
--------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - filter("T"."TEMP_DATE"=TO_DATE(:Z,'dd-mon-yyyy'))
 
Note: cpu costing is off
 
16 rows selected.


see the pstart/pstop -- it KNOWS it will partition eliminate, it doesn't know which partitions yet, 
but it knows it will.

partition elimination works dandy with binds by design.

 

5 stars bind variable in static SQL and Java (VB) code   July 20, 2004 - 8pm Central time zone
Reviewer: Sean from NJ, USA
Hi Tom,

You mentioned in your books and on this site that without using bind variable is one of the biggest 
problems in application.  Here are a few questions:

1.  In stored procedure, it seems that I would have bind variable issue when I use dynamic SQL.  
Most of my stored procedures use static SQL like the following one:
-----------------------------------
create table emp_temp as select * from emp where 1=2;

create or replace procedure p1 (p_deptno in number)
is
begin
    for r_emp in ( select * from emp where deptno = p_deptno )
    loop
        insert into emp_temp (empno, sal)
        values(r_emp.empno, r_emp.sal *1.1);
    end loop;
end;
/
------------------------------------

I assume that "r_emp.empno" and "r_emp.sal *1.1" are bind variables.  Of course, I understand that 
if I use "select * from emp where depnto = 7900", then 7900 is not bind variable.  But it is quite 
rare we would have such query in the stored procedure.

Question:  Do I need to worry about bind variable issue if I have stored procedure using static SQL 
like the above one?

2.  If bind variable issues are only related to dynamic SQL, it is hard for me to understand why 
this is the big issue since in general we don't use much dynamic SQL in stored procedure, at least 
in our application.

3.  You mentioned that in your book that JDBC use only dynamic SQL, my guess is that bind variable 
problem are mainly from code in Java or VB.  If it is true, I would like to know how to use bind 
variables in Java code.

4.  In page 449 of Export One on One, you have code how to find whether application use bind 
variables, but I did not 
find them on the zip file.

Thank so much for your help.
 

 


Followup   July 20, 2004 - 9pm Central time zone:

1) static sql in plsql is always 100% perfectly bound, you cannot code it otherwise.

only dynamic sql must be explictly bound by you.

2) because many of the developers out there that were taught VB or Java believe there is no other 
language, or that all other languages are to be avoided.

and in VB and Java, there is only dynamic sql.

3) Use prepared/callable statements and the "bind" methods available with them.

4) everything should have been there, but:

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:1163635055580
has it. 

5 stars Bind variable in Java or VB code   July 20, 2004 - 9pm Central time zone
Reviewer: Sean from NJ, USA
Hi Tom,
(1) So actually according to your experience, most of SQL code which do not use bind variables are 
from VB or Java, not from stored procedures though almost all your examples of bind variables use 
stored procedure.

(2) If (1) is true, we should focus on fixing bind variable problems in VB or Java instead of 
stored procedure, hence more examples or doc should be available.  By looking at Oracle JDBC guide, 
it seems that it didn't mention using bind variable.  Would you please give us a link or some doc 
which show how to use bind variable in Java or VB (maybe it is so simple that every VB or Java 
developer should know bind variable already).

Thanks so much for your help.


 


Followup   July 20, 2004 - 10pm Central time zone:

1) yes on the first part, but not sure what you mean by the second part (other than I use plsql as 
my demonstration language of choice, yes..)

2) our documentation is not the documenation of the entire api - more about extensions to jdbc that 
we've added and "oracle'isms"

Sun "owns" the jdbc spec if you will - they document these things.

The java docs document the prepared/callable statement classes, pretty much every book on jdbc 
programming (we do not have one on jdbc programming, just on what our jdbc extensions are) 
discusses it. 

4 stars How to use bind variables?   August 22, 2004 - 5am Central time zone
Reviewer: A reader 
Hi Tom.
I am writting a dynamic sql procedure, which builds inline views. The code below is being 
developed, Could you please help me how to use bind variables in inline view:
declare        
l_sclause LONG;
l_wclause LONG;
l_from_clause LONG;
l_state_code DBMS_SQL.VARCHAR2_TABLE;
l_exec_string LONG;
begin
SELECT DISTINCT state_code BULK COLLECT INTO l_state_code FROM STATE;

l_sclause := 'INSERT /*+ APPEND */ INTO test_1 (client, total_amnt) 
              SELECT main.client, main.value+';
l_wclause := 'WHERE main.client = ';
l_from_clause := ' FROM test_3 main, ';
FOR i in 1.. l_state_code.count LOOP
l_from_clause := l_from_clause||
' (SELECT client, NVL(estimate,0) estimate FROM test_2 WHERE 
state_code='||''''||l_state_code(i)||''''||') '||l_state_code(i) ;
l_sclause := l_sclause ||'NVL('||l_state_code(i)||'.estimate,0)+';
l_exec_string := rtrim(l_sclause,'+');
l_exec_string := l_exec_string||l_from_clause;
l_exec_string := l_exec_string||' '||l_wclause||l_state_code(i)||'.client(+)';
P(l_exec_string);
EXECUTE IMMEDIATE l_exec_string;
END LOOP;
COMMIT;
END;
/
________________
output:
----------------
INSERT /*+ APPEND */ INTO test_1 (client, total_amnt)
              SELECT
main.client, main.value+NVL(A.estimate,0) FROM test_3 main,  (SELECT client,
NVL(estimate,0) estimate FROM test_2 WHERE state_code='A') A WHERE main.client =
A.client(+)
----------------

PL/SQL procedure successfully completed

I was reading chapter 5 of your new book, I can write to use bind variables with execute immediate, 
can I use bind variables in inline views?
Also, I use your P procedure to display the output (o/p), if o/p is very large, Is there any way I 
can get the formatted o/p?
 
Thanks 


Followup   August 22, 2004 - 8am Central time zone:

they would be no different than bind variables without inline views.  

you will be using DBMS_SQL since you do not know the number of bind variables at compile time.


for i in 1 .. l_state_code.count
loop
  l_query := l_query || 
           ' (select ... from test_2 where state_code = :state_'||i|| '.....';
end loop;

dbms_sql.parse that query

for i in 1 .. l_state_code.count
loop
  dbms_sql.bind_variable( l_cursor, 'state_'||i, l_state(i) );
end loop;


Here is a procedure "p" i use with dbms_output for lines > 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;
/
 

5 stars P procedure   August 23, 2004 - 5am Central time zone
Reviewer: A reader 
Hi Tom,
Thanks for your help. As per your book "Expert one-on-one", appendix 'A' page 1154, it says that it 
takes string upto 32KB. I am hitting this limit, Could you please help, how can I overcome this? I 
am using Oracle 9.2. Can I use clob?
Regards, 


Followup   August 23, 2004 - 8am Central time zone:

yes, you can use a clob. 

4 stars Session Hangs   August 24, 2004 - 4pm Central time zone
Reviewer: A reader 
Hi Tom,
When I changed variable in p procedure to clob from long, my session hangs. Finally , I have to 
kill the session. Please advise.
Thanks 


Followup   August 24, 2004 - 7pm Central time zone:

you probably coded an infinite loop....

no clue, no code, no clue.

and if the code is bigger than a screen - make it smaller before posting, I'm not going to read 
scads of code.  try to debug it yourself using conventional techniques 
(dbms_application_info.set_client_info called in your code to say where you are/what you are doing 
is useful as this can be queried in another session right out of v$session - -can be very useful) 

4 stars More info...   August 25, 2004 - 8am Central time zone
Reviewer: A reader 
Hi Tom,

Here is more info...
   
  1  DECLARE
  2   X clob;
  3   y clob;
  4   z clob;
  5  BEGIN
  6  x := rpad('X',32767,'X');
  7  z := x||y;
  8  --dbms_output.put_line(z);
  9* END;
SQL> /

PL/SQL procedure successfully completed.
___________________
  1  DECLARE
  2   X clob;
  3   y clob;
  4   z clob;
  5  BEGIN
  6  x := rpad('X',32768,'X');
  7  z := x||y;
  8  --dbms_output.put_line(z);
  9* END;
SQL> /
DECLARE
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 6
_______________

Session Hangs

-- Where p is using clob
1  DECLARE
2   X clob;
3   y clob;
4   z clob;
5  BEGIN
6  x := rpad('X',32767,'X');
7  z := x||y;
8  p(z);
9  --dbms_output.put_line(z);
0* END;
/

Thanks
 


Followup   August 25, 2004 - 8am Central time zone:

does not "hang" for me -- however the ora-6502 should not be happening in the second example, since 
you have the ultimate in concise test cases -- please file that with support.


if you just changed p from varchar2/long to clob, it is as I said, you coded an INFINITE LOOP 
(hint: does the l_str clob value ever go "null" as the string l_str would have?


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

4 stars Parsing   November 19, 2004 - 3pm Central time zone
Reviewer: reader from USA
select 'X'
FROM
 PS_CP_CONFIG C1 WHERE C1.BUSINESS_UNIT = 'MSTRX' AND C1.INV_ITEM_ID = :1 AND
  C1.CONFIG_CODE = :2


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     1668      0.33       0.29          0          0          0           0
Execute   1668      0.08       0.09          0          0          0           0
Fetch     1668      0.03       0.06          3       5004          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     5004      0.44       0.44          3       5004          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 21  (SYSADM)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  INDEX UNIQUE SCAN (object id 5087)


Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'PS_CP_CONFIG' (UNIQUE)

********************************************************************************


1) As you can see in the TKPROF results, no of parses = no of executes, which is bad. Would using a 
bind variable in WHERE C1.BUSINESS_UNIT = 'MSTRX'  help solve this ?  


2) If yes, then what is the problem with the below sql (using only bind variable here ....)

select IBU_GROUP
from
 PS_BUS_UNIT_TBL_IN where BUSINESS_UNIT = :1


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse     1668      0.21       0.33          0          0          0           0
Execute   1668      0.08       0.10          0          0          0           0
Fetch     1668      1.04       1.07          3      11676       6672        1668
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     5004      1.33       1.50          3      11676       6672        1668

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 21  (SYSADM)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  INDEX FAST FULL SCAN (object id 375491)


Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   INDEX   GOAL: ANALYZED (FAST FULL SCAN) OF 'PSABUS_UNIT_TBL_IN'
              (NON-UNIQUE)

********************************************************************************
 Why is no. of parses = no. of executes ?

Thanks 


Followup   November 19, 2004 - 7pm Central time zone:

1) no, it is well bound -- that literal is in fact constant.

what needs be fixed here is the CODE, the programmer is parse/binding/executing/closing.  they want 
to parse  bind/execute and bind/execute and bind/execute.

when parse = execute, it is the code's fault. 

4 stars parsing   November 22, 2004 - 10am Central time zone
Reviewer: reader from USA
Thanks for this clarification. Now my next question on how to achieve what your are suggesting.
Basically the program does this :

1) Read the staging table to pick up all unprocessed rows.(using just a SQL, not a Pl/Sql cursor)
2) For each row perform a series of validation (like the 2 sqls I have mentioned above)
3) Write the row into the actual Table, if it satifies all validation.

How to  achieve parse, bind/execute, bind/execute....... in this scenario ? Even if the code is 
changed to pl/sql, we would still have 

1) Open the cursor to read unprocessed rows from Staging table
2) Fetch row
3) Validate condition1
            condition2
            condition3 ......
4) write into actual table

I dont see this method achieving the parse, bind/execute, bind/execute .... either. Would this 
still not be parse, bind, execute, close , parse, bind, execute, close  .......


So having described what the program is for, what would be the best method to implement your 
suggestion.Is there any way of doing it without changing the whole thing to pl/sql ( The programmer 
writing this program is not familiar with pl/sql) 

Thanks




 


Followup   November 22, 2004 - 3pm Central time zone:

programmer needs to read about "prepared statements" to figure out how programmer can parse ONCE 
and execute over and over.


in plsql, it would just happen.

in your 3gl code -- they have to CODE IT.  

without knowing the language, it is truly hard to say more (and if you say "VB", I'll just refer 
you elsewhere as that particular language refuses to compile and run on any OS i use) 

3 stars Parsing   November 22, 2004 - 5pm Central time zone
Reviewer: reader from USA
Thanks Tom.
But I still need to clarify a few things.
First, not sure what prepared statements are . Looked thru this site, but did not find anything (as 
yet) , that explained this concept. Could you direct me to the right place ? The program is being 
written using SQR (PeopleSoft)
You can embed SQL in this.

Second, how does it 'just happen' in pl/sql ? Can I have an example ?

Also, in the trace file I see that Parse = 0 for some statements. Wouldn't each statement be parsed 
atleast once ?
What does this indicate ? I have listed 2 examples below 


insert into ps_cm_rcv_cost_inv
values
 (:1, :2, :3, :4, :5, :6, :7, :8, :9, :10, :11, :12, :13, 1, '100', :14, :15,
  0)


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute  41013     10.74      10.84        269       1970     302428       41013
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    41013     10.74      10.84        269       1970     302428       41013



select max(seq_nbr)
from
 ps_transaction_inv where business_unit = :1 and   inv_item_id   = :2 and
  dt_timestamp  = :3


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute  89533      4.46       4.00          0          0          0           0
Fetch   177593      4.13       3.98         71     270014          0        1473
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   267126      8.59       7.98         71     270014          0        1473


Thanks 


Followup   November 22, 2004 - 5pm Central time zone:

you'll have to ask the makers of SQR if they thought about performance at all... I can show you 
preparedstatements in java, C, many languages -- but SQR?  nope.

a search for preparedstatement on this site returned about 428 or so hits.  very first hit 
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:1993620575194
talked about it.

parse count of 0 just means you started tracing AFTER they parsed it -- if that insert comes from 
sqr, sqr definitely supports prepared statements -- for they parsed the insert ONCE and executed it 
41,013 times for 41,013 rows (which is another problem, row by slow)....


something way funny on the select max(), select max without a group by always returns at least one 
and at most one row.  those counts look wacky -- 1,473 doesn't seem possible. 

5 stars   November 23, 2004 - 4am Central time zone
Reviewer: Helena Marková from Bratislava, Slovakia


4 stars Peeking bind values..   November 24, 2004 - 5pm Central time zone
Reviewer: RD from NZ
Hi Tom,

I am going through this thread and a few questions come to mind (regarding a few scenerios we are 
facing at work at the very moment) and they are:

Is this PEEKING of bind variables only a 9i and above feature or 8i( which we are using) as well? I 
ask because, from what you explained to Vikas, we are facing a scenerio where some queries which 
were taking just a few seconds are suddenly taking ages now. Seriously. One query which was running 
in 12 seconds is now running in 24 min's. Can this be a cause?(guess not if it's a new 9i feature). 

What else can be done ? and frankly I don't understand why this would be happening? Nothing !! no 
really nothing has changed in the application because all changes go in through me only. We also 
don't have the before explain plans for all queries so can't compare. We analyze all tables weekly 
on sundays and we have an OLTP database.

I am really in need of some help or advice here.

Thanks,
Regards,
RD. 


Followup   November 24, 2004 - 5pm Central time zone:

9i and above...

did the problem happen immediately after an analyze took place? 

5 stars YES   November 24, 2004 - 5pm Central time zone
Reviewer: RD from NZ
Hi Tom,

Exactly!!! Problems started on Monday morning.

Regards,
RD. 


Followup   November 24, 2004 - 5pm Central time zone:

well you see the statement "nothing changed" is not accurate, you analyzed, that is a change.

Suggest you take a look at the autotrace traceonly output for this query AND the tkrprof the query 
and see where the optimizer has gone very wrong -- look at the CARD= in the autotrace, look at the 
rows flowing through that step.  compare them and see where it is "very very wrong".

Then we can start to isolate the cause. 

what is the exact way you gather stats?
 

5 stars Peeking ..   November 24, 2004 - 6pm Central time zone
Reviewer: RD from NZ
Hi Tom,

I analyze the schema like this:-

exec sys.dbms_utility.analyze_schema('swbprod','compute');
 
Not good???

Thanks,
Regards,
RahulD.   


Followup   November 24, 2004 - 6pm Central time zone:

that is good -- but in the future, you'll want to add in "dbms_stats.export..." first (save them 
off so you can restore them if you need)


but -- back to the problem at hand, what do you see in the autotrace/tkprof 

5 stars Query..   November 25, 2004 - 8pm Central time zone
Reviewer: RD from NZ
Hi Tom,

Hi Tom,

The latest query to become a victim is this one:-

SELECT DISTINCT B.TREE_NODE , Z.RECDESCR 
FROM PSTREEDEFN A ,PSTREENODE B ,PS_SCRTY_ACC_GRP C ,PSTREENODE E ,PSROLECLASS X ,PSROLEUSER 
Y,PSRECDEFN Z 
WHERE A.SETID=' ' 
AND A.TREE_STRCT_ID='ACCESS_GROUP' 
AND A.EFF_STATUS='A' 
AND A.EFFDT = ( SELECT MAX(D.EFFDT) 
    FROM PSTREEDEFN D 
    WHERE D.SETID=' ' 
    AND D.TREE_NAME=A.TREE_NAME 
    AND D.EFFDT<=TO_DATE(TO_CHAR(SYSDATE,'YYYY-MM-DD'),'YYYY-MM-DD')) 
    AND Y.ROLEUSER='RJD' AND Y.ROLENAME=X.ROLENAME 
    AND X.CLASSID = C.CLASSID AND C.TREE_NAME=A.TREE_NAME 
    AND C.ACCESSIBLE='Y' AND B.SETID=' ' 
    AND B.TREE_NAME=E.TREE_NAME AND B.EFFDT=E.EFFDT 
    AND B.TREE_NODE_TYPE='R' AND B.TREE_NODE=Z.RECNAME 
    AND Z.RECTYPE IN (0, 1, 6) AND E.SETID=' ' 
    AND E.TREE_NAME=A.TREE_NAME AND E.EFFDT=A.EFFDT 
    AND E.TREE_NODE_TYPE='G' AND B.TREE_NODE_NUM BETWEEN E.TREE_NODE_NUM 
    AND E.TREE_NODE_NUM_END AND C.ACCESS_GROUP=E.TREE_NODE 
    AND ((NOT EXISTS ( SELECT 'X' 
        FROM PS_SCRTY_ACC_GRP F
         WHERE F.CLASSID=X.CLASSID 
    AND F.TREE_NAME=A.TREE_NAME 
    AND F.ACCESSIBLE='N')) 
    OR (E.TREE_NODE_NUM = ( SELECT MAX(G.TREE_NODE_NUM) 
            FROM PSTREENODE G ,PS_SCRTY_ACC_GRP H 
            WHERE G.SETID=' ' 
            AND G.TREE_NAME=A.TREE_NAME 
            AND G.EFFDT=A.EFFDT and G.TREE_NODE_TYPE='G' 
            AND B.TREE_NODE_NUM BETWEEN G.TREE_NODE_NUM 
            AND G.TREE_NODE_NUM_END AND H.CLASSID=X.CLASSID 
            AND H.TREE_NAME=A.TREE_NAME AND H.ACCESS_GROUP=G.TREE_NODE)))


Till last Sunday it was running in say 12 seconds but suddenly on Monday morning it started taking 
2 min's almost and also figured on my STATSPACK report.

  Buffer Gets    Executions  Gets per Exec  % Total  Hash Value
--------------- ------------ -------------- ------- ------------
      5,523,922            2    2,761,961.0    35.2   2175643882
SELECT DISTINCT B.TREE_NODE , Z.RECDESCR FROM PSTREEDEFN A ,PSTR
EENODE B ,PS_SCRTY_ACC_GRP C ,PSTREENODE E ,PSROLECLASS X ,PSROL
EUSER Y ,PSRECDEFN Z WHERE A.SETID=' ' AND A.TREE_STRCT_ID='ACCE
SS_GROUP' AND A.EFF_STATUS='A' AND A.EFFDT = ( SELECT MAX(D.EFFD
T) FROM PSTREEDEFN D WHERE D.SETID=' ' AND D.TREE_NAME=A.TREE_NA

BUT the strange thing is when I delete statistics on table "PS_SCRTY_ACC_GRP" the query
run great.( in just 12 seconds ). 

The Autotrace, explainplan and timing before dropping stats on "PS_SCRTY_ACC_GRP"


3653 rows selected.

Elapsed: 00:01:03.06

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=14 Card=1 Bytes=240)
   1    0   SORT (UNIQUE) (Cost=14 Card=1 Bytes=240)
   2    1     FILTER
   3    2       NESTED LOOPS (Cost=11 Card=1 Bytes=240)
   4    3         NESTED LOOPS (Cost=10 Card=1 Bytes=203)
   5    4           NESTED LOOPS (Cost=10 Card=1 Bytes=185)
   6    5             NESTED LOOPS (Cost=9 Card=1 Bytes=161)
   7    6               NESTED LOOPS (Cost=7 Card=1 Bytes=121)
   8    7                 NESTED LOOPS (Cost=4 Card=1 Bytes=81)
   9    8                   INDEX (FAST FULL SCAN) OF 'PSAPSTREEDEFN'(NON-UNIQUE) (Cost=1 Card=1 
Bytes=35)
  10    8                   INDEX (RANGE SCAN) OF 'PSAPSTREENODE' (NON-UNIQUE) (Cost=3 Card=1224 
Bytes=56304)
  11    7                 TABLE ACCESS (BY INDEX ROWID) OF 'PSTREENODE(Cost=3 Card=1224 
Bytes=48960)
  12   11                   INDEX (RANGE SCAN) OF 'PSGPSTREENODE' (NON-UNIQUE) (Cost=2 Card=1224)
  13    6               TABLE ACCESS (FULL) OF 'PS_SCRTY_ACC_GRP' (Cost=2 Card=1261 Bytes=50440)
  14    5             INDEX (FAST FULL SCAN) OF 'PS_PSROLECLASS' (UNIQUE) (Cost=1 Card=1151 
Bytes=27624)
  15    4           INDEX (UNIQUE SCAN) OF 'PS_PSROLEUSER' (UNIQUE)
  16    3         TABLE ACCESS (BY INDEX ROWID) OF 'PSRECDEFN' (Cost=1 Card=13049 Bytes=482813)
  17   16           INDEX (UNIQUE SCAN) OF 'PS_PSRECDEFN' (UNIQUE)
  18    2       SORT (AGGREGATE)
  19   18         INDEX (RANGE SCAN) OF 'PS_PSTREEDEFN' (UNIQUE) (Cost=1 Card=1 Bytes=23)
  20    2       TABLE ACCESS (BY INDEX ROWID) OF 'PS_SCRTY_ACC_GRP' (Cost=3 Card=1 Bytes=24)
  21   20         INDEX (RANGE SCAN) OF 'PS_SCRTY_ACC_GRP' (UNIQUE) (Cost=2 Card=1)
  22    2       SORT (AGGREGATE)
  23   22         NESTED LOOPS (Cost=2 Card=1 Bytes=85)
  24   23           TABLE ACCESS (BY INDEX ROWID) OF 'PSTREENODE' (Cost=2 Card=1 Bytes=46)
  25   24             INDEX (RANGE SCAN) OF 'PSFPSTREENODE' (NON-UNIQUE) (Cost=1 Card=1)
  26   23           INDEX (UNIQUE SCAN) OF 'PS_SCRTY_ACC_GRP' (UNIQUE)




Statistics
----------------------------------------------------------
         14  recursive calls
     705000  db block gets
    2040354  consistent gets
         16  physical reads
          0  redo size
     201731  bytes sent via SQL*Net to client
      31537  bytes received via SQL*Net from client
        246  SQL*Net roundtrips to/from client
         40  sorts (memory)
          0  sorts (disk)
       3653  rows processed

After dropping statistics.


3653 rows selected.

Elapsed: 00:00:01.07

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13 Card=1 Bytes=242)
   1    0   SORT (UNIQUE) (Cost=13 Card=1 Bytes=242)
   2    1     FILTER
   3    2       NESTED LOOPS (Cost=10 Card=1 Bytes=242)
   4    3         NESTED LOOPS (Cost=9 Card=1 Bytes=205)
   5    4           NESTED LOOPS (Cost=6 Card=1 Bytes=165)
   6    5             NESTED LOOPS (Cost=4 Card=1 Bytes=119)
   7    6               NESTED LOOPS (Cost=4 Card=12 Bytes=1212)
   8    7                 NESTED LOOPS (Cost=3 Card=1 Bytes=77)
   9    8                   INDEX (FAST FULL SCAN) OF 'PSAPSTREEDEFN'(NON-UNIQUE) (Cost=1 Card=1 
Bytes=35)
  10    8                   TABLE ACCESS (BY INDEX ROWID) OF 'PS_SCRTY_ACC_GRP' (Cost=2 Card=9 
Bytes=378)
  11   10                     INDEX (RANGE SCAN) OF 'PSBSCRTY_ACC_GRP'(NON-UNIQUE) (Cost=1 Card=9)
  12    7                 INDEX (FAST FULL SCAN) OF 'PS_PSROLECLASS' (UNIQUE) (Cost=1 
d=1151Bytes=27624)
  13    6               INDEX (UNIQUE SCAN) OF 'PS_PSROLEUSER' (UNIQUE)
  14    5             TABLE ACCESS (BY INDEX ROWID) OF 'PSTREENODE' (Cost=2 Card=1224 Bytes=56304)
  15   14               INDEX (RANGE SCAN) OF 'PSCPSTREENODE' (NON-UNIQUE) (Cost=1 Card=1224)
  16    4           TABLE ACCESS (BY INDEX ROWID) OF 'PSTREENODE' (Cost=3 Card=1224 Bytes=48960)
  17   16             INDEX (RANGE SCAN) OF 'PSGPSTREENODE' (NON-UNIQUE) (Cost=2 Card=1224)
  18    3         TABLE ACCESS (BY INDEX ROWID) OF 'PSRECDEFN' (Cost=1 Card=13049 Bytes=482813)
  19   18           INDEX (UNIQUE SCAN) OF 'PS_PSRECDEFN' (UNIQUE)
  20    2       SORT (AGGREGATE)
  21   20         INDEX (RANGE SCAN) OF 'PS_PSTREEDEFN' (UNIQUE) (Cost=1 Card=1 Bytes=23)
  22    2       TABLE ACCESS (BY INDEX ROWID) OF 'PS_SCRTY_ACC_GRP' (Cost=2 Card=1 Bytes=30)
  23   22         INDEX (RANGE SCAN) OF 'PSBSCRTY_ACC_GRP' (NON-UNIQUE) (Cost=1 Card=1)
  24    2       SORT (AGGREGATE)
  25   24         NESTED LOOPS (Cost=2 Card=1 Bytes=86)
  26   25           TABLE ACCESS (BY INDEX ROWID) OF 'PSTREENODE' (Cost=2 Card=1 Bytes=46)
  27   26             INDEX (RANGE SCAN) OF 'PSFPSTREENODE' (NON-UNIQUE) (Cost=1 Card=1)
  28   25           INDEX (UNIQUE SCAN) OF 'PS_SCRTY_ACC_GRP' (UNIQUE)




Statistics
----------------------------------------------------------
          0  recursive calls
       5024  db block gets
      40557  consistent gets
         12  physical reads
          0  redo size
     201731  bytes sent via SQL*Net to client
      31511  bytes received via SQL*Net from client
        246  SQL*Net roundtrips to/from client
         40  sorts (memory)
          0  sorts (disk)
       3653  rows processed

See the consistent gets too, way low.I was particularily watching this.
 
Table             rows

PSTREEDEFN              146
PSTREENODE              22036
PS_SCRTY_ACC_GRP        1261
PSROLECLASS             1151
PSROLEUSER              1519
PSRECDEFN               35240

This has to have some explaination that I'm sure only you are capable of providing. No one else 
seems to even come close to understatding this.

Thanks in advance,
Regards,
RahulD.
            

             


Followup   November 25, 2004 - 8pm Central time zone:

please re-read what I asked for -- totally different from what was supplied. 

5 stars Query..   November 25, 2004 - 8pm Central time zone
Reviewer: RD from NZ
Hi Tom,

This is what I got from autotrace. Is that not what you asked for? Or
may be I misunderstood.
Regards,
RahulD. 


Followup   November 25, 2004 - 8pm Central time zone:

<quote>

Suggest you take a look at the autotrace traceonly output for this query AND the 
tkrprof the query and see where the optimizer has gone very wrong -- look at the 
CARD= in the autotrace, look at the rows flowing through that step.  compare 
them and see where it is "very very wrong".

Then we can start to isolate the cause. 

</quote> 

4 stars Same problem here too RD, you're not alone   November 28, 2004 - 4pm Central time zone
Reviewer: Kay from NY
Hi Tom,

I have been following this thread and have experianced the same problems as RD and am eagerly 
waiting for some guidence on how to go about analyzing this parlicular problem which has given me 
much grief( and made me look bad because I could'ent provide any explaination to why it happens). 
Being a Junior/Intermediate DBA dosen't help much either.

Best of luck to all in same boat as US.

So long,
Eugene.
 


Followup   November 28, 2004 - 7pm Central time zone:

yes you are -- in that neither of you has compared what tkprof got (row count wise) with what 
autotrace traceonly explain thought would happen, presented that -- along with the parts of the 
predicate that would be associated with that.

for example in yours I see:

     61          INDEX FAST FULL SCAN (object id 34559)

vs

 INDEX (FAST FULL SCAN) OF 'PSAPSTREEDEFN'(NON-UNIQUE) (Cost=1 Card=1 Bytes=35)


Ok, soooo -- why so far off? and given that you and only you know the query here and the indexing 
scheme and the predicate -- you and only you can answer this.

that is what I would start doing.  looking at the differences between "what was assumed" and what 
really happened and start asking "why"?

are the stats up to date.
maybe I needed a histogram.

things like that. 

4 stars Autotrace and TKPROF   November 28, 2004 - 5pm Central time zone
Reviewer: RD from NZ
Hi Tom,
Hope I got it right this time around,

From Autotrace:-

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=14 Card=1 Bytes=240)
   1    0   SORT (UNIQUE) (Cost=14 Card=1 Bytes=240)
   2    1     FILTER
   3    2       NESTED LOOPS (Cost=11 Card=1 Bytes=240)
   4    3         NESTED LOOPS (Cost=10 Card=1 Bytes=203)
   5    4           NESTED LOOPS (Cost=10 Card=1 Bytes=185)
   6    5             NESTED LOOPS (Cost=9 Card=1 Bytes=161)
   7    6               NESTED LOOPS (Cost=7 Card=1 Bytes=121)
   8    7                 NESTED LOOPS (Cost=4 Card=1 Bytes=81)
   9    8                   INDEX (FAST FULL SCAN) OF 'PSAPSTREEDEFN'(NON-UNIQUE) (Cost=1 
ard=1Bytes=35)
  10    8                   INDEX (RANGE SCAN) OF 'PSAPSTREENODE' (NON-UNIQUE) (Cost=3 Card=1224 
=56304)
  11    7                 TABLE ACCESS (BY INDEX ROWID) OF 'PSTREENODE(Cost=3 Card=1224 
Bytes=48960)
  12   11                   INDEX (RANGE SCAN) OF 'PSGPSTREENODE' (NON-UNIQUE) (Cost=2 Card=1224)
  13    6               TABLE ACCESS (FULL) OF 'PS_SCRTY_ACC_GRP' (Cost=2 Card=1261 Bytes=50440)
  14    5             INDEX (FAST FULL SCAN) OF 'PS_PSROLECLASS' (UNIQUE) (Cost=1 Card=1151 
Bytes=27624)
  15    4           INDEX (UNIQUE SCAN) OF 'PS_PSROLEUSER' (UNIQUE)
  16    3         TABLE ACCESS (BY INDEX ROWID) OF 'PSRECDEFN' (Cost=1 Card=13049 Bytes=482813)
  17   16           INDEX (UNIQUE SCAN) OF 'PS_PSRECDEFN' (UNIQUE)
  18    2       SORT (AGGREGATE)
  19   18         INDEX (RANGE SCAN) OF 'PS_PSTREEDEFN' (UNIQUE) (Cost=1 Card=1 Bytes=23)
  20    2       TABLE ACCESS (BY INDEX ROWID) OF 'PS_SCRTY_ACC_GRP' (Cost=3 Card=1 Bytes=24)
  21   20         INDEX (RANGE SCAN) OF 'PS_SCRTY_ACC_GRP' (UNIQUE) (Cost=2 Card=1)
  22    2       SORT (AGGREGATE)
  23   22         NESTED LOOPS (Cost=2 Card=1 Bytes=85)
  24   23           TABLE ACCESS (BY INDEX ROWID) OF 'PSTREENODE' (Cost=2 Card=1 Bytes=46)
  25   24             INDEX (RANGE SCAN) OF 'PSFPSTREENODE' (NON-UNIQUE) (Cost=1 Card=1)
  26   23           INDEX (UNIQUE SCAN) OF 'PS_SCRTY_ACC_GRP' (UNIQUE)




Statistics
----------------------------------------------------------
         14  recursive calls
     705000  db block gets
    2040354  consistent gets
         16  physical reads
          0  redo size
     201731  bytes sent via SQL*Net to client
      31537  bytes received via SQL*Net from client
        246  SQL*Net roundtrips to/from client
         40  sorts (memory)
          0  sorts (disk)
       3653  rows processed

From TKPROF :-

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.39       0.54          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch      245     64.88      66.35        199    2040351     705000        3653
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      247     65.27      66.89        199    2040351     705000        3653

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 521  

Rows     Row Source Operation
-------  ---------------------------------------------------
   3653  SORT UNIQUE 
   4986   FILTER 
   4987    NESTED LOOPS 
   5364     NESTED LOOPS 
 188552      NESTED LOOPS 
 162042       NESTED LOOPS 
  14209        NESTED LOOPS 
    769         NESTED LOOPS 
     61          INDEX FAST FULL SCAN (object id 34559)
    828          INDEX RANGE SCAN (object id 34578)
  14976         TABLE ACCESS BY INDEX ROWID PSTREENODE 
  16727          INDEX RANGE SCAN (object id 34583)
 176249        TABLE ACCESS FULL PS_SCRTY_ACC_GRP 
 350592       INDEX FAST FULL SCAN (object id 35215)
 193914      INDEX UNIQUE SCAN (object id 35186)
  10349     TABLE ACCESS BY INDEX ROWID PSRECDEFN 
  10354      INDEX UNIQUE SCAN (object id 34383)
      0    SORT AGGREGATE 
      0     NESTED LOOPS 
      0      TABLE ACCESS BY INDEX ROWID PSTREENODE 
      0       INDEX RANGE SCAN (object id 34582)
      0      INDEX UNIQUE SCAN (object id 35245)
     42    TABLE ACCESS BY INDEX ROWID PS_SCRTY_ACC_GRP 
     85     INDEX RANGE SCAN (object id 35245)
     82    SORT AGGREGATE 
     41     INDEX RANGE SCAN (object id 34558)

Thanks yet again in advance,
RD. 


4 stars Bind Variables   March 18, 2005 - 2pm Central time zone
Reviewer: HM from NJ
Tom,
I am not sure if this relevant to this thread, but here's the issue:

sqlstmt:='select * from emp where employee_name=:the_name';
execute immediate sqlstmt using 'jdoe';
does not appear to work (assuming that tables and data exist).

This works if the bind variable is an integer for a different column ofcourse.

Am I doing something wrong? 


Followup   March 18, 2005 - 2pm Central time zone:

what do you expect it to do exactly?

how do you know that a select that cannot give you any output whatsoever based on the way you are 
using it "doesn't work"

now,

execute immediate sqlstmt into var1, var2, ... varN using 'jdoe';

that might work (assuming you have "one" jdoe)

unless you have a real need to use dynamic sql (this example doesn't show that), I strongly 
recommend you don't use dynamic sql. 

3 stars Bind Variables   March 18, 2005 - 3pm Central time zone
Reviewer: A reader 
Tom,
I simplified the query quite a bit. There are several if/then/else statements that form a more 
complex query. However, I did notice that the query is parsed every time only because, the username 
changes, while the other parameters stay fairly static, so I was hoping to use a bind variable for 
the Username, such that it would not be parsed repeatedly. I noticed that the bind varible does not 
appear to work for a character variable, but I am not convinced. I could give you more detail if 
this is not clear. 


Followup   March 18, 2005 - 8pm Central time zone:

well, strings are the simpliest of bind variables and they totally 100% work

so yes, you would need to provide a concise, small, short example to demonstrate what you mean. 

4 stars Bind variable   April 5, 2005 - 9am Central time zone
Reviewer: Yogesh from Pune, India
IÂ’m facing performance issue in one of the procedure.  Procedure has one main cursor, which accepts 
2 parameters sdate and edate. Following is the tkprof output for cursor query, when sdate = 
01-FEB-2005 and edate = 2-FEB-2005

SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= :b1  AND T1.TDATE <= :b2  AND   
T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND NOT EXISTS  (SELECT  'X'   FROM T4  WHERE T1.CNO = T4.CNO)

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     9716    347.98     346.35          2     666599          3        9715
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     9718    347.98     346.35          2     666599          3        9715


But, If I hardcode the values in procedure I get entirely different results i.e. when sdate = 
01-FEB-2005 and edate = 15-FEB-2005

SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= '01-FEB-2005'  AND T1.TDATE <= 
'15-FEB-2005'  AND   T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND NOT EXISTS  (SELECT  'X'   FROM T4  
WHERE T1.CNO = T4.CNO)


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.03          0          0          0           0
Execute      1      0.01       0.01          0          0          0           0
Fetch    62987     66.61      65.27          0    2898142          3       62986
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    62989     66.65      65.31          0    2898142          3       62986

In the above explanation, where 1000000 rows are inserted and I guess every time it has parsed the 
statement.  But in this case, we have to parse the statement only once. Can you please explain why 
so much of difference? 
 


Followup   April 5, 2005 - 12pm Central time zone:

I'm missing something here, are you saying "when we bind we get a different answer"?

if so, need to see the client code that is binding.

I would suggest a 10046 level 12 trace to VERIFY the binds. 

3 stars 10046 Trace   April 5, 2005 - 12pm Central time zone
Reviewer: Yogesh from Pune,India
I did used 10046 trace. when I'm creating my cursor with following query 

cursor c1 is 
SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= sdate  AND T1.TDATE <= edate  
AND   T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND NOT EXISTS  
(SELECT  'X'   FROM T4  WHERE T1.CNO = T4.CNO)

I got following in raw trace .... 

SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= :b1  AND T1.TDATE <= :b2  AND   
T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND NOT EXISTS  (SELECT  'X'   FROM T4  WHERE T1.CNO = T4.CNO)

when I used following to create cursor 

cursor c1 is 
SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= '01-FEB-2005'  AND T1.TDATE <= 
'15-FEB-2005'  AND   T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND NOT EXISTS  (SELECT  'X'   FROM T4  
WHERE T1.CNO = T4.CNO)

I got following in raw trace

SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= '01-FEB-2005'  AND T1.TDATE <= 
'15-FEB-2005'  AND   T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND NOT EXISTS  (SELECT  'X'   FROM T4  
WHERE T1.CNO = T4.CNO)

TKPROF for first approach 

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     9716    347.98     346.35          2     666599          3        9715
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     9718    347.98     346.35          2     666599          3        9715

for second approach 

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.03          0          0          0           0
Execute      1      0.01       0.01          0          0          0           0
Fetch    62987     66.61      65.27          0    2898142          3       62986
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    62989     66.65      65.31          0    2898142          3       62986

Sorry I was not very clear in last post

 


Followup   April 5, 2005 - 12pm Central time zone:

describe t1 for us.  most probably a "we compare dates to strings...."


ops$tkyte@ORA9IR2> create table t ( x varchar2(20) );
 
Table created.

ops$tkyte@ORA9IR2> insert into t values ( '20-feb-2005' );
 
1 row created.
 
ops$tkyte@ORA9IR2> insert into t values ( '20-feb-2004' );
 
1 row created.
 
ops$tkyte@ORA9IR2> select * from t where x between '01-feb-2005' and '28-feb-2005';
 
X
--------------------
20-feb-2005
20-feb-2004
 
ops$tkyte@ORA9IR2> select * from t where x between to_date('01-feb-2005') and 
to_date('28-feb-2005');
 
X
--------------------
20-feb-2005
 

3 stars Bind variable   April 5, 2005 - 12pm Central time zone
Reviewer: Yogesh 
I'm using oracle 8.0.4.  


3 stars   April 5, 2005 - 1pm Central time zone
Reviewer: Yogesh 
COLUMN_NAME    DATA_TYPE

CNO        NUMBER
ANO        NUMBER
ONO        CHAR
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
TDATE        DATE
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
TOTAL        NUMBER
TCODE        CHAR
CSNO        VARCHAR2


Column data type is DATE  


Followup   April 5, 2005 - 6pm Central time zone:

show us the binds from the trace file and are the plans different or the same.

 

3 stars Plans   April 6, 2005 - 9am Central time zone
Reviewer: Yogesh 
Procedure call (cursor with static values)

begin 
p1;
end;
/

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.03          0          0          0           0
Execute      1      0.01       0.01          0          0          0           0
Fetch    62987     66.61      65.27          0    2898142          3       62986
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    62989     66.65      65.31          0    2898142          3       62986

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 18  (USER1)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   FILTER
      0    NESTED LOOPS
      0     HASH JOIN
      0      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T2'
      0      PARTITION (CONCATENATED)
      0       TABLE ACCESS   GOAL: ANALYZED (BY LOCAL INDEX ROWID) OF 'T1'
      0        INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'T1_S1' (NON-UNIQUE)
      0     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'T3'
      0      INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'T3_PRIM' (UNIQUE)
      0    INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'T4_PRIM' (UNIQUE)


>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

Procedure call 

begin 
p1 ('01-FEB-2005','02-FEB-2005');
end;
/

Procedure p1 (startdate date, enddate date);


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     9709    354.48     529.72      20383     666578          3        9708
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     9711    354.48     529.72      20383     666578          3        9708

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 18  (USER1)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
  12941   FILTER
  12941    NESTED LOOPS
 109867     NESTED LOOPS
   1721      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T2'
      0      PARTITION (CONCATENATED)
 109867       TABLE ACCESS   GOAL: ANALYZED (BY LOCAL INDEX ROWID) OF 'T1'
81649721        INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'T1_S1' (NON-UNIQUE)
  18653     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'T3'
 109867      INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'T3_PRIM' (UNIQUE)
  12515    INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'T4_PRIM' (UNIQUE)

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

Procedure call 

begin 
p1 (to_date('01-FEB-2005'),to_date('02-FEB-2005'));
end;
/

Procedure p1 (startdate date, enddate date);



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     9709    347.97     348.00          0     666578          3        9708
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     9711    347.97     348.00          0     666578          3        9708

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 18  (USER1)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
  12941   FILTER
  12941    NESTED LOOPS
 109867     NESTED LOOPS
   1721      TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T2'
      0      PARTITION (CONCATENATED)
 109867       TABLE ACCESS   GOAL: ANALYZED (BY LOCAL INDEX ROWID) OF 'T1'
81649721        INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'T1_S1' (NON-UNIQUE)
  18653     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'T3'
 109867      INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'T3_PRIM' (UNIQUE)
  12515    INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'T4_PRIM' (UNIQUE)

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


binds from the trace file


SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= :b1  AND 
T1.TDATE <= :b2  AND   T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND NOT EXISTS  
(SELECT  'X'   FROM T4  WHERE T1.CNO = T4.CNO)


Are you looking for this or something else? 

 


Followup   April 6, 2005 - 1pm Central time zone:

let's see it in cut and paste, just plsql

alter session set sql_trace=true;
begin
   for x in ( select 1 .... )
   loop
     null;
    end loop;
    for x in ( select 2 .... )
    loop
      null;
    end loop;
end;
/
exit; 

3 stars   April 6, 2005 - 1pm Central time zone
Reviewer: Yogesh 
alter session set sql_trace=true;
BEGIN
   FOR x IN ( SELECT 1 FROM DUAL)
   LOOP
     NULL;
    END LOOP;
    FOR x IN ( SELECT 2 FROM DUAL  )
    LOOP
      NULL;
    END LOOP;
END;
/
exit; 

And paste trace file after that? 

I'm sorry, but I could not understand what exactly you want me to do....  


Followup   April 6, 2005 - 2pm Central time zone:

i want to see YOUR queries in there.

select 1
and 
select 2

your two queries, I want to *see* everything. 

3 stars   April 6, 2005 - 2pm Central time zone
Reviewer: Yogesh 
cursor c1 is 
SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= sdate  AND T1.TDATE <= edate  
AND   T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND NOT EXISTS  
(SELECT  'X'   FROM T4  WHERE T1.CNO = T4.CNO)


cursor c1 is 
SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= '01-FEB-2005'  AND T1.TDATE <= 
'15-FEB-2005'  AND   T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND 
NOT EXISTS  (SELECT  'X'   FROM T4  WHERE T1.CNO = T4.CNO)
 


Followup   April 6, 2005 - 2pm Central time zone:

please code the entire example as I asked... ok, here, just run this and paste the output AND the 
tkprof results with the plans:

desc t1
alter session set sql_trace=true;
set serveroutput on
decalre
   sdate date := to_date( '01-feb-2005', 'dd-mon-yyyy' );
   edate date := to_date( '15-feb-2005', 'dd-mon-yyyy' );
   cnt1 number := 0;
   cnt2 number := 0;
BEGIN
   FOR x IN ( SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= sdate  AND 
T1.TDATE <= edate  AND   T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND NOT EXISTS  
(SELECT  'X'   FROM T4  WHERE T1.CNO = T4.CNO))
   LOOP
     cnt1 := cnt1+1;
    END LOOP;
    FOR x IN ( SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= '01-FEB-2005' 
 AND T1.TDATE <= '15-FEB-2005'  AND   T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND 
NOT EXISTS  (SELECT  'X'   FROM T4  WHERE T1.CNO = T4.CNO) )
    LOOP
     cnt2 := cnt2+1;
    END LOOP;
    dbms_output.put_line( cnt1 || ', ' || cnt2 );
END;
/
exit;  

4 stars TKPROF OUTPUT   April 7, 2005 - 9am Central time zone
Reviewer: Yogesh 
DESC T1

Name           Null?        Type
------------- --------         ----
CNO         NOT NULL     NUMBER(38)
ANO         NOT NULL     NUMBER(3)
ONO         NOT NULL     CHAR(4)
TDATE       NOT NULL     DATE
TOTAL                    NUMBER(9,2)
TCODE       NOT NULL     CHAR(1)
DNO                      NUMBER(3)
CSNO                     VARCHAR2(8)

Starting First cursor 07-APR-2005 12:03:21
Finished First cursor 07-APR-2005 13:19:29
Finished Second cursor 07-APR-2005 13:22:56
62975, 62975

PL/SQL procedure successfully completed.

Elapsed: 01:19:35.36

tkprof ora_199326_USER1.trc ora_199326_USER1.out explain=/ sys=no 

TKPROF: Release 8.0.4.3.0 - Production on Thu Apr 7 13:24:35 2005

(c) Copyright 1997 Oracle Corporation.  All rights reserved.

Trace file: ora_199326_USER1.trc
Sort options: default

********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing 
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

alter session set sql_trace=true


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        1      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 18  (USER1)
********************************************************************************

alter session set timed_statistics =true


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        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           0

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 18  (USER1)
********************************************************************************

alter session set max_dump_file_size = unlimited


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        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 18  (USER1)
********************************************************************************

begin dbms_output.enable(2000); 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          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.00          0          0          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 18  (USER1)
********************************************************************************

declare
   sdate date := to_date( '01-feb-2005', 'dd-mon-yyyy' );
   edate date := to_date( '15-feb-2005', 'dd-mon-yyyy' );
   cnt1 number := 0;
   cnt2 number := 0;
BEGIN
   
dbms_output.put_line( 'Starting First cursor ' || To_CHAR(SYSDATE,'DD-MON-YYYY HH24:MI:SS') );
   
FOR x IN 
(
SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= sdate  AND T1.TDATE <= edate  
AND   T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND NOT EXISTS  
(SELECT  'X'   FROM T4  WHERE T1.CNO = T4.CNO)
)

LOOP
        cnt1 := cnt1+1;
END LOOP;
   
dbms_output.put_line( 'Finished First cursor ' || To_CHAR(SYSDATE,'DD-MON-YYYY HH24:MI:SS') );
    
FOR x IN 
(
SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= '01-FEB-2005'AND T1.TDATE <= 
'15-FEB-2005'  AND   T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND NOT EXISTS  (SELECT  'X'   FROM T4  
WHERE T1.CNO = T4.CNO)
)

LOOP
        cnt2 := cnt2+1;
END LOOP;
    
dbms_output.put_line( 'Finished Second cursor ' || To_CHAR(SYSDATE,'DD-MON-YYYY HH24:MI:SS') );
    dbms_output.put_line( cnt1 || ', ' || cnt2 );
END;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1     19.62      22.40          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     19.63      22.41          0          0          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 18  (USER1)
********************************************************************************

SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= :b1  AND T1.TDATE <= :b2  AND   
T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND NOT EXISTS  (SELECT  'X'   FROM T4  WHERE T1.CNO = T4.CNO)

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    62976   4263.14    4556.93      33720    6313882          3       62975
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    62978   4263.14    4556.93      33720    6313882          3       62975

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 18  (USER1)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   FILTER
      0    NESTED LOOPS
      0     NESTED LOOPS
      0      TABLE ACCESS (FULL) OF 'T2'
      0      PARTITION (CONCATENATED)
      0       TABLE ACCESS   GOAL: ANALYZED (BY LOCAL INDEX ROWID) OF 'T1'
      0        INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'T1_S1' (NON-UNIQUE)
      0     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'T3'
      0      INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'T3_PRIM' (UNIQUE)
      0    INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'T4_PRIM' (UNIQUE)

********************************************************************************

SELECT T1.* ,TRUNC(T3.IDATE,'mon') FROM T1, T3   WHERE T1.TDATE >= '01-FEB-2005' AND T1.TDATE <= 
'15-FEB-2005'  AND   T1.CNO = T3.CNO AND T3.PTYPE != 'IP' AND NOT EXISTS  (SELECT  'X'   FROM T4  
WHERE T1.CNO = T4.CNO)


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.03          0          0          0           0
Execute      1      0.01       0.01          0          0          0           0
Fetch    62976     73.38     195.96      14578    2898109          3       62975
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    62978     73.42     196.00      14578    2898109          3       62975

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 18  (USER1)   (recursive depth: 1)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      0   FILTER
      0    NESTED LOOPS
      0     HASH JOIN
      0      TABLE ACCESS (FULL) OF 'T2'
      0      PARTITION (CONCATENATED)
      0       TABLE ACCESS   GOAL: ANALYZED (BY LOCAL INDEX ROWID) OF 'T1'
      0        INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'T1_S1' (NON-UNIQUE)
      0     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'T3'
      0      INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'T3_PRIM' (UNIQUE)
      0    INDEX   GOAL: ANALYZED (RANGE SCAN) OF 'T4_PRIM' (UNIQUE)

********************************************************************************

begin dbms_output.get_lines(:lines, :numlines); end;


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           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          0          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 18  (USER1)



********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        5      0.01       0.01          0          0          0           0
Execute      6     19.63      22.40          0          0          0           3
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11     19.64      22.41          0          0          0           3

Misses in library cache during parse: 1


OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.03       0.03          0          0          0           0
Execute      2      0.01       0.01          0          0          0           0
Fetch   125952   4336.52    4752.89      48298    9211991          6      125950
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   125956   4336.56    4752.93      48298    9211991          6      125950

Misses in library cache during parse: 1

    8  user  SQL statements in session.
    0  internal SQL statements in session.
    8  SQL statements in session.
    2  statements EXPLAINed in this session.
********************************************************************************
Trace file: ora_199326_USER1.trc
Trace file compatibility: 7.03.02
Sort options: default

       1  session in tracefile.
       8  user  SQL statements in trace file.
       0  internal SQL statements in trace file.
       8  SQL statements in trace file.
       8  unique SQL statements in trace file.
       2  SQL statements EXPLAINed using schema:
           USER1.prof$plan_table
             Default table was used.
             Table was created.
             Table was dropped.
  126067  lines in trace file.
 


Followup   April 7, 2005 - 11am Central time zone:

well, you didn't type "exit"

and you used explain=

I want the Row Source, those two are using *different plans* -- not explain=

I know 8.0.6 had this, if you run stuff in plus, then EXIT, then run tkprof, it'll show you the Row 
Source Operation with the *real* plan not an explain plan which "lies" sometimes.  It'll show the 
rows and all.




but why did they get the *same answer* this time??  I thought this was all about "wrong data"? 

4 stars Raw output after EXIT   April 7, 2005 - 1pm Central time zone
Reviewer: Yogesh 
When I was trying to figure out the delay, I used date range of 2 days in first query. ThatÂ’s why 
number of rows was different.


Final Output, after EXIT  

Starting First cursor07-APR-2005 16:00:54
Finished First cursor07-APR-2005 17:30:29
Finished Second cursor07-APR-2005 17:33:52
62975, 62975


Cursor with sdate and edate

1.    STAT #2 id=1 cnt=85216 pid=0 pos=0 obj=0 op='FILTER '
2.    STAT #2 id=2 cnt=85216 pid=1 pos=1 obj=0 op='NESTED LOOPS '
3.    STAT #2 id=3 cnt=790359 pid=2 pos=1 obj=0 op='NESTED LOOPS '
4.    STAT #2 id=4 cnt=1721 pid=3 pos=1 obj=256190 op='TABLE ACCESS FULL T2 '
5.    STAT #2 id=5 cnt=0 pid=3 pos=2 obj=0 op='PARTITION CONCATENATED '
6.    STAT #2 id=6 cnt=790359 pid=5 pos=1 obj=36035 op='TABLE ACCESS BY LOCAL INDEX ROWID T1 '
7.    STAT #2 id=7 cnt=1088718395 pid=6 pos=1 obj=251242 op='INDEX RANGE SCAN '
8.    STAT #2 id=8 cnt=122668 pid=2 pos=2 obj=2086 op='TABLE ACCESS BY INDEX ROWID T3 '
9.    STAT #2 id=9 cnt=790359 pid=8 pos=1 obj=198949 op='INDEX UNIQUE SCAN '
10.    STAT #2 id=10 cnt=82197 pid=1 pos=2 obj=224461 op='INDEX RANGE SCAN '


Cursor with static values

11.    STAT #3 id=1 cnt=85216 pid=0 pos=0 obj=0 op='FILTER '
12.    STAT #3 id=2 cnt=85216 pid=1 pos=1 obj=0 op='NESTED LOOPS '
13.    STAT #3 id=3 cnt=1331885 pid=2 pos=1 obj=0 op='HASH JOIN '
14.    STAT #3 id=4 cnt=1721 pid=3 pos=1 obj=256190 op='TABLE ACCESS FULL T2 '
15.    STAT #3 id=5 cnt=0 pid=3 pos=2 obj=0 op='PARTITION CONCATENATED '
16.    STAT #3 id=6 cnt=840567 pid=5 pos=1 obj=36035 op='TABLE ACCESS BY LOCAL INDEX ROWID T1 '
17.    STAT #3 id=7 cnt=840568 pid=6 pos=1 obj=251242 op='INDEX RANGE SCAN '
18.    STAT #3 id=8 cnt=122668 pid=2 pos=2 obj=2086 op='TABLE ACCESS BY INDEX ROWID T3 '
19.    STAT #3 id=9 cnt=790359 pid=8 pos=1 obj=198949 op='INDEX UNIQUE SCAN '
20.    STAT #3 id=10 cnt=82212 pid=1 pos=2 obj=224461 op='INDEX RANGE SCAN '


Differences in plan 


3.    STAT #2 id=3 cnt=790359 pid=2 pos=1 obj=0 op='NESTED LOOPS '
13.    STAT #3 id=3 cnt=1331885 pid=2 pos=1 obj=0 op='HASH JOIN '


7.    STAT #2 id=7 cnt=1088718395 pid=6 pos=1 obj=251242 op='INDEX RANGE SCAN '
17.    STAT #3 id=7 cnt=840568 pid=6 pos=1 obj=251242 op='INDEX RANGE SCAN '


 


Followup   April 7, 2005 - 1pm Central time zone:

the cbo is using a different plan when you use binds, it has less information.

What this means is -- in generaly the plan with the binds is "best"

but if you know the dates should "always be close together, the amount of data small" and since you 
have 8.0 without bind variable peeking (so the optimizer is "blind"), you might have to hint (I'd 
try first_rows)

(please -- in the future, apples and apples, I seriously thought the issue was "binds return X and 
constants return Y" that is where I was going with the date/string thing.  you were comparing the 
incomparable!) 

4 stars 8.0 optimizer   April 8, 2005 - 4am Central time zone
Reviewer: Yogesh 
"Since you have 8.0 without bind variable peeking"

So, can we call it as limitation of optimizer in 8.0? 

In which version onwards this issue is taken care by optimizer?

"in the future, apples and apples", are you referring to following? 

begin 
p1 ('01-FEB-2005','02-FEB-2005');
end;
 


Followup   April 8, 2005 - 7am Central time zone:

in 9i the database peeks at bind variables.  search this site for "bind variable peeking"  to read 
about it.


I was referring to the fact that you had us looking at:

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     9716    347.98     346.35          2     666599          3        9715
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     9718    347.98     346.35          2     666599          3        9715


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.03          0          0          0           0
Execute      1      0.01       0.01          0          0          0           0
Fetch    62987     66.61      65.27          0    2898142          3       62986
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    62989     66.65      65.31          0    2898142          3       62986


trying to figure out "what was wrong".  It was very confusing to see outputs with different row 
counts.  I thought we were looking at "when I bind I get answer 1, when I don't bind I get answer 
2" 

4 stars FIRST_ROWS   April 8, 2005 - 8am Central time zone
Reviewer: Yogesh 
I tried  /*+ first_rows*/ .. But no difference. Query took 1.17 hrs for execution.  Plan remain 
same (one with bind variables) . 

What is the solution for this problem? 
Upgrade to new version ? or be happy with the current performace?  


 


Followup   April 8, 2005 - 8am Central time zone:

with such old software, hint it I guess.  You seem happier with the NESTED LOOPS plan, so hint the 
bound query to use the indexe/nested loop path you desire.

I would be excessively happy if you upgraded, based on many questions you've had in the past, I 
think you would be excessively happy as well.

And after you do, remove the hints. 

4 stars USE_HASH   April 8, 2005 - 9am Central time zone
Reviewer: Yogesh 
I totally agree with you ... I'm using 10 year old technology ... 

One more question to add.... following plan looks good to me 

11.    STAT #3 id=1 cnt=85216 pid=0 pos=0 obj=0 op='FILTER '
12.    STAT #3 id=2 cnt=85216 pid=1 pos=1 obj=0 op='NESTED LOOPS '
13.    STAT #3 id=3 cnt=1331885 pid=2 pos=1 obj=0 op='HASH JOIN '
14.    STAT #3 id=4 cnt=1721 pid=3 pos=1 obj=256190 op='TABLE ACCESS FULL T2 '
15.    STAT #3 id=5 cnt=0 pid=3 pos=2 obj=0 op='PARTITION CONCATENATED '
16.    STAT #3 id=6 cnt=840567 pid=5 pos=1 obj=36035 op='TABLE ACCESS BY LOCAL 
INDEX ROWID T1 '
17.    STAT #3 id=7 cnt=840568 pid=6 pos=1 obj=251242 op='INDEX RANGE SCAN '
18.    STAT #3 id=8 cnt=122668 pid=2 pos=2 obj=2086 op='TABLE ACCESS BY INDEX 
ROWID T3 '
19.    STAT #3 id=9 cnt=790359 pid=8 pos=1 obj=198949 op='INDEX UNIQUE SCAN '
20.    STAT #3 id=10 cnt=82212 pid=1 pos=2 obj=224461 op='INDEX RANGE SCAN '

But if you see line 13, where it is using the hash join, obj=0. How do I determine which table I 
should be using with USE_HASH hint? 


Followup   April 8, 2005 - 9am Central time zone:

it is a hash join of the two tables 

the plan should be readable as normal in the tkprof. 

5 stars bind variable peeking question   February 5, 2006 - 1am Central time zone
Reviewer: Menon 
Are there situations (discarding the bugs such as the one mentioned at 
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:19398056075583#48426602526853

when bind variable peeking does not happen? Or for all bind variables - the very first time, the 
peeking happens and the plan is the result of the actual value in the bind variables?

Thanx! 


Followup   February 6, 2006 - 12am Central time zone:

yes, functions for example can really "hide" bind variable peeking in a fashion.

depends on the release how far into a function (builtin function) the optimizer can  peek. 

5 stars interesting...   February 6, 2006 - 12pm Central time zone
Reviewer: Menon 
"yes, functions for example can really "hide" bind variable peeking in a fashion.

depends on the release how far into a function (builtin function) the optimizer 
can  peek. "

Are there any other cases (apart from when we use functions) like this? By above I presume you mean 
when we use functions in where clause (e.g. dt > trunc(sysdate))...

It seems to me that if your query uses normal columns (not vitual ones), then a bind variable 
peeking would always be done during hard parse...So, perhaps we can state that in a typical 
database system  (OLTP or D/W), most of the hard parses would undergo bind variable peeking... Is 
there any article you are aware of that discusses this issue in detail? 


Followup   February 7, 2006 - 1am Central time zone:

yes, any sort of function...

We are in the process of writing such an article "right here" :)

run this in 9ir2 and 10gr2:

drop table t;

create table t
as
select 99 id, a.* from all_objects a;
update t set id = 1 where rownum = 1;
create index  t_idx on t(id);

begin
    dbms_stats.gather_table_stats
    ( user, 'T',
      method_opt => 'for all indexed columns size 254',
      cascade => true );
end;
/

variable str varchar2(25)
variable num number

exec :str := '1'; :num := 1;
alter session set sql_trace=true;
select * from t where id = to_number(:str);
select * from t where id = :num;



9i tkprof:

select * from t where id = to_number(:str)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL T
********************************************************************************
select * from t where id = :num

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID OBJ#(33502)
      1   INDEX RANGE SCAN OBJ#(33503) (object id 33503)


10g tkprof:


select * from t where id = to_number(:str)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T (cr=4 pr=0 pw=0 time=90 us)
      1   INDEX RANGE SCAN T_IDX (cr=3 pr=0 pw=0 time=69 us)(object id 56200)
********************************************************************************
select * from t where id = :num

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T (cr=4 pr=0 pw=0 time=89 us)
      1   INDEX RANGE SCAN T_IDX (cr=3 pr=0 pw=0 time=66 us)(object id 56200)
 

5 stars a paper on CBO   February 6, 2006 - 2pm Central time zone
Reviewer: Menon 
http://www.oracle.com/technology/products/bi/db/10g/pdf/twp_general_cbo_migration_10gr2_0405.pdf
contains some info on when bind variable peeking happens and when it does not take place. 


5 stars when does bind variable peeking occur?   February 6, 2006 - 7pm Central time zone
Reviewer: Menon 
The above url pdf tells us when bind variable peeking occurs:

"There are two cases where the optimizer would peek at the actual bindings of a bind variable and 
where the actual bindings therefore could make a difference for what plan would get generated.
Range predicates. Example:
sales_date between :1 and :2 and price > :3.
Equality predicates when the column has histograms. Example:
order_status = :4
assuming that order_status has histograms.
In contrast, a condition like order_id = :1 will not trigger bind peeking assuming that order_id 
does not have histograms. (It may, for instance, be a primary key column, in which case histograms 
are not beneficial.)"

So, there could be lots of queries actually, where bind variable peeking is not done. 

Also at 
http://www.oaktable.net/getChallenge.jsp;jsessionid=F8889A54FBA1FC1F0DB2DBCEC0E3EBDD?id=66,
I found the following 
question:

Q: 9.2 does bind variable peeking.  Is there a hint to force the optimizer to peek or not peek?

Answer: No hint to stop it, but there is the hint CURSOR_SHARING_EXACT which may have some effect. 
It's expected use is to stop literals being converted to bind variables Note - peeking seems to 
occur only on the first parse; after which the path is fixed - UNLESS some column in the WHERE 
clause has a histogram defined on it; in which case the statement is hard parsed on every run

The last part about a statement always getting hard parsed in case of a column containing 
histograms seems to be inaccurate? 


Followup   February 7, 2006 - 1am Central time zone:

that answer is wrong.  histograms do not cause a hard parse every time, cursor sharing exact would 
not really come into play.

I'll email Mogens about this... 

5 stars thanx for the example...   February 7, 2006 - 11am Central time zone
Reviewer: Menon 
"yes, any sort of function...

We are in the process of writing such an article "right here" :)"...

Thanx!:-)
I believed you when you talked about functions - it did make sense. But thanx for the proof and the 
"just-in-time" article!;) 

What I am I want to know is how common is the situation where bind variable peeking occurs. I am 
getting somewhat contradictory (to me) messages:

1. You point out the "function" example, which, one can argue, is not that common in queries -> 
meaning bind variable peeking is the norm).
2. Jonathan also points out in his book that bind variable peeking is almost always the case (dont 
have the book with me right now to quote)
3. From the paper that I posted above(official Oracle paper;), I get the message that there are 
only two cases where bind variable peeking DOES occur. 
1. range predicates
2. predicates, where you have equality on a column that has histograms.

The above seems to suggest that bind variable peeking is in fact not very common?

Just wanted to know what your thoughts (or anyone else who cares to comment) are...
 


Followup   February 8, 2006 - 1am Central time zone:

the answer is as always "it depends"

if you don't use functions - very common
if you always use functions - not as common (varies by version in fact)


but, taking the official paper in mind, chew on this:

drop table t;
create table t
as
select 'x' str, a.*
  from all_objects a;
update t set str='a' where rownum = 1;
create index t_idx on t(str);
exec dbms_stats.gather_table_stats( user, 'T', method_opt=>'for all indexed columns size 254', 
cascade=>true );
variable s varchar2(5)
alter session set sql_trace=true;
set autotrace traceonly statistics
exec :s := 'a%';
select * from t parse_with_a where str like :s;
exec :s := 'x%';
select * from t parse_with_x where str like :s;
set autotrace off




select * from t parse_with_a where str like :s

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID T (cr=4 pr=0 pw=0 time=73 us)
      1   INDEX RANGE SCAN T_IDX (cr=3 pr=0 pw=0 time=53 us)(object id 56237)
********************************************************************************
select * from t parse_with_x where str like :s

Rows     Row Source Operation
-------  ---------------------------------------------------
  49792  TABLE ACCESS FULL T (cr=3973 pr=0 pw=0 time=249145 us)



is LIKE equality or range?

:) 

5 stars The answer from Mogens   February 7, 2006 - 11am Central time zone
Reviewer: Menon 
"that answer is wrong.  histograms do not cause a hard parse every time, cursor 
sharing exact would not really come into play.

I'll email Mogens about this... "

Perhaps he meant cursor sharing "similar" which, I understand, could result in many queries getting 
hard parsed (still not always) 


5 stars hmmm   February 8, 2006 - 12pm Central time zone
Reviewer: Menon 
"is LIKE equality or range?"...

I think Oracle treats like as "range" - so this does not really give a counter example to the 
official doc. 


Followup   February 9, 2006 - 4am Central time zone:

but it is not a range, it is a like...

so, what operators are not equality or range then.  

use 10g, turn on 10053 trace - it says when bind peeking is or is not done.  


I would say "like is not a range" personally, I think of range as - well, a range.

is "where x like 'A%_bc_X%'  a "range".  Sure, it can use an index in a range scan - but so can 
equality.  So, why say equality, why not just say range....

 

5 stars   February 27, 2006 - 1am Central time zone
Reviewer: Su Baba from Ca, USA
I downloaded your AllAboutBinds.ppt. I'd like to educate developers on the virtues of using bind 
variables. Is it ok if I use a portion of your Powerpoint presentation to do my presentation?  
Thanks. 


Followup   February 27, 2006 - 7am Central time zone:

absolutely, I only post them so you can do that. 

5 stars How to check current values?   March 21, 2006 - 6am Central time zone
Reviewer: Michal from Poland
Hi,

I can't find anywhere how to check values that users assigned to bind variables when they were 
executing their queries. I would like to make some statistics on what values are most common for a 
given query (to help me understand how users are using the system/data, not due to any business 
requirements). Unfortunately there is no debug code to do that in the application/stored procedures 
and any change to the code takes time to be accepted by the customer, so I would much prefer to do 
that on the DBA level. Any hints how to do that?

Thanks a lot, 


Followup   March 22, 2006 - 1pm Central time zone:

you can turn on tracing and it'll be in the trace files (using dbms_system.set_ev and the 10046 
level 12 trace

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:330817260752#15167181082646
)  

in 10g, you'll have v$sql_bind_capture 

5 stars Great   March 23, 2006 - 8am Central time zone
Reviewer: Michal from Poland
I wish it was so easy in 9i as it is in 10g but at least I know how to do that. Thanks! 


4 stars Have read many useful things here about peeking on bind variables...   April 26, 2006 - 5am Central time zone
Reviewer: Dmytro from Kyiv, Ukraine
...still one more question - is bind variable peeking still applicable for table-type variables, 
for example:
SELECT det_id
  BULK COLLECT INTO g_details                       
  FROM details
 WHERE det_numb IN (SELECT *
                      FROM TABLE(CAST(l_det_accessible AS tt_strings)));

l_det_accessible and g_details here are table-type variables and the first of them, will have 
around 0-50 records (details table has nearly 300,000 records). So, will Oracle count number of 
rows in this table-type variable and create a plan based on this value (using index) or just use 
some constant like 8168 and do a FTS? 


Followup   April 26, 2006 - 8am Central time zone:

it won't peek, it uses default sizes (8168 rows on an 8k block size database) for that.

http://www.oracle.com/technology/oramag/oracle/04-sep/o54asktom.html
see query plans with temporary tables... 

5 stars Search in AskTom   April 26, 2006 - 11am Central time zone
Reviewer: An Oracle Fan 
Hi Tom,
Your responses on this page are excellent as usual. A bit of a problem:
I wanted to see all posts regarding use of bind variables in a VC++ MFC application that queries an 
Oracle database. I typed VC++ in the search box. The search ended up querying {VC} with 750 
results. I tried enclosing VC++ in double quotes. It did search {VC++} this time, but I got same(?) 
750 results. The top four entries (score 100) were sans the search term.
Quirks of Oracle Text?
 


5 stars Specifying Bind Variables in Partitions   April 27, 2006 - 10am Central time zone
Reviewer: Steve Booth from Pewaukee, WI USA
I can see where using Bind Variables could be very helpful. However, when I'm working with SQL on a 
Partitioned Table, I've found that specifying the partition where the data resides results in a 
much faster query execution. For instance:

SELECT * 
  FROM T_Data PARTITION(D_20060102)
 WHERE MyDate = TO_DATE(:1, 'YYYYMMDD')
   AND Fld2 = :2;

:1 = 20060102
:2 = Whatever

Because of the specification of the partition name, wouldn't each SQL statement be considered a 
separate statement having to be "Hard-Parsed"? 

Thoughts and/or suggestions?

Thanks in Advance.

 


Followup   April 27, 2006 - 3pm Central time zone:

it would be (a hard parse) for each unique sql.

however, you give no clues here even about what you partitioned by.

I can only guess:

- you have data that is skewed very differently in each partition
- the access path that works for partition 1 is not what you like for partition 2
- you need a different plan for each partition

I would almost rather NOT use a bind for the partition key, rather than name the partition - that 
would achieve the same goal.

Which in your case is "a plan possible for each partition, using the local partition stats". 

5 stars   May 1, 2006 - 3pm Central time zone
Reviewer: Richard Z. Tan from Fairfax, Virginia
Hi Tom,

Thank you very much for your help.

Due to text template with multiple bind variables in there,

I try to use bind variable like the following,

Select ‘abc…’||:x||’def…’||:y||’ghi…’||:z||’jkl…’ from dual;

If I use double pipe in various forms, it does not work at all, but if I use concat() function to 
replace all double pipe, it works perfectly, but the template looks cumbersome if there are many 
bind variables in one single template.

Is there a very clean way to define a series of bind variables in the sql select statement, not sql 
criteria portion?

Thanks for help.

Richard Tan 


Followup   May 2, 2006 - 3am Central time zone:

if you use "double pipe" and it does not work then the only conclusion I can come to is....

You didn't use double pipes correctly yet.


whether the binds are in the select or the where or whatever - doesn't matter, it is all the same 
to us.



ops$tkyte@ORA10GR2> variable a varchar2(10)
ops$tkyte@ORA10GR2> variable b varchar2(10)
ops$tkyte@ORA10GR2> variable c varchar2(10)
ops$tkyte@ORA10GR2> exec :a := 'Hello'; :b := 'World'; :c := 'ABC'

PL/SQL procedure successfully completed.

ops$tkyte@ORA10GR2> select '---' || :a || '---' || :b || '---' || :c from dual;

'---'||:A||'---'||:B||'---'||:C
-------------------------------------------------------------------------------
---Hello---World---ABC

 

4 stars Bind Variables with Partition Names   May 2, 2006 - 12pm Central time zone
Reviewer: Steve Booth from Pewaukee, WI USA
Thanks for the response. The data is, as the partition name implies, partitioned by date. Each 
partition is a single date. This makes for the removal of obsolete data pretty easy, we just drop 
the partition and the local indices go away as well. 

The data itself is very consistent on a partition by partition basis. In this application, 
approximately 75 SQL statements are used to perform all of the calculations necessary in order to 
determine the correct values for a single day.

Yet another ignorant question: Since we have to go through a "Hard Parse" process for the Partition 
specification, would it still make sense to use bind variables?

Thanks again.

  


Followup   May 2, 2006 - 4pm Central time zone:

the last question doesn't make "sense" - if you use binds - that means "no hard parse".  If you 
don't it does.

can you give me an example of where using binds is not good and using the partition name is? (back 
to the original issue here) 

4 stars Specifying Partitions and Bind Variables   May 3, 2006 - 1pm Central time zone
Reviewer: Steve Booth from Pewaukee, WI USA
I'm sorry that I haven't been clear. Let me try to explain:

By using this SQL

SELECT * 
  FROM T_Data PARTITION(D_20060102)
 WHERE MyDate = TO_DATE('20060102', 'YYYYMMDD')
   AND Fld2 = 1234;

I get much better responsiveness because I'm starting out by restricting the processing to look 
only in the "D_20060102" Partition. In this table, each partition contains a single day of data. 
The SQL variable that changes on this statement is the date. Sometimes I need: 20060102, sometimes 
I need: 20051231 etc.

BUT because I am specifying this value as part of the Partition Name, my SQL statement is going to 
be "Hard-Parsed" each time it is called.

If I specify the SQL as:

SELECT * 
  FROM T_Data 
 WHERE MyDate = TO_DATE(:1, 'YYYYMMDD')
   AND Fld2 = 1234;

:1 = '20060102'

I would avoid the "Hard Parse" step, but the resultant response time isn't as good. Several years 
ago on an 8i system, by specifying the "PARTITION()" the data was returned in about 1/4 of the time 
(elapsed). The actual SQL that was being used was (obviously) much more complicated. This SQL was 
meant to demonstrate the point (and failed). There are approximately 45,000 data records per day 
and 500 days in the table.

The followup question that I was attempting to ask is that since I have to specify the PARTITION 
after the table name in any case, would it make still make sense to use bind variables?

Once again, I hope that I've clarified what I meant.

Thanks again for your time. 


Followup   May 3, 2006 - 2pm Central time zone:

IF myDate is the partition key
THEN
   WHERE MyDate = TO_DATE(:1, 'YYYYMMDD') also restricts the processing to look at 
   a SINGLE PARTITION
END IF.


I do not believe you need to specify the partition name, I would like to see a demonstrable case - 
just use tkprof for us and show us 

5 stars About your follow-up answer   May 4, 2006 - 3pm Central time zone
Reviewer: Richard Tan from Fairfax, Virginia, U.S.A.
Hi Tom,

Thank you very much for your follow-up a couple of blocks up.
All templates are stored in the database, so I have to select and use dbms_sql, number of bind 
variables are not given before runtime, I use your example, please see anything wrong.

declare
v_html_text varchar2(4000) := '---' || :a || '---' || :b || '---' || :c ;
v_cursor number;
v_status number;
v_html_text_output varchar2(4000);
begin
   v_html_text := 'select '||v_html_text ||' from dual';
   v_cursor := dbms_sql.open_cursor;
   dbms_sql.parse(c             => v_cursor,
                  statement     => v_html_text,
                  language_flag => dbms_sql.native);
   dbms_sql.bind_variable(c              => v_cursor,
                          name           => ':a',
                          value          => 'aaa',
                          out_value_size => 4000);
   dbms_sql.bind_variable(c              => v_cursor,
                          name           => ':b',
                          value          => 'bbb',
                          out_value_size => 4000);
   dbms_sql.bind_variable(c              => v_cursor,
                          name           => ':c',
                          value          => 'ccc',
                          out_value_size => 4000);
   dbms_sql.define_column(c           => v_cursor,
                          position    => 1,
                          column      => v_html_text_output,
                          column_size => 4000);
   v_status := dbms_sql.execute(c => v_cursor);
   while (dbms_sql.fetch_rows(c => v_cursor) > 0)
   loop
      dbms_sql.column_value(c        => v_cursor,
                            position => 1,
                            value    => v_html_text_output);
   end loop;
   dbms_sql.close_cursor(c => v_cursor);
   dbms_output.put_line('v_html_text_output = '|| substr(v_html_text_output,1,250));
   dbms_output.put_line('v_html_text_output = '|| substr(v_html_text_output,251,500));
   dbms_output.put_line('v_html_text_output = '|| substr(v_html_text_output,501,750));
   dbms_output.put_line('v_html_text_output = '|| substr(v_html_text_output,751,1000));
end;
/

Thank you very much for help. 


Followup   May 4, 2006 - 5pm Central time zone:

v_html_text varchar2(4000) := '---' || :a || '---' || :b || '---' || :c ;

that line does not make sense.  

why are :a, :b, and :c not IN THE STRING????

ops$tkyte@ORA9IR2> declare
  2  v_html_text varchar2(4000) := ' ''---'' ||  :a || ''---'' || :b || ''---'' || :c' ;
  3  v_cursor number;
  4  v_status number;
  5  v_html_text_output varchar2(4000);
  6  begin
  7     v_html_text := 'select '||v_html_text ||' from dual';
  8     v_cursor := dbms_sql.open_cursor;
  9     dbms_sql.parse(c             => v_cursor,
 10                    statement     => v_html_text,
 11                    language_flag => dbms_sql.native);
 12     dbms_sql.bind_variable(c              => v_cursor,
 13                            name           => ':a',
 14                            value          => 'aaa',
 15                            out_value_size => 4000);
 16     dbms_sql.bind_variable(c              => v_cursor,
 17                            name           => ':b',
 18                            value          => 'bbb',
 19                            out_value_size => 4000);
 20     dbms_sql.bind_variable(c              => v_cursor,
 21                            name           => ':c',
 22                            value          => 'ccc',
 23                            out_value_size => 4000);
 24     dbms_sql.define_column(c           => v_cursor,
 25                            position    => 1,
 26                            column      => v_html_text_output,
 27                            column_size => 4000);
 28     v_status := dbms_sql.execute(c => v_cursor);
 29     while (dbms_sql.fetch_rows(c => v_cursor) > 0)
 30     loop
 31        dbms_sql.column_value(c        => v_cursor,
 32                              position => 1,
 33                              value    => v_html_text_output);
 34     end loop;
 35     dbms_sql.close_cursor(c => v_cursor);
 36     dbms_output.put_line('v_html_text_output = '||
 37  substr(v_html_text_output,1,250));
 38     dbms_output.put_line('v_html_text_output = '||
 39  substr(v_html_text_output,251,500));
 40     dbms_output.put_line('v_html_text_output = '||
 41  substr(v_html_text_output,501,750));
 42     dbms_output.put_line('v_html_text_output = '||
 43  substr(v_html_text_output,751,1000));
 44  end;
 45  /
v_html_text_output = ---aaa---bbb---ccc
v_html_text_output =
v_html_text_output =
v_html_text_output =

PL/SQL procedure successfully completed.

 

4 stars Difference between Bind Variable and Reference variable   June 27, 2006 - 2pm Central time zone
Reviewer: Mahmood Lebbai from Bothell,WA USA
Tom,

Plan A:

Variable yr1 number;
Insert into t values(:yr1);
exec :yr1:=2006;

Plan B:

Insert into t values (&yr1);

I understand, Plan A would result in Soft parse (as you mentioned in your Oracle Expert one-on-one) 
but can I use the Plan B? Will that guarantee a soft parse? If it is not, then what is the 
difference between them?

Thanks.
 


Followup   June 27, 2006 - 2pm Central time zone:

&yr1 is a substitution variable, processed by the client.  It results in the sql statment:

insert into t values ( 2006 )

being submited to the database - use binds. 

5 stars Bind variables   July 18, 2006 - 8am Central time zone
Reviewer: a reader 
I have gone through this thread, just wanted to confirm
my understandings.
I am using PL/SQL in an OLTP system.

1)
Which SQL is better ?
SELECT c1 FROM t1 WHERE c1='A' and c2=:var2
OR
SELECT c1 FROM t1 WHERE c1= :var1 and c2=:var2

2)
Which SQL is better
SELECT c1 FROM t2 WHERE C2 IN ('A','B')
OR
SELECT c1 FROM t2 WHERE C2 IN (:VAR1,:VAR2)


Thanks

 


Followup   July 18, 2006 - 8am Central time zone:

1) if the sql is always "c1='A'", then "c1='A'" is the only choice you want to consider.  You bind 
only that which VARIES from sql statement execution to execution.

2) see #1, if 'A', 'B' are ALWAYS 'A', 'B', there is quite simply no point in binding and reasons 
for NOT binding them

reason to not bind if 'a','b' are constant:  they are constant, they do not change, it has meaning 
to have 'a','b' in the sql text, it is obvious they are constant.  next person to read the code 
will SEE 'a','b' and say "ah, they are constant"

the optimizer gets to see 'a', 'b' and can make use of that fact (that they are 'a', 'b') when 
optimizing the query. 

5 stars fine, but I was thinknig it from a different angle   July 19, 2006 - 4am Central time zone
Reviewer: a reader 
Ok, fine, understood,

But my point of view was with respect to "good develpment practice/principles".

Consider a large project where several developers
are writing queries without know what the other developer
is writing. For e.g. 
Developer A writes SELECT * FROM t1 WHERE c1 = 'A'
Developer B writes SELECT * FROM t1 WHERE c1 = 'B'
Developer C writes SELECT * FROM t1 WHERE c1 = 'C'

Now each developer's query will have a hard parse.
Yes C='A' is constant, but its constant with respect to
that indivisual developer's code only not, with respect to the whole project code. But if all 
developers follow a simple rule to use bind variables everywhere, we will
have less hard parses.

Do you agree to make it a standard rule for the whole
project?


 


Followup   July 19, 2006 - 9am Central time zone:

I doubt that would happen and it would be limited by the number of developers hence I sort of doubt 
it would become a problem.

Reason also - 

developer a writes 

select * from t1 where c1 = :x

developer b writes 

Select *
  From t1
 Where c1 = :x;

developer C writes

select * from t1 where t1.c1 = :x;

developer D writes

SELECT A, B, C, D
  FROM T1
 WHERE C1 = :X;


they are all using binds, they are all different sql.


No, OVERBINDING is bad - bind only that which varies. 

5 stars How over binding is bad   July 19, 2006 - 9am Central time zone
Reviewer: a reader 
Ok, the SQLs are all using binds, they are actualy same
but different for the parser, ok? Can this problem (being different for parser) be 
avoided/minimized by following
some coding conventions/standards? Can you suggest some of such coding conventions/standards.

You said, overbinding is not good, how? please ellaborate
it, I can hardly think of it, even in Datawarehouses, because I think that binding always "HELPS" 
or "DOES NOT HELP" but it never "HARMS"

 


Followup   July 20, 2006 - 7am Central time zone:

if you use plsql to encapsulate all of your sql - you'll centralize your code and ensure that the 
sql is "consistent" :)


say you have the query:

for x in ( select * from user_objects where object_type = 'TABLE' )
loop


Tell me - what does that query "say".  It says "I'm processing tables", it is very clear, not 
ambigous, self documenting, useful.

Now:

for x in ( select * from user_objects where object_type = pkg.global )
loop

what does that say?  it says "i'm processing something, but you don't know what, you can guess, you 
might be right, you might be wrong"


the literal adds value for us humans.

It can also add value for the optimizer (although it can do bind peeking) 

5 stars   July 19, 2006 - 10am Central time zone
Reviewer: A reader 
Do a quick search for Bind Variable Peeking to see the answer to this. 


5 stars harms of overbinding   July 20, 2006 - 11am Central time zone
Reviewer: a reader 
Ok, so can we say that the harms of  over binding are only

1- Readability of the code is not good.
2- The query is hard parsed once, and due to bind variable peeking, the plan it generates may not 
be good for the other values of the variable.

Are there any other harms you want to list.
 


Followup   July 22, 2006 - 4pm Central time zone:

you missed the important one:

"It can also add value for the optimizer"

the optimizer gets the hard coded values - very relevant.


By using the "are only" terminology, it almost sounds like you are down playing this readability 
issue.  You shouldn't. 

5 stars overbinding harm list   July 21, 2006 - 9am Central time zone
Reviewer: a reader 
Any body like to add to the above list 


4 stars TKPROF OUTPUT WHEN USING BIND VARIABLES   September 12, 2006 - 5pm Central time zone
Reviewer: Seema from USA
Hi Tom,

I tried comparing the tkprof output using your example with following 3 cases:

set timing on
alter session set sql_trace=true;
begin
            for i in 1 .. 100000
            loop
                    insert /* STATIC SQL */ into t values ( i );
            end loop;
end;
/
begin
    for i in 1 .. 100000
    loop
            execute immediate 'insert /* DYNAMIC SQL USING BIND VARIABLES*/ into t values ( :i )'
                using i;
    end loop;
end;
/
-- If you really want to see the effect of not using bind variables use this:
begin
    for i in 1 .. 100000
    loop
        execute immediate 'insert /* DYNAMIC SQL NOT USING BIND VARIABLES*/  into t 
values('||i||')';
    end loop;
end;
/
disconnect

But I get a LOT of extra things in the tkprof output as following:
_________________
_________________
FOR THE 1st CASE:
_________________
_________________
********************************************************************************

begin
            for i in 1 .. 100000
            loop
                    insert /* STATIC SQL */ into t values ( i );
            end loop;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.16       0.16          0          0          0           0
Execute      1      0.65       6.34          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.81       6.51          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61  
********************************************************************************

select user# 
from
 sys.user$ where name = 'OUTLN'


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        1      0.00       0.00          0          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          2          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID USER$ 
      1   INDEX UNIQUE SCAN I_USER1 (object id 44)

********************************************************************************

INSERT INTO T 
VALUES
 ( :B1 )


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.04       0.03          0          0          0           0
Execute 100000     11.08      15.10          0        328     105275      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   100001     11.12      15.14          0        328     105275      100000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61     (recursive depth: 1)
********************************************************************************
_________________
_________________
FOR THE 2nd CASE:
_________________
_________________
********************************************************************************

begin
 for i in 1 .. 100000
 loop
         execute immediate
                'insert /* DYNAMIC SQL USING BIND VARIABLES*/ into t values ( :i )'
                using i;
 end loop;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.05       0.04          0          0          0           0
Execute      1      9.73      21.36          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      9.78      21.41          0          0          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61  
********************************************************************************

insert /* DYNAMIC SQL USING BIND VARIABLES*/ into t 
values
 ( :i )


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse   100000     10.94       3.89          0          0          0           0
Execute 100000     12.69      18.22          0     100326     304972      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200000     23.64      22.11          0     100326     304972      100000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61     (recursive depth: 1)
********************************************************************************
_________________
_________________
FOR THE 3rd CASE:
_________________
_________________
********************************************************************************

begin
 for i in 1 .. 100000
 loop
  execute immediate
  'insert /* DYNAMIC SQL NOT USING BIND VARIABLES*/  into t values('||i||')';
 end loop;
end;

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1     17.30      31.79          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     17.30      31.79          0          0          0           1

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 61  
********************************************************************************

insert /* DYNAMIC SQL NOT USING BIND VARIABLES*/  into t 
values
(1)


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.01          0          0          0           0
Execute      1      0.00       0.00          0          1          3           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.02       0.01          0          1          3           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61     (recursive depth: 1)
********************************************************************************

insert /* DYNAMIC SQL NOT USING BIND VARIABLES*/  into t 
values
(2)


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          1          3           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          1          3           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61     (recursive depth: 1)
********************************************************************************

insert /* DYNAMIC SQL NOT USING BIND VARIABLES*/  into t 
values
(3)


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          1          3           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          1          3           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61     (recursive depth: 1)
********************************************************************************

insert /* DYNAMIC SQL NOT USING BIND VARIABLES*/  into t 
values
(4)


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          1          3           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.00          0          1          3           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61     (recursive depth: 1)
********************************************************************************

insert /* DYNAMIC SQL NOT USING BIND VARIABLES*/  into t 
values
(5)


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          1          3           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0          1          3           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61     (recursive depth: 1)
********************************************************************************

insert /* DYNAMIC SQL NOT USING BIND VARIABLES*/  into t 
values
(6)

_________________________________________
_________________________________________
AND THIS GOES ON AND ON .................
.........................................
.........................................
.........................................
.........................................
.........................................
.....................................TILL
_________________________________________
_________________________________________

********************************************************************************

insert /* DYNAMIC SQL NOT USING BIND VARIABLES*/  into t 
values
(100000)


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          1          3           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.00          0          1          3           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61     (recursive depth: 1)
********************************************************************************

select pos#,intcol#,col#,spare1,bo#,spare2 
from
 icol$ where obj#=:1


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      5      0.00       0.00          0          0          0           0
Fetch       22      0.00       0.00          0         44          0          17
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       29      0.00       0.00          0         44          0          17

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 2)
********************************************************************************

select obj#,type#,ctime,mtime,stime,status,dataobj#,flags,oid$, spare1, 
  spare2 
from
 obj$ where owner#=:1 and name=:2 and namespace=:3 and remoteowner is null 
  and linkname is null and subname is null


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        1      0.00       0.00          0          4          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          4          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)
********************************************************************************

select node,owner,name 
from
 syn$ where obj#=:1


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        1      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          3          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)
********************************************************************************

select grantee#,privilege#,nvl(col#,0),max(mod(nvl(option$,0),2))
from
 objauth$ where obj#=:1 group by grantee#,privilege#,nvl(col#,0) order by 
  grantee#


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      3      0.00       0.00          0          0          0           0
Fetch       11      0.01       0.00          0         11          0           8
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       16      0.01       0.00          0         11          0           8

Misses in library cache during parse: 2
Optimizer goal: CHOOSE
Parsing user id: SYS   (recursive depth: 1)



********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        6      0.37       0.39          0          0          0           0
Execute      7     27.72      59.55          0          0          0           6
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       13     28.10      59.95          0          0          0           6

Misses in library cache during parse: 3
Misses in library cache during execute: 1


OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse   200064    148.35     138.78          0          5          0           0
Execute 300101     40.00      54.36          0     201008     715286      300006
Fetch      273      0.09       0.06          0        582          0         191
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   500438    188.45     193.22          0     201595     715286      300197

Misses in library cache during parse: 100029

200008  user  SQL statements in session.
   63  internal SQL statements in session.
200071  SQL statements in session.
********************************************************************************
Trace file: bpsrep2_ora_1592.trc
Trace file compatibility: 9.02.00
Sort options: default

       1  session in tracefile.
  200008  user  SQL statements in trace file.
      63  internal SQL statements in trace file.
  200071  SQL statements in trace file.
  100026  unique SQL statements in trace file.
 1300870  lines in trace file.

_________________________________________
_________________________________________

how do I read and get final conclusions (like the way you demonstarted) from all this for the 3 
types of statements that I ran.

 


Followup   September 13, 2006 - 7am Central time zone:

look at the overall totals.

compare them.

that is the "summary of the work performed by each" 

4 stars tkprof for comparing bind variables performance benefit   September 13, 2006 - 6pm Central time zone
Reviewer: Seema from USA
Ok Tom, I picked the following results for the 1st case(STATIC SQL)& 2nd case(DYNAMIC SQL USING 
BIND VARIABLES.

But about the 3rd case (DYNAMIC SQL NOT USING BIND VARIABLES) I did not find no summary in the 
tkprof output. The tfprof output is already pasted in my previous post. Please advise. 

********************************************************************************

INSERT INTO T 
VALUES
 ( :B1 )


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.04       0.03          0          0          0           0
Execute 100000     11.08      15.10          0        328     105275      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   100001     11.12      15.14          0        328     105275      100000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61     (recursive depth: 1)
********************************************************************************
********************************************************************************

insert /* DYNAMIC SQL USING BIND VARIABLES*/ into t 
values
 ( :i )


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse   100000     10.94       3.89          0          0          0           0
Execute 100000     12.69      18.22          0     100326     304972      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200000     23.64      22.11          0     100326     304972      100000

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61     (recursive depth: 1)
******************************************************************************** 


Followup   September 14, 2006 - 9am Central time zone:

my advice is constant and consistent:

USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS 
USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS 
USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS 
USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS 
USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS 
USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS 
USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS 
USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS 
USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS 
USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS USE BINDS 
USE BINDS USE BINDS 


tkprof shows cpu time used
tkprof shows elapsed time
tkprof shows IO's performed.

is it not *obvious* from the above which uses

o less cpu
o less elapsed time
o less IO's

what tkprof does not show is the latching involved - and the second one (with 100,000 parses) does 
TONS more latching than the first meaning when you go multi-user, the second one you have will use 
MUCH MORE cpu than it currently does (more users = more latch contention = more cpu used = less 
scalable = not nearly as much work gets done)

just use binds. 

5 stars Helpful, but need some clarification   December 20, 2006 - 10am Central time zone
Reviewer: A reader 
We have an application server that sends various updates/select/inserts to the database.  It uses 
bind variables.

One particular update statement, which uses bind variables is performing a FTS.  It performs a 
basic update on T1 against 3 columns where a particular column, COL1 = :V1.

T1 has 3million+ rows and COL1=0 for 99.999% of the rows.  The row that gets updated is the row 
where COL!=0, but some number that is substituted for the bind variable.  

I'm trying to understand if the optimizer uses the sql with the bind variable to determine is 
execution path or will it generate an execution path based upon a fixed value for COL1?

The bind variable version is very expensive and performs a FTS, however if I run the update with a 
specific value for COL1 then the explain plan shows that it uses the index.

 


Followup   December 20, 2006 - 1pm Central time zone:

it will bind peek the first time around - I'd look into "implicit datatype conversion" first and 
foremost.

you can use a 10053 trace (search for that number on this site) to verify bind peeking is 
happening.

Look out for implicit conversions though! (eg: col1 is a varchar2 in the database, programmer binds 
a number, the real predicate would be "to_number(col1) = :x" 

4 stars Help please   March 7, 2007 - 1pm Central time zone
Reviewer: Loïc from Paris
Hi Tom,
I work on a project on which certain "design patterns" are used to write SQL queries.
Among them stands the following:

"Write a generic SQL query to handle every case so that maintenance is very simple."

select ... 
from my_data 
where col1 = :1 and
(:2 is null or col2 = :3);


The last line has two parameters which are binded at runtime with the same value:
input of a user in a search screen for example.

If the user inputs nothing, then the query works and we have:

select ... 
from my_data 
where col1 = :1 and
(null is null or col2 = null);


which only consists of filtering on col1.

If the user inputs a value, it becomes:

select ... 
from my_data 
where col1 = :1 and
('value' is null or col2 = 'value');


and the filtering is also made on col2.


Following is a testcase to see this design pattern in action:

SET ECHO OFF
SET SERVEROUTPUT ON SIZE 1000000
SET FEEDBACK OFF
column "Plan Table" format a255

prompt Cleaning...
DROP TABLE CASH_COMPENSATION;
DROP TABLE compensation;

prompt Creating tables...
CREATE TABLE compensation 
( 
  compensation_id NUMBER not NULL primary KEY, 
  status  VARCHAR2(4) 
);

create table CASH_COMPENSATION 
(
  CASH_COMPENSATION_ID NUMBER not NULL PRIMARY KEY,
  TRX_ID               NUMBER,
  ORIGIN               VARCHAR2(5) not NULL
);

alter table CASH_COMPENSATION
  add constraint FK_CASH_COM_FK_CASH_C_COMPENSA foreign key (CASH_COMPENSATION_ID)
  references COMPENSATION (COMPENSATION_ID);

prompt Inserting data into tables...

DECLARE

num_rows NUMBER;

BEGIN

  FOR i IN 1..50000 LOOP
      BEGIN
         INSERT INTO compensation (compensation_id, status) VALUES (i, decode(MOD(i,5), 0, 'SETT', 
'COMP') );
  
         IF MOD(i,10) != 9 THEN 
            INSERT INTO cash_COMPENSATION (CASH_COMPENSATION_ID, trx_id, origin) VALUES 
            (i,decode(MOD(i,2), 0, i*3, NULL),decode(MOD(i,4), 0, 'INCO', 'OUTGO'));
         END IF;
 
      END;
  END LOOP;
  
  COMMIT;
  
  SELECT COUNT(*) INTO num_rows FROM compensation;

  dbms_output.put_line('Inserted '||num_rows||' rows into COMPENSATION');

  SELECT COUNT(*) INTO num_rows FROM cash_compensation;
  
  dbms_output.put_line('Inserted '||num_rows||' rows into CASH_COMPENSATION');

END;

/

prompt Gathering statistics on tables...

EXEC dbms_stats.gather_table_stats( ownname => USER, tabname => 'COMPENSATION', estimate_percent => 
100, method_opt => 'FOR ALL COLUMNS SIZE AUTO', cascade => TRUE );

EXEC dbms_stats.gather_table_stats( ownname => USER, tabname => 'CASH_COMPENSATION', 
estimate_percent => 100, method_opt => 'FOR ALL COLUMNS SIZE AUTO', cascade => TRUE );

truncate table plan_table;

explain plan FOR
select COMP.COMPENSATION_ID,
       CASHCOMP.TRX_ID,
       CASHCOMP.ORIGIN
  from COMPENSATION      COMP,
       CASH_COMPENSATION CASHCOMP
 where COMP.COMPENSATION_ID = CASHCOMP.CASH_COMPENSATION_ID(+)
   and (:1 is null or :2 = COMP.COMPENSATION_ID);

select plan_table_output "Plan Table" from table(dbms_xplan.display('plan_table',null,'serial'));
ROLLBACK;

explain plan FOR
select COMP.COMPENSATION_ID,
       CASHCOMP.TRX_ID,
       CASHCOMP.ORIGIN
  from COMPENSATION      COMP,
       CASH_COMPENSATION CASHCOMP
 where COMP.COMPENSATION_ID = CASHCOMP.CASH_COMPENSATION_ID(+)
   and :2 = COMP.COMPENSATION_ID;

select plan_table_output "Plan Table" from table(dbms_xplan.display('plan_table',null,'serial'));
ROLLBACK;

Output:

Cleaning...
Creating tables...
Inserting data into tables...

Inserted 50000 rows into COMPENSATION
Inserted 45000 rows into CASH_COMPENSATION
Gathering statistics on tables...

Plan Table
----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------
| Id  | Operation                    |  Name              | Rows  | Bytes | Cost (%CPU)|
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                    |  2501 | 45018 |  6579  (61)|
|   1 |  NESTED LOOPS OUTER          |                    |  2501 | 45018 |  6579  (61)|
|*  2 |   INDEX FULL SCAN            | SYS_C0015604931    |  2501 | 12505 |   955  (91)|
|   3 |   TABLE ACCESS BY INDEX ROWID| CASH_COMPENSATION  |     1 |    13 |     3  (67)|
|*  4 |    INDEX RANGE SCAN          | SYS_C0015604934    |     1 |       |            |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(:Z IS NULL OR "COMP"."COMPENSATION_ID"=TO_NUMBER(:Z))
   4 - access("COMP"."COMPENSATION_ID"="CASHCOMP"."CASH_COMPENSATION_ID"(+))

Plan Table
----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------
| Id  | Operation                    |  Name              | Rows  | Bytes | Cost (%CPU)|
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                    |     1 |    18 |     5  (60)|
|   1 |  NESTED LOOPS OUTER          |                    |     1 |    18 |     5  (60)|
|*  2 |   INDEX UNIQUE SCAN          | SYS_C0015604931    |     1 |     5 |     3  (67)|
|   3 |   TABLE ACCESS BY INDEX ROWID| CASH_COMPENSATION  |     1 |    13 |     3  (67)|
|*  4 |    INDEX UNIQUE SCAN         | SYS_C0015604934    |     1 |       |            |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("COMP"."COMPENSATION_ID"=TO_NUMBER(:Z))
   4 - access("CASHCOMP"."CASH_COMPENSATION_ID"(+)=TO_NUMBER(:Z))
       filter("COMP"."COMPENSATION_ID"="CASHCOMP"."CASH_COMPENSATION_ID"(+))


As you can see, the first and second execution plan (and thus the costs) are different ^_^;

However, my question is: how can I explain in simple words such behaviour to developers?

I have said at least to write two different queries to separate the two cases (input / no input)
but I can't find a simple explanation to tell them: although the col2 (or compensation_id) is indexed
and althgouh the clause ":1 is null" is independent of the tables/columns... Oracle doesn't use
your index (or at least in a poor way: INDEX FULL SCAN).


Regards,
Loïc

Followup   March 7, 2007 - 2pm Central time zone:

allow me to expand on this:

"Write a generic SQL query to handle every case so that maintenance is very simple."

it should be:

"Write a generic SQL query to handle every case so that maintenance is pretty hard since the query will be ugly and complicated with lots of functions and of course performance will be at its very worst. But it will be generic."

http://jonathanlewis.wordpress.com/2007/02/14/conditional-sql-2/

4 stars How to bind Varchar2 variables?   March 8, 2007 - 5pm Central time zone
Reviewer: Maverick 
Tom, How to put bind variable in Execute immediate for Varchar2 ?

for eg:-

declare
  v_desc varchar2(20):='john';
  v_sql varchar2(500):='';
  v_desc1 varchar2(20):='';
  v_id1 integer:=0;
begin  
  v_sql:='select empno,ename from emp where upper(ename) like ''%'||upper(v_desc)||'%'' and 
rownum=1'; 

           
  dbms_output.put_line(v_sql);
  Execute immediate v_sql into v_id1,v_desc1 ;

  dbms_output.put_line(v_id1||' ,'||v_desc1);
     
end;           

How can I bind upper(v_desc) instead of concatenating in this string?

Thanks,

Followup   March 8, 2007 - 8pm Central time zone:

scott%ORA9IR2> declare
  2    v_desc varchar2(20):='king';
  3    v_sql varchar2(500):='';
  4    v_desc1 varchar2(20):='';
  5    v_id1 integer:=0;
  6  begin
  7    v_sql:='select empno,ename from emp where upper(ename) like :x and rownum=1';
  8    dbms_output.put_line(v_sql);
  9    Execute immediate v_sql into v_id1,v_desc1 using '%'||upper(v_desc)||'%';
 10    dbms_output.put_line(v_id1||' ,'||v_desc1);
 11  end;
 12  /
select empno,ename from emp where upper(ename) like :x and rownum=1
7839 ,KING

PL/SQL procedure successfully completed.

5 stars Thanks for solution   March 8, 2007 - 8pm Central time zone
Reviewer: Maverick 
Thanks so much Tom. Why didn't I think of that :-(. I tried all combinations inside quotes to no avail ...

Thanks again.

5 stars Is there anyway to force Oracle to always peek for a certain query?   May 1, 2007 - 7pm Central time zone
Reviewer: Brandon from Phoenix, AZ
I understand bind variable peeking is good in most OLTP situations, but it seems to me that many/most database applications these days are OTLP/DW hybrids rather than truly dedicated OLTP-only systems. I work mostly with the Baan ERP application and I think it falls under the hybrid category. Baan uses bind variables for all queries and performs very well about 99.9% of the time, but there are a few troublesome queries ever since upgrading to 9i & 10g with the introduction of bind variable peeking where a query is executed once for a single row, and then the exact same query is executed later for a large range of rows. In these cases, the latter execution gets stuck with an inappropriate explain plan (often a MERGE JOIN CARTESIAN/BUFFER SORT) and performance is terrible. I understand this is really a defect of the application - it should differentiate between these two queries rather than sharing a cursor for both of them, but it seems to me that it would be very helpful for a lot of customers if Oracle could provide a work around because many applications seem to suffer from this problem and it isn't always possible/feasible for us to fix the application code. It would be great if Oracle could provide a method of forcing the CBO to always peek at the bind variables for a specific query. One way to do this would be with a hint, e.g. ALWAYS_PEEK, which could then be applied to the problematic query with a stored outline. Is there any undocumented hint or other method of doing this currently? If not, is there any chance of adding it as an enhancement in future versions?

I've already opened an SR about this too and am awaiting response.

Thanks,
Brandon

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

there is not currently, and not as far as I know into the future.
1 stars Trigger :NEW.Value :OLD.Value   May 2, 2007 - 3am Central time zone
Reviewer: A reader 
Hi Tom,

We have to develop an Audit Trail for exceptional transcations on important transaction tables. We can not use the built-in audit features because of our requirements. So, we want to develop our own generic procedure to store the values in Audit Trail Table. That procedure will be called in all the triggers (before insert or update on delete for each row) on the selected transaction Tables. Our objective is to call the same procedure in all the tables and so that the signature of the procedure is same. So we want to pass the :OLD.columns or :NEW.columns to that procedure dynamically. But it is not possible to resolve the value of :OLD / :NEW dynamically. What could be the best method to implement it?

5 stars   May 2, 2007 - 7am Central time zone
Reviewer: Alberto Dell'Era from Milan, Italy
It seems that Brandon, Albert Nelson and me are asking for the same thing:

http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:122537200346913471#1228410003469
80212


5 stars Enhancement request to force bind variable re-peeking   May 2, 2007 - 7pm Central time zone
Reviewer: Brandon from Phoenix, AZ
Yes, it seems we (Alberto, Albert and I) are all in agreement, and I also found a couple old bug/enhancement request notes on metalink regarding the same issue: 2463736 & 4041455, but it looks like nothing has been done on them in years.

I submitted my enhancement request in SR 6299151.992 yesterday. Maybe if we all submit ERs, something might get done?


4 stars Specifying partitions in queries   May 3, 2007 - 1pm Central time zone
Reviewer: DW from Dallas TX USA
I have a question related to Steve Booth's questions in this thread from May 2-3, 2006.

My data is partitioned by month, and my query needs to find data in the "current" partition, something like:

SELECT *
FROM T_Data
WHERE MyDate >= TRUNC(SYSDATE,'MONTH')
  AND Fld2 = 1234;

I have tried "forcing" it to the current partition (looking for PARTITION RANGE (SINGLE) in the execution plan) by using "where mydate between trunc(sysdate,'MONTH') and sysdate", but I always get PARTITION RANGE (ITERATOR) or PARTITION RANGE (ALL) unless I specifically identify the partition, as in:

SELECT *
FROM T_Data PARTITION (M_200705)
WHERE MyDate >= TRUNC(SYSDATE,'MONTH')
  AND Fld2 = 1234;

Consistent gets and physical reads are always better with the specified partition. I need to do this in an overnight job, and I would rather not have to resort to dynamic SQL. Is there anything else I can try?


Followup   May 3, 2007 - 10pm Central time zone:

give me example to work with.

but basically, you are saying:

where mydate >= :bind_variable and fld2=1234;


because sysdate is an ever moving target, it's value depends on when you submit the query. It'll NEVER be partition range single - because it could be ALL partitions!!!


show us the row source operations from tkprofs with and without the partition(pname)
5 stars Using a Dynamic Comment to Force Hard Parse   May 4, 2007 - 1pm Central time zone
Reviewer: Brandon from Phoenix, AZ
Tom,

I found another thread where someone was asking for similar functionality as discussed above to force certain queries to always be hard parsed and have their bind variables peeked, and you suggested a workaround of using dynamic SQL to create the query with the return value of the systimestamp function in the comment/hint section:

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


This is a great idea, but in my situation I can't use dynamic SQL. Is there anyway to execute a function in the comment area of static query? I thought I saw some special syntax for doing this before, but now I can't find it.

Thanks,
Brandon

Followup   May 4, 2007 - 3pm Central time zone:

this is a horrible idea.

remember I also wrote there:

but, it'll kill your system if that is done more than "once in a long while"


why can't you use dynamic sql?

5 stars Will only be done once in a long while   May 4, 2007 - 4pm Central time zone
Reviewer: Brandon from Phoenix, AZ
Yes, I caught that part too, but the query I'm working with is only executed a few times a day so I'm not too concerned. It won't be flooding my shared pool and the trade off of extra hard parsing for the improved explain will be well worth it. I can't use dynamic SQL because this is a 3rd-party app (Baan ERP) and I don't have source code to their database driver that sends the queries to Oracle. All I can do is inject some text into the comment/hint section of the query. I just figured out that this will work:

alter session set nls_date_format='DD-MON-YY hh24:mi:ss';

select /*+ &_DATE */ sysdate from dual;

But, I think that will only work in SQL*Plus.


Followup   May 8, 2007 - 9am Central time zone:

if you cannot do dynamic sql, I'm not seeing how you can dynamically change the comment.
5 stars How are binds handled where the condition does not involve a column?   May 17, 2007 - 3pm Central time zone
Reviewer: Ray DeBruyn from Ottawa, Ontario, Canada
Hi Tom,

I have many PLSQL Web Toolkit pages having cursors declared with where clauses like:
...
AND (  p_name IS NULL
  OR p_name IS NOT NULL AND name = p_name)...

The plan should be quite different depending on whether p_name is null or not null. Does the plan get set based on the first parse (say with a not null value and then always always assume a not null value in future executions)?

I may have 3 or 4 such parameters used in a single cursor. Do you see any issue in doing it this way?

Followup   May 18, 2007 - 3pm Central time zone:

one plan, one plan...


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

4 stars Usage of Bind Variable for a Frequently used Procedure   June 6, 2007 - 12pm Central time zone
Reviewer: RAM from UK
Hi tom ,

We have a procedure which is used to take a report and runs every day, the procedure used gets different arguments and because of this we are having a hard parse every execute
To be more clear
we are having hard pares (7327 ) times of
BEGIN get_login_name('<value>'); END; 

The procedure would be called in the below fashion
BEGIN get_login_name('annehammond@greenhithe.freeserve.co.uk'); END; 
BEGIN get_login_name('davenlaura@sandpiper43.freeserve.co.uk'); END; 
BEGIN get_login_name('james.ballingall@partnershipsuk.org.uk'); END; 
BEGIN get_login_name('jenny.marriott@english-heritage.org.uk'); END; 
BEGIN get_login_name('annamilldown@tithefarme.freeserve.co.uk'); END; 
BEGIN get_login_name('maxine.bernard@jobcentreplus.gsi.gov.uk'); END;


Is there a way we can use bind variables here

ie to convert the above to something like
BEGIN get_login_name(':b'); END;

so that the hard parses are avoided

Thanks


Followup   June 6, 2007 - 9pm Central time zone:

sure, just bind it???

begin get_login_name( :b ); end;

(no quotes, just like sql)

It seems you already know what to do - so do it?
4 stars Binds   June 7, 2007 - 6am Central time zone
Reviewer: RAM from UK
Hi tom ,

Thanks for the response , was just cross checking whether i am going in the right direction.

Thanks again



3 stars Bind variable and sequence.nextval   July 18, 2007 - 11am Central time zone
Reviewer: Sandro from Italy
create sequence s;

create table t1 (c1 number, c2 varchar2(10), c3 varchar2(10), c4 varchar2(10), c5 varchar2(10));
create table t2 (c1 number, c2 varchar2(10), c3 varchar2(10), c4 varchar2(10), c5 varchar2(10));
insert into t1 values (1, 'a', 'b', 'c', 'd');
commit;

select * from t1;
    C1 C2      C3      C4      C5
---------- ---------- ---------- ---------- ----------
      1 a      b      c      d


declare
t_trc_file varchar2(256) := 'alter session set tracefile_identifier=' || chr(39) || user || to_char(sysdate, 'YYYYDDMMHH24MISS') || chr(39);
t_trc_stat varchar2(256) := 'alter session set timed_statistics=true';
t_trc_size varchar2(256) := 'alter session set max_dump_file_size=unlimited';
t_trc_sqlSTART varchar2(256) := 'alter session set events ' || chr(39) || '10046 trace name context forever, level 12' || chr(39);
t_trc_sqlSTOP  varchar2(256) := 'alter session set events ' || chr(39) || '10046 trace name context OFF'|| chr(39);
begin
execute immediate t_trc_file;
execute immediate t_trc_stat;
execute immediate t_trc_size;
execute immediate t_trc_sqlSTART; -- START TRACE
for i in 1 .. 10000 loop
  insert into t2
    select s.nextval
        ,c2
        ,c3
        ,c4
        ,c5
    from t1;
end loop;
execute immediate t_trc_sqlSTOP; -- START TRACE
end;
/

tkprof C:\oracle\admin\orcl9\udump\orcl9_ora_3188_test20071807173427.trc C:\oracle\admin\orcl9\udump\orcl9_ora_3188_test20071807173427.txt explain=test/test table=test.plan_table sys=no sort=prsela,fchela,exeela


call  count    cpu  elapsed    disk    query  current    rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse    1    0.00    0.00      0      0      0      0
Execute 10000    1.31    3.51      0    70026    10827    10000
Fetch    0    0.00    0.00      0      0      0      0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total  10001    1.31    3.51      0    70026    10827    10000

question:
why
  insert into t2
    select s.nextval
        ,c2
        ,c3
        ,c4
        ,c5
    from t1;
Use correctly bind and pars once?

I thought has to do...
declare
n integer;
begin
select s.nextval into n from dual;
insert into t2
  select n
      ,c2
      ,c3
      ,c4
      ,c5
    from t1;
...for correct use of bind variables.

Followup   July 18, 2007 - 12pm Central time zone:

you thought wrong?

I don't know why you would think that - sequences are sort of like a function, the value is passed into the sql statement.
5 stars How it get parsed that many times.   January 17, 2008 - 5am Central time zone
Reviewer: Karthick from India
How your dynamic insert that used bind variable got parsed that many times. For me it get parsed 
only once.

BEGIN
    FOR i IN 1 .. 100000
    LOOP
        INSERT /* STATIC SQL */ INTO t VALUES ( i );
    END LOOP;
END;
/
BEGIN
    FOR i IN 1 .. 100000
    LOOP
        EXECUTE IMMEDIATE 'insert /* DYNAMIC SQL */ into t values ( :i )' USING i;
    END LOOP;
END;
/


INSERT INTO T VALUES ( :B1 )


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute 100000     13.76      19.72          0        169     304722      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   100001     13.76      19.72          0        169     304722      100000


insert /* DYNAMIC SQL */ into t values ( :i )


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute 100000     14.25      14.38          0        171     304712      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   100001     14.25      14.38          0        171     304712      100000

Also another thing i noted was when rewriting my static insert it removed the comment any idea why.

iam using

Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod
PL/SQL Release 10.2.0.1.0 - Production
CORE    10.2.0.1.0      Production
TNS for 32-bit Windows: Version 10.2.0.1.0 - Production
NLSRTL Version 10.2.0.1.0 - Production


Followup   January 17, 2008 - 11am Central time zone:

you are using 10g

I was using 8i or 9i


10g introduce a native dynamic sql feature that would cache the last statement, so in 9i and before

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

would be:

parse insert...
bind insert ...
execute insert...


in 10g it would be:

if ( last_statement_executed_here <> insert ... )
then
   parse insert....
   last_statement_executed_here = insert....
end if;
bind insert...
execute insert....




that is why everything on this site has a version and a timestamp associated with it so you can tell what release I was using and how long ago we did it.
5 stars Can we avoid soft parse also??   January 17, 2008 - 5am Central time zone
Reviewer: Karthick from India

Ok i know what is soft and hard parsing. I learned from you (your site and your book) only.

I was reading this followup

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

and the below comment got me confused. So i came back here to read it again.

insert /* DYNAMIC SQL */ into t values ( :i )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse   100000      4.56       4.40          0          0          0           0
Execute 100000     10.97      11.04          0     100168     302764      100000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   200000     15.53      15.44          0     100168     302764      100000

look at that -- terrible. parse counts like crazy. way over parsed. at least we
bound -- but the price we paid for parsing is extremely high. They were soft parses but
a parse is a parse and something to be avoided when possible.


A bind variable has been used. And yet the statement has parsed 100000 times. And you say they are soft parse meaning it gose for syntax and sematic check alone.

I was in an assumption that any query that is submitted gose throught those two stages. A query cannot get executed without that two checks. Am i wrong here.

What you mean by avoide a soft parse.

Followup   January 17, 2008 - 11am Central time zone:

syntax, semantic alone - you say that like they are "free", "cheap" or "easy"... they are none of the above.

to avoid a soft parse is to - well - avoid a soft parse, to not reparse a sql statment, to just bind and execute it over and over.

5 stars   January 18, 2008 - 5am Central time zone
Reviewer: Karthick Pattabiraman 
Thanks for the pseudocode it was helpfull.

BEGIN
    FOR i IN 1 .. 100000
    LOOP
        INSERT /* STATIC SQL */ INTO t VALUES ( i );
    END LOOP;
END;
/

Will the pseudocode for static sql will be like this ?

parese insert

for i in 1..1000
loop
   bind insert
   execute
end loop


Followup   January 19, 2008 - 10pm Central time zone:

it is even better than that for a stored procedure, but for the anonymous block, sure, that is it - parse once, execute many.

5 stars   January 20, 2008 - 2am Central time zone
Reviewer: karthick from India
Can you give the pseudocode for stored procedures?


Followup   January 20, 2008 - 7am Central time zone:

a stored procedure might not parse it on a given call, they cache it

for a stored procedure, it might just be:

loop
bind it
execute it
end loop


if it was already called once before in your session
5 stars How to hard parse to avoid the peek for other bind values   January 29, 2008 - 11am Central time zone
Reviewer: Tom from Leesburg
I believe I am experiencing the side effect of the peeking.

You wrote:
what I'm saying is -- ok, you have a piece of code.  It does a poplist, populates some list box.  

that piece of code will either ALWAYS index range scan (most likely) or NEVER.  The design of your 
interactive OLTP application is such that it is ALWAYS going to use inputs that permit an efficient 

index range scan -- or, it is not.  It is the very nature of your application here. 

We have a job that runs every night and loops through a cursor using the below query: 

         SELECT 
       DISTINCT COL1
              , COL2
           FROM X
          WHERE COL3 = :b1

Plan
SELECT STATEMENT ALL_ROWS Cost: 105,871 Bytes: 147,437,130 Cardinality: 6,410,310 
     2 HASH UNIQUE Cost: 105,871 Bytes: 147,437,130 Cardinality: 6,410,310 
          1 TABLE ACCESS FULL TABLE OWNER.X Cost: 61,665 Bytes: 147,437,130 Cardinality: 6,410,310 


There is a non-unique index ind1 on COL3:

         SELECT 
       DISTINCT COL1
              , COL2
           FROM X
          WHERE COL3 = 99


Plan
SELECT STATEMENT ALL_ROWS Cost: 5 Bytes: 23 Cardinality: 1 
     3 HASH UNIQUE Cost: 5 Bytes: 23 Cardinality: 1 
          2 TABLE ACCESS BY INDEX ROWID TABLE OWNER.X Cost: 4 Bytes: 23 Cardinality: 1 
               1 INDEX RANGE SCAN INDEX OWNER.ind1 Cost: 3 Cardinality: 1 


On some days it uses the index and the process is complete in about 10 minutes. On other days it 
runs all day long and causes production support problems.

After reviewing all of the post on this page I believe we may be having a peeking problem. I am not 
sure what would make oracle not want to use the index for some of the COL3 values other than 
peeking. We have millions of rows in the table.

Table X is truncated and repopulated in the step before this process executes. We analyze the table 
before the process begins: 

dbms_stats.gather_table_stats(ownname => schema_i
                                 ,tabname => table_name_i
                                 ,degree  => degree_i
                                 ,cascade => TRUE);


I believe that on some mornings the first value of COL3 makes up a high percentage of table X and 
oracle therefore does a full table scan. The problem is that it continues to do a full table scan 
for all of the other values causing it to run all day instead of 10 minutes.

I have listed the COL3 data profile:

COL3        COUNT(*)
23371        12771172
23372        607864
23376        3021962
23377        1412095
24641        146147
25808        530440
23375        31767560
25809        7807889
22734        9069642
17343        6741786
23373        2902454
24640        90895

I show that when Oracle peeks and the COL3 is 23371 it does a full table scan but it continues to 
do a full table scan for all of the other COL3 values also.

Is there an easy way to get oracle to hard parse for each COL3 value of the cursor or do I need to 
rewrite the code in a different manner?


Followup   January 30, 2008 - 9am Central time zone:

since col3 has so few distinct values, you do not need to bind it, just use a literal instead
5 stars After more research into the code.   January 29, 2008 - 12pm Central time zone
Reviewer: Tom from Leesburg, VA
It looks like the SQL cursor is in a procedure that is called once for each COL3 value. Would this be less of a peeking problem and maybe related to cursor sharing issue instead?

Followup   January 30, 2008 - 9am Central time zone:

it is classic bind peeking - the cursor is hard parsed once and the plan chosen by the first value. So instead of:

create procedure foo( p_col3 in number )
as
   cursor c is select * from t where col3 = p_col3;
begin
   open c;   
   loop
     fetch c into ....;
     exit when c%notfound;
   end loop;
   close c;
end;


do this

create procedure foo( p_col3 in number )
as
   c sys_refcursor;
begin
   open c for 'select * from t where col3 = ' || p_col3;   
   loop
     fetch c into ....;
     exit when c%notfound;
   end loop;
   close c;
end;



as long as col3 has few values, as you indidcated it did above.
4 stars Root Cause   January 30, 2008 - 7am Central time zone
Reviewer: Tom from Leesburg, VA
This appears to be a case of developers believing that they can join oracle tables better than 
oracle. This code has a driving cursor fetching about 3000 records. For each of the 3000 records it 
is executing the SQL statement:

     SELECT 
       DISTINCT COL1
              , COL2
           FROM X
          WHERE COL3 = :b1


 even though there are only 12 distinct COL3 values in table X. Therefore they are executing this 
query 2988 times unnecessarily. They could have added this to the driving query and let oracle 
filter them out and we would not be having this conversation. I have been told that I am not 
allowed to redesign all of the code therefore I need to get their lookup query to run faster for 
each of the 3000 COL3 bind values. 

In the case when the first execution value is in the table and has a large representation Oracle 
correctly does a full table scan. Problem is that for each of the queries that follow oracle finds 
the execution plan in the SGA and executes the full table scan again. To force Oracle to hard parse 
each of the 3000 executions I am considering using an OPEN-FOR ref cursor and turning the SQL into 
dynamic SQL with the COL3 bind value concatenated. I believe that this will force a hard parse and 
allow oracle to full table scan on COL3 values that have a large number of rows and use the index 
for the other values. I would like to rewrite the entire module but can¿t.

This may no longer belong to this section. I do believe it would be of use for people who are 
suffering the same problem and am interested in your take on the issue.

Thank you.


Followup   January 30, 2008 - 10am Central time zone:

what a bunch of hooey. I hate "developers" some days (I am a developer by the way... One that understands what databases can and cannot do. For former is a long long list, the latter is pretty short)



3 stars Problem Solved   January 31, 2008 - 1pm Central time zone
Reviewer: Tom from Leesburg, VA
Thank you for your time. The fix went into production last night and the batch job runs in just a 
couple of minutes now :) vs 24 hours a day.


5 stars Bind variables in dynamic SQL   February 7, 2008 - 2pm Central time zone
Reviewer: Gary Wicke from Indianapolis, IN US of A
Hi Tom

I have a piece of PL/SQL code that is returning an error and I can't see the issue. I have a table NET_SALES_JE_PROGRAMS that contains the columns PROGRAM (VARCHAR2) and DOC_TYPE (VARCHAR2).

I have another table that contains similar column names but represent the combination of PROGRAM and DOC_TYPE that I want to DELETE from the first table. Since the DOC_TYPE in the second table could be a '%', saying that ALL DOC_TYPEs for that PROGRAM should be deleted, I am using the LIKE function on the concatenation of PROGRAM and DOC_TYPE.

Here is my PL/SQL code:
DECLARE
l_program  owb_rtt.net_sales_program_exclude.program%TYPE;
l_doc_type owb_rtt.net_sales_program_exclude.doc_type%TYPE;
BEGIN
        FOR xrow in (select program, doc_type from OWB_RTT.NET_SALES_PROGRAM_EXCLUDE where 
STATUS_CD = 'ACTIVE')
        LOOP

        l_program := xrow.program;
        l_doc_type := xrow.doc_type;

        execute immediate  'delete from OWB_RTT.NET_SALES_JE_PROGRAMS where PROGRAM||DOC_TYPE like 
'':prog||:doctype''' USING l_program, l_doc_type;

        dbms_output.put_line('Program: ' || l_program || '     Doc Type: ' || l_doc_type);

        END LOOP;

EXCEPTION
     WHEN OTHERS
     THEN dbms_output.put_line('Error: ' || SQLCODE || '     Error msg: ' || SQLERRM);
END;
/


I am getting the following error message returned from my exception: Error: -1006  Error msg: ORA-01006: bind variable does not exist

I'm assuming it's the way I'm trying to use the combination of the cursor and the dynamic SQL but I can't seem to find the trick.

Many thanks for pointing me in the right direction.

/gary

Followup   February 7, 2008 - 3pm Central time zone:

you do not quote binds

your delete is literally:

delete from x.y where c1||c2 like ':prog||:doctype'

you are comparing c1||c2 to the LITERAL STRING :prog||:doctype

Just because there is a colon in a string doesn't make it a bind.


execute immediate '
delete from x.y 
  where c1||c2 like :prog||:doctype
' using l_program, l_doc_type;



and for the love of everything DELETE THAT EXCEPTION HANDLER NOW and never ever do that again.

when others, not followed by RAISE or RAISE_APPLICATION_ERROR is a bug 99.99999999999% of the time you see it. Here, it most definitely is a bug.

read this
http://www.oracle.com/technology/oramag/oracle/07-jul/o47asktom.html



5 stars   February 25, 2008 - 4pm Central time zone
Reviewer: A reader from IL
Hey Tom:

    We have hard time on our database now. we tuned the query, and then put them on live. But some 
time when we checked the plan on live, the query is using the different plan with what we were 
tuning it. and the gets is very high. I guess it should come from bind peeking. (we are using 
"force" in cursor sharing. ) when the query first came into the system, with the value it had, pLan 
A might be better for it. so in the following, it will not hard parse. it will keep using plan A, 
although it may not be the optimal plan for the other values.

    Then when this happens, how can we change the plan for the queries. how can we change the plan 
already cached in the system?

    Is there a good method to overcome bind peeking?

Thank you very much


Followup   February 25, 2008 - 4pm Central time zone:

... we tuned the query, .... (we are using "force" in cursor
sharing. ) ....

you get the irony in that statement. If you tuned, you would not be using cursor sharing =force, you would have already fixed the BUG, the obvious BUG in the code... So, you have not tuned anything yet. You haven't even started (since 99% of the gains to be had from tuning start from looking at the application - not automagical switches and things in the database)

http://tkyte.blogspot.com/2007/09/sqltracetrue-part-two.html

5 stars   February 26, 2008 - 2pm Central time zone
Reviewer: A reader from IL
Hey Tom:

    We have OLTP system. I read some discussion in here, so for OLTP, it is better to 
cursor_sharing= force at first to help the application. And after developers fix their bugs in the 
code, (using their own bind variable in the right way), we can change the cursor_sharing= Exact. is 
this what you mean?

   and what is the difference between the "Exact" and the "Similar". this is my understanding, not 
sure if it is correct: in exact mode, optimizor will soft parse all the queries has the similar 
structure but different literals, no matter there is a histogram on the table, index or not. but in 
similar mode, optimizor will consider the existing plan and histogram to make decision if it needs 
to hard parse or not. any other difference? like the number of soft parse...

 this is from you blog:
"Is it possible to force Oracle somehow to peek at the bind variables of a SQL without the cost of 
doing a hard parse?


cursor_sharing=similar with literals will do that actually - at the huge expense of a soft parse."

  will "similar" cost more soft parse than force?

    And next question comes. Will the frequent gethering histogram will affect the database 
performance?

    and another question: we are now using "force". when we saw a query in system, we had 39 
sessions having the same queries, and this query is on the top of the activity in grid control. the 
plan is not the optimal plan. This should come from the bind peeking.So how can we get the query 
hard parsed again. I remember you said the autotrace will alway hard parse the query when 
generating execution plan, and also the sql_trace=true hard parse the query in the first time too. 
If I run the query with autotrace or sql_trace=true at this time, will the plan cached in the 
shared pool for this query be changed? will the queries which were using bad plan will swith to 
using the new parsed plan?


4 stars One bind variable   March 5, 2008 - 11am Central time zone
Reviewer: A reader 
Hi Tom

     How can I get the same result using one bind variable instead of two bind variable

      SELECT DECODE(:TYP,'NULL',NULL,:TYP) FROM DUAL

Thanks


Followup   March 6, 2008 - 7am Central time zone:

select decode( x, 'null', null, x ) from (select :typ x from dual);

but why would you use sql to do that. if you are in a 3gl (java, c, plsql whatever) JUST WRITE CODE - do not bother the server for something so trivial.
3 stars SELECT NULLIF(:TYP,'NULL') FROM DUAL   March 6, 2008 - 8am Central time zone
Reviewer: A reader 


5 stars values of bind variables when explain plan has been created   March 27, 2008 - 4am Central time zone
Reviewer: Nenad Noveljic from Switzerland
Is it possible to find out from some dictionary views, what values of bind variables has been used 
to create the current explain plan?


Followup   March 27, 2008 - 10am Central time zone:

10gr2....

ops$tkyte%ORA10GR2> create table t ( x int );

Table created.

ops$tkyte%ORA10GR2> insert into t select 5 from all_objects;

49884 rows created.

ops$tkyte%ORA10GR2> insert into t values ( 99 );

1 row created.

ops$tkyte%ORA10GR2> create index t_idx on t(x);

Index created.

ops$tkyte%ORA10GR2> exec dbms_stats.gather_table_stats(user,'T',method_opt=>'for all indexed 
columns' );

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2> variable n number;
ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> exec :n := 99;

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> select * from t where x = :n;

         X
----------
        99

ops$tkyte%ORA10GR2> select * from table(dbms_xplan.display_cursor(null,null,'basic 
+PEEKED_BINDS'));

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select * from t where x = :n

Plan hash value: 2946670127

----------------------------------
| Id  | Operation        | Name  |
----------------------------------
|   0 | SELECT STATEMENT |       |
|   1 |  INDEX RANGE SCAN| T_IDX |
----------------------------------

Peeked Binds (identified by position):
--------------------------------------

   1 - :N (NUMBER): 99


18 rows selected.

5 stars bind peeking in 11g   April 4, 2008 - 4pm Central time zone
Reviewer: A reader 
Hey Tom:

      I read your article in the Oracle Megazine about the bind peeking, and you said the 11g has 
the new feature of "intelligent cursor sharing" which enables the optimizer to generate different 
plan for diffrent bind vavriable. and I tested the test case you provide in 11g, I found some 
interesting thing:

create table ttt
as
select case when rownum = 1 then 1 else 99 end id, a.*
 from all_objects a
/
 alter table ttt modify object_id null;

create index ttt_idx on ttt(id);

begin
dbms_stats.gather_table_stats
( user, 'TTT',
 method_opt=> 'for all indexed columns',cascade=>TRUE);
end;
 /

SQL> variable id number
SQL> exec :id := 99
SQL> select * from ttt where id = :id;
             .........
67965 rows selected.

SQL> select sql_id,sql_text,to_char(LAST_ACTIVE_TIME,'DD_MM_YYYY 
HH24:MI'),CHILD_NUMBER,FIRST_LOAD_TIME
  2  from v$sql
  3  where upper(sql_text)  like '% * FROM TTT%';

SQL_ID
-------------
SQL_TEXT
TO_CHAR(LAST_ACT CHILD_NUMBER FIRST_LOAD_TIME
---------------- ------------ -------------------
90z9b4d8swhua
select sql_id,sql_text,to_char(LAST_ACTIVE_TIME,'DD_MM_YYYY HH24:MI'),CHILD_NUMBER,FIRST_LOAD_TIME 
from v$sql where upper(sql_text)  like '% * FROM TTT%'
04_04_2008 16:54            0 2008-04-04/16:52:43

gx8dupra9bzqj
select * from ttt where id = :id
04_04_2008 16:54            0 2008-04-04/16:53:28


SQL> exec :id := 1

PL/SQL procedure successfully completed.

SQL> select * from ttt where id = :id;
     ...
   1 rows selected.
     
SQL>
SQL> select sql_id,sql_text,to_char(LAST_ACTIVE_TIME,'DD_MM_YYYY 
HH24:MI'),CHILD_NUMBER,FIRST_LOAD_TIME
  2  from v$sql
  3  where upper(sql_text)  like '% * FROM TTT%';

SQL_ID
-------------
SQL_TEXT

TO_CHAR(LAST_ACT CHILD_NUMBER FIRST_LOAD_TIME
---------------- ------------ -------------------
90z9b4d8swhua
select sql_id,sql_text,to_char(LAST_ACTIVE_TIME,'DD_MM_YYYY HH24:MI'),CHILD_NUMBER,FIRST_LOAD_TIME 
from v$sql where upper(sql_text)  like '% * FROM TTT%'
04_04_2008 16:55            0 2008-04-04/16:52:43

gx8dupra9bzqj
select * from ttt where id = :id
04_04_2008 16:54            0 2008-04-04/16:53:28

????????????????????? and through display the cursor, the id=1 is using the same plan with 99. why 
the last active time is not changed?

****** I executed it again

SQL> select * from ttt where id = :id
  2  ;
SQL> select sql_id,sql_text,to_char(LAST_ACTIVE_TIME,'DD_MM_YYYY 
HH24:MI'),CHILD_NUMBER,FIRST_LOAD_TIME
  2  from v$sql
  3  where upper(sql_text)  like '% * FROM TTT%';

SQL_ID
-------------
SQL_TEXT

TO_CHAR(LAST_ACT CHILD_NUMBER FIRST_LOAD_TIME
---------------- ------------ -------------------
b7h8dsvsx89y5
select * from ttt where id = :id
04_04_2008 16:57            0 2008-04-04/16:57:06

90z9b4d8swhua
select sql_id,sql_text,to_char(LAST_ACTIVE_TIME,'DD_MM_YYYY HH24:MI'),CHILD_NUMBER,FIRST_LOAD_TIME 
from v$sql where upper(sql_text)  like '% * FROM TTT%'
04_04_2008 16:57            0 2008-04-04/16:52:43

gx8dupra9bzqj
select * from ttt where id = :id
04_04_2008 16:54            0 2008-04-04/16:53:28


???????????????why this time there is another sql_id with the same sql_text, and if you check the 
plan, you will find they are the same plan "full table scan".

*************and I execute it again

SQL> select * from ttt where id = :id;

SQL> select sql_id,sql_text,to_char(LAST_ACTIVE_TIME,'DD_MM_YYYY 
HH24:MI'),CHILD_NUMBER,FIRST_LOAD_TIME
  2  from v$sql
  3  where upper(sql_text)  like '% * FROM TTT%';

SQL_ID
-------------
SQL_TEXT
TO_CHAR(LAST_ACT CHILD_NUMBER FIRST_LOAD_TIME
---------------- ------------ -------------------
b7h8dsvsx89y5
select * from ttt where id = :id
04_04_2008 16:57            0 2008-04-04/16:57:06

90z9b4d8swhua
select sql_id,sql_text,to_char(LAST_ACTIVE_TIME,'DD_MM_YYYY HH24:MI'),CHILD_NUMBER,FIRST_LOAD_TIME 
from v$sql where upper(sql_text)  like '% * FROM TTT%'
04_04_2008 17:14            0 2008-04-04/16:52:43

gx8dupra9bzqj
select * from ttt where id = :id
04_04_2008 17:14            0 2008-04-04/16:53:28

gx8dupra9bzqj
select * from ttt where id = :id
04_04_2008 17:14            1 2008-04-04/16:53:28


*************and here comes another one, I am totally confused, what is this one from?

????????? and when I set autotrace traceonly, I found 

SQL> set autotrace traceonly
SQL> select * from ttt where id = :id;


Execution Plan
----------------------------------------------------------
Plan hash value: 774701505

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 33983 |  3451K|   289   (1)| 00:00:04 |
|*  1 |  TABLE ACCESS FULL| TTT  | 33983 |  3451K|   289   (1)| 00:00:04 |
--------------------------------------------------------------------------

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

   1 - filter("ID"=TO_NUMBER(:ID))


but in 10gr2, the plan will show using index. since autotrace generate plan from hard parse. why 
here in 11g, the autotrace show the real plan? is that correct in 11g, autotrace gives the plan 
from v$sql table instead of hard parse?

notes:
the value 99 only been used once in the very beginning, all the following query are binded to 1;


Thank you very much!!!


Followup   April 7, 2008 - 8am Central time zone:

ops$tkyte%ORA11GR1> alter system flush shared_pool;
System altered.

ops$tkyte%ORA11GR1> create table ttt
  2  as
  3  select case when rownum = 1 then 1 else 99 end id, a.*
  4   from all_objects a where rownum <= 20000
  5  /
Table created.

ops$tkyte%ORA11GR1>  alter table ttt modify object_id null;
Table altered.

ops$tkyte%ORA11GR1> create index ttt_idx on ttt(id);
Index created.

ops$tkyte%ORA11GR1> begin
  2  dbms_stats.gather_table_stats
  3  ( user, 'TTT',
  4   method_opt=> 'for all indexed columns',cascade=>TRUE);
  5  end;
  6  /
PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR1> SET SERVEROUTPUT OFF
ops$tkyte%ORA11GR1> variable id number
ops$tkyte%ORA11GR1> exec :id := 99
PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR1> select * from ttt where id = :id;
.....
19999 rows selected.

rather than use autotrace, we'll use dbms_xplan directly, here I 
call display cursor, this shows the result of the LAST EXECUTED SQL
in my session - using the not documented +peeked_binds, we can see
what bind values were used to optimize this query...

ops$tkyte%ORA11GR1> select * from table(dbms_xplan.display_cursor(null,null,'typical 
+peeked_binds'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------
SQL_ID  gx8dupra9bzqj, child number 0
-------------------------------------
select * from ttt where id = :id

Plan hash value: 774701505

------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time
------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |       |       |    41 (100)|
|*  1 |  TABLE ACCESS FULL| TTT  | 19999 |   820K|    41   (0)| 00:00:01
------------------------------------------------------------------------

Peeked Binds (identified by position):
--------------------------------------

   1 - :ID (NUMBER): 99

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

   1 - filter("ID"=:ID)

23 rows selected.

we'll also look in v$sql at the new columns "is_bind_sensitive" (for 11g and adaptive cursor
sharing, is the query subject to changes in plans due to binds changing over time....) and
"is_bind_aware" (if we are sensitive to binds, have we become aware yet of a performance issue
with regards to the bind values).

Right now, our query is bind sensitive - the optimizer knows to look for problems with the plan 
over
time since different binds would lead to different plans.  We are not aware yet of any problems
since we've only just run the query once...

ops$tkyte%ORA11GR1> select sql_id, is_bind_sensitive, is_bind_aware from v$sql where 
sql_text='select * from ttt where id = :id';

SQL_ID        I I
------------- - -
gx8dupra9bzqj Y N


 now, chang the binds....

ops$tkyte%ORA11GR1> exec :id := 1;
PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR1> select * from ttt where id = :id;

        ID  OBJECT_ID OBJECT_NAME
---------- ---------- ------------------------------
OWNER                          OBJECT_TYPE
------------------------------ -------------------
         1         20 ICOL$
SYS                            TABLE


ops$tkyte%ORA11GR1> select * from table(dbms_xplan.display_cursor(null,null,'typical 
+peeked_binds'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------
SQL_ID  gx8dupra9bzqj, child number 0
-------------------------------------
select * from ttt where id = :id

Plan hash value: 774701505

------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time
------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |       |       |    41 (100)|
|*  1 |  TABLE ACCESS FULL| TTT  | 19999 |   820K|    41   (0)| 00:00:01
------------------------------------------------------------------------

Peeked Binds (identified by position):
--------------------------------------

   1 - :ID (NUMBER): 99

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

   1 - filter("ID"=:ID)


23 rows selected.

same plan - same child number - same everything - but the database did notice a problem, when it 
ran
this query, it was expecting LOTS of rows, it got one.  So, the next time we run it....

ops$tkyte%ORA11GR1> select * from ttt where id = :id;

        ID  OBJECT_ID OBJECT_NAME
---------- ---------- ------------------------------
OWNER                          OBJECT_TYPE
------------------------------ -------------------
         1         20 ICOL$
SYS                            TABLE


ops$tkyte%ORA11GR1> select * from table(dbms_xplan.display_cursor(null,null,'typical 
+peeked_binds'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------
SQL_ID  gx8dupra9bzqj, child number 1
-------------------------------------
select * from ttt where id = :id

Plan hash value: 240739660

------------------------------------------------------------------------
| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%C
------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |       |       |     2 (1
|   1 |  TABLE ACCESS BY INDEX ROWID| TTT     |     1 |    42 |     2
|*  2 |   INDEX RANGE SCAN          | TTT_IDX |     1 |       |     1
------------------------------------------------------------------------

Peeked Binds (identified by position):
--------------------------------------

   1 - :ID (NUMBER): 1

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

   2 - access("ID"=:ID)


24 rows selected.

it automagically did the hard parse for us and got an optimal plan for this bind value, peeking 
at 
v$sql again



ops$tkyte%ORA11GR1> select sql_id, is_bind_sensitive, is_bind_aware from v$sql where 
sql_text='select * from ttt where id = :id';

SQL_ID        I I
------------- - -
gx8dupra9bzqj Y N
gx8dupra9bzqj Y Y

we see the plan is now BIND AWARE - there are two of them - the first one will not be used again, 
since we 
became aware of a problem with binds - for example, even though we parsed with 99 once, we'll do it 
again to 
get the bind aware plan:


ops$tkyte%ORA11GR1> exec :id := 99

PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR1> set termout off
ops$tkyte%ORA11GR1> select * from ttt where id = :id;

....
19999 rows selected.

ops$tkyte%ORA11GR1> select * from table(dbms_xplan.display_cursor(null,null,'typical 
+peeked_binds'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------
SQL_ID  gx8dupra9bzqj, child number 2
-------------------------------------
select * from ttt where id = :id

Plan hash value: 774701505

------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time
------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |       |       |    41 (100)|
|*  1 |  TABLE ACCESS FULL| TTT  | 19999 |   820K|    41   (0)| 00:00:01
------------------------------------------------------------------------

Peeked Binds (identified by position):
--------------------------------------

   1 - :ID (NUMBER): 99

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

   1 - filter("ID"=:ID)


23 rows selected.

ops$tkyte%ORA11GR1> select sql_id, is_bind_sensitive, is_bind_aware from v$sql where 
sql_text='select * from ttt where id = :id';

SQL_ID        I I
------------- - -
gx8dupra9bzqj Y N
gx8dupra9bzqj Y Y
gx8dupra9bzqj Y Y

ops$tkyte%ORA11GR1> spool off

5 stars bind peeking in 11g   April 7, 2008 - 9am Central time zone
Reviewer: A reader 
This is a great explanation!!! i will try it by myself.


THANK YOU VERY MUCH TOM!!!


5 stars   June 2, 2008 - 5am Central time zone
Reviewer: vaibhav from India
PROCEDURE sps_find_transactions(p_service_id            IN    mhs_transactions.service_id%TYPE,
                p_interaction_id        IN    VARCHAR2,
                p_originating_party_id      IN    mhs_transactions.originating_party_id%TYPE,
                p_destination_party_id      IN    mhs_transactions.destination_party_id%TYPE,
                p_originating_system_id     IN    mhs_transactions.originating_system_id%TYPE,
                p_destination_system_id     IN    mhs_transactions.destination_system_id%TYPE,
                p_manual_retry_attempts     IN    mhs_transactions.manual_retry_attempts%TYPE,
                p_manual_attempts_flag      IN    NUMBER,
                p_error_code                IN    mhs_errors.error_code%TYPE,
                p_total_attempts            IN    mhs_sched_processing.total_attempts%TYPE,
                p_total_attempts_flag       IN    NUMBER,
                p_from_timestamp            IN    mhs_transactions.persistence_timestamp%TYPE,
                p_to_timestamp              IN    mhs_transactions.persistence_timestamp%TYPE,
                p_status_type               IN    mhs_transaction_status.status_type%TYPE,
                p_order_by_col              IN    VARCHAR2,
                p_order_type                IN    VARCHAR2,
                p_batchsize                 IN    NUMBER,
                o_count_flag                OUT    NUMBER,
                o_cur_find_transactions     OUT    SYS_REFCURSOR)
AS
/***************************************************************************************************
*********
This procedure will open a sys refcursor that will return records from the tables MHS_TRANSACTIONS,
MHS_TRANSACTION_STATUS and MHS_MESSAGE_ID_MAP based on the input parameters.
****************************************************************************************************
********/

    v_sql                VARCHAR2(4000);
    v_result_count            NUMBER := 0;

    v_transaction_id        mhs_transactions.transaction_id%TYPE;
    v_interaction_id        mhs_transactions.interaction_id%TYPE;                
    v_status_type            mhs_transaction_status.status_type%TYPE;
    v_message_id_type        mhs_message_id_map.message_id_type%TYPE;
    v_message_id            mhs_message_id_map.message_id%TYPE;
    v_is_ref            mhs_message_id_map.is_ref%TYPE;
    v_originating_party_id        mhs_transactions.originating_party_id%TYPE;
    v_destination_party_id        mhs_transactions.destination_party_id%TYPE;
    v_originating_system_id        mhs_transactions.originating_system_id%TYPE;
    v_destination_system_id        mhs_transactions.destination_system_id%TYPE;
    v_persistence_timestamp        mhs_transactions.persistence_timestamp%TYPE;
    v_message_timestamp        mhs_transactions.message_timestamp%TYPE;

BEGIN

    o_count_flag := 0;

    v_sql := 'SELECT DISTINCT mt.transaction_id, interaction_id, status_type, message_id_type, 
message_id, is_ref, message_timestamp, '||CHR(10)||
             'originating_party_id, destination_party_id, originating_system_id, 
destination_system_id, persistence_timestamp '||CHR(10)||
             'FROM   mhs_message_id_map mmip, mhs_errors me, mhs_sched_processing msp, 
mhs_transactions mt, '||CHR(10);

    /* If p_status_type is null, then status_type should be the latest status from the view 
MHS_TRANSACTION_STATUS_V.
    Else, status_type should be the input p_status_type. 
    Include appropriate table names in the FROM clause */
    IF (p_status_type IS NOT NULL) THEN
        v_sql := v_sql || 'mhs_transaction_status mts '||CHR(10);
    ELSE
        v_sql := v_sql || 'mhs_transaction_status_v mtsv, mhs_transaction_status mts '||CHR(10);
    END IF;
    
        
    v_sql := v_sql || 'WHERE mt.transaction_id = msp.transaction_id '||CHR(10)||
                  'AND msp.transaction_id = mmip.transaction_id '||CHR(10)||   
                  'AND mmip.transaction_id = me.transaction_id '||CHR(10)||
                  'AND me.transaction_id = mts.transaction_id '||CHR(10);


    /* If p_service_id is null, then do not include it in the WHERE clause. */
    IF (p_service_id IS NOT NULL) AND (UPPER(p_service_id) NOT IN ('ALL', 'ANY')) THEN
        v_sql := v_sql || 'AND service_id = :p_service_id '||CHR(10);
    ELSE
        v_sql := v_sql || 'AND NVL(service_id, ''1'') = (CASE WHEN 1=2 THEN :p_service_id ELSE 
NVL(service_id, ''1'') END) '||CHR(10);
    END IF;
    
    
    /* If p_interaction_id is null, then do not include it in the WHERE clause.
    Else, call the function REPLACE_FUNC to separate out the comma-separated multiple Interaction 
IDs*/
    IF (p_interaction_id IS NOT NULL) AND (UPPER(p_interaction_id) NOT IN ('ALL', 'ANY')) THEN
        v_sql := v_sql || 'AND interaction_id IN ' || replace_func(p_interaction_id);
        v_sql := v_sql || CHR(10);
    END IF;


    v_sql := v_sql || 'AND NVL(originating_party_id, ''1'') = NVL(:p_originating_party_id, 
NVL(originating_party_id, ''1'')) '||CHR(10)||
              'AND NVL(destination_party_id, ''1'') = NVL(:p_destination_party_id, 
NVL(destination_party_id, ''1'')) '||CHR(10)||
                  'AND NVL(originating_system_id, ''1'') = NVL(:p_originating_system_id, 
NVL(originating_system_id, ''1'')) '||CHR(10)||
                  'AND NVL(destination_system_id, ''1'') = NVL(:p_destination_system_id, 
NVL(destination_system_id, ''1'')) '||CHR(10);


    IF (p_manual_retry_attempts IS NOT NULL) THEN
        IF (p_manual_attempts_flag = 1) THEN
            v_sql := v_sql || 'AND manual_retry_attempts >= :p_manual_retry_attempts '||CHR(10);
        ELSE    
            v_sql := v_sql || 'AND manual_retry_attempts = :p_manual_retry_attempts '||CHR(10);
        END IF;
    END IF;


    /* If p_to_timestamp is null, then default is sysdate */
    IF (p_to_timestamp IS NOT NULL) THEN
        v_sql := v_sql || 'AND persistence_timestamp BETWEEN ''' || p_from_timestamp || ''' AND ''' 
|| p_to_timestamp || ''' '||CHR(10);
    ELSE
        v_sql := v_sql || 'AND persistence_timestamp BETWEEN ''' || p_from_timestamp || ''' AND 
systimestamp '||CHR(10);
    END IF;
    
    
    IF (p_total_attempts IS NOT NULL) THEN
        IF (p_total_attempts_flag = 1) THEN
            v_sql := v_sql || 'AND total_attempts >= :p_total_attempts '||CHR(10);
        ELSE    
            v_sql := v_sql || 'AND total_attempts = :p_total_attempts '||CHR(10);
        END IF;
    END IF;

    
    /* If p_status_type is null, then status_type should be the latest status from the view 
MHS_TRANSACTION_STATUS_V.
    Hence, an euqui-join condition is added to the WHERE clause.
    Else, status_type should be the input p_status_type. Call the function REPLACE_FUNC to separate 

    out the comma-separated multiple Status Types */
    IF (p_status_type IS NOT NULL) THEN
        v_sql := v_sql || 'AND status_type IN ' || replace_func(p_status_type);
        v_sql := v_sql || CHR(10);
    ELSE
        v_sql := v_sql || CHR(10)||'AND msp.transaction_id = mtsv.transaction_id '||CHR(10)||
                  'AND status_type = current_status '||CHR(10);
    END IF;
         
    v_sql := v_sql || 'AND error_code = NVL(:p_error_code, error_code) '||CHR(10)||
              'ORDER BY mt.' || p_order_by_col || ' ' || p_order_type;

    OPEN o_cur_find_transactions FOR v_sql USING p_service_id, p_originating_party_id, 
p_destination_party_id,
                             p_originating_system_id, p_destination_system_id, 
p_manual_retry_attempts,
                             p_total_attempts, p_error_code;

    
    /* Count the number of records fetched to decide whether to set the O_COUNT_FLAG or not*/
    LOOP
    FETCH o_cur_find_transactions INTO v_transaction_id, v_interaction_id, v_status_type, 
v_message_id_type, 
                           v_message_id, v_is_ref, v_message_timestamp, v_originating_party_id, 
v_destination_party_id,
                           v_originating_system_id, v_destination_system_id, 
v_persistence_timestamp;
    EXIT WHEN o_cur_find_transactions%notfound;
        v_result_count := v_result_count + 1;
    END LOOP;

    
    IF (v_result_count > p_batchsize) THEN
        o_count_flag := 1;
    END IF;


    /* Filter out p_batchzise number of records */
    OPEN o_cur_find_transactions FOR ' SELECT * FROM ( ' || v_sql || ' ) WHERE ROWNUM <= 
:p_batchsize' 
                     USING p_service_id, p_originating_party_id, p_destination_party_id,
                           p_originating_system_id, p_destination_system_id, 
p_manual_retry_attempts,
                           p_total_attempts, p_error_code, p_batchsize;

                         
END sps_find_transactions;


how do i make this procedure use bind variables in all cases...i mean there are 2input variables 
that are timestamp...for every different dates passed to thsi proc, a hard parse occurs...how do i 
avoid this


5 stars thanks   June 4, 2008 - 2am Central time zone
Reviewer: Vaibhav from India
Hi,

I went through the link and realised that all i had to do is take date values as string and use 
to_timestamp so that i can use bind variables....your comments and answers are always wonderfull...

v_sql := 'SELECT  me.error_code, '||
    'COUNT (CASE when me.error_timestamp BETWEEN (:p_error_date - 3/24) AND (:p_error_date - 3/24) 
+ NUMTODSINTERVAL(15, 'MINUTE')   '||
    'THEN 1 ELSE NULL END) in1,  '||
    'COUNT (CASE when me.error_timestamp BETWEEN (:p_error_date - 3/24) + NUMTODSINTERVAL(15, 
'MINUTE') AND (:p_error_date - 3/24) + NUMTODSINTERVAL(30, 'MINUTE')   '||
    'THEN 1 ELSE NULL END) in2,  '||
    'COUNT (CASE when me.error_timestamp BETWEEN (:p_error_date - 3/24) + NUMTODSINTERVAL(30, 
'MINUTE') AND (:p_error_date - 3/24) + NUMTODSINTERVAL(45, 'MINUTE')  '||
    'THEN 1 ELSE NULL END) in3,  '||
    'COUNT (CASE when me.error_timestamp BETWEEN (:p_error_date - 3/24) + NUMTODSINTERVAL(45, 
'MINUTE') AND (:p_error_date - 3/24) + NUMTODSINTERVAL(60, 'MINUTE')  '||
    'THEN 1 ELSE NULL END) in4,  '||
    'COUNT (CASE when me.error_timestamp BETWEEN (:p_error_date - 3/24) + NUMTODSINTERVAL(60, 
'MINUTE') AND (:p_error_date - 3/24) + NUMTODSINTERVAL(75, 'MINUTE')  '||
    'THEN 1 ELSE NULL END) in5,  '||
    'SUM (COUNT(1)) OVER (PARTITION BY error_code ORDER BY error_code) total_count  '||
'FROM    mhs_errors me '||
'AND me.error_timestamp BETWEEN :p_error_date - 3/24 AND :p_error_date  '||
GROUP BY me.error_code ';

Open a_cursor for v_sql using p_error_date, p_error_date, p_error_date, p_error_date, p_error_date, 
p_error_date,
p_error_date, p_error_date, p_error_date, p_error_date, p_error_date, p_error_date;

1) is the above way the right way to use bind variable in this context...i mean will i have to 
repeat the same variable name for their corresponding bind variables...

2) also i have written a dynamic query that take smany inputs...but the problem is one input takes 
multiple values in the format 'a,b,c,d'...i use a function at my end to split it as 
('a','b','c','d')...

IF (p_interaction_id IS NOT NULL) THEN
    v_sql := v_sql || 'AND interaction_id IN '|| replace_func(p_interaction_id);
END IF;

IF (p_manual_attempts_flag = 1) THEN
v_sql := v_sql || 'AND manual_retry_attempts >= :p_manual_retry_attempts '||CHR(10);
END IF;

how do i make use of bind variable here, i have 14 paarmeters for thsi proc and all are using bind 
variables expect this one...because everytime a different value is passed for this parameter, hard 
parse occurs...how do i avoid this parsing everytime...i face a similiar prob in another proc as 
well where the input parameter is comma separated...

in fact, i will always be doing a hard parse for the procedures where the input parameter would be 
comma separated...

is there a way out...

Tom...please help...

Thanks a million again for patiently answering all my questions...

Btw, i have started reading Expert oracle by design and i m really enjoying it...chap 2 that 
explains how to understand AUTOTRACE report is just awesome...simply brilliant


Followup   June 4, 2008 - 9am Central time zone:

1) yes. but why are you using dynamic sql??? why not just use static sql?????

2)
http://tkyte.blogspot.com/2006/06/varying-in-lists.html

5 stars   June 5, 2008 - 12am Central time zone
Reviewer: Vaibhav from India
hi...

Thanks for that quick reply...

i was using static sql in the error count query but then the requirement changed and even that proc 
would now take a comma separated input parameter...as a result of which i thought of using dynamic 
query to make use of bind variables...i will go through the link that you gave and i am sure my 
solution will be right there...

once i know how to make use of bind variables for such comma separated situations, i am sure i can 
use that in many procs...


thanks again for all the information and help...

and yea, i was just thinking if i could use a TYPE and store the comma separated parameter in the 
plsql table using using instr and substr and then just say something like 

WHERE interaction_id IN (SELECT * FROM TABLE(id_tab AS id_type))interaction);

i think the above sql would always be static...and i would just avoid using a bind variable for 
that input parameter interaction_id

what do u suggest...would this be a performance issue...

and yea...a bad news...an online store charged me for Expert one on one oracle but they refunded it 
in my account and dropped me a mail saying "OUT OF STCOK"...atleast i have Expert Oracle by 
Design...i wont give up though...will definitely find one...hehe

Thanks...


4 stars   June 9, 2008 - 10am Central time zone
Reviewer: Marius NITU from France
Hi Tom,
We are dynamically generating in PL/SQL and executing a statement like this one (Oracle 9.2.0.5.0)

Insert into <GlobalTemporaryTable> Select * from (
        Select M.* From(
    Select ( :bind1, :bind2, ..., :bind50 From Dual), ComplexView M
            Where M.col1 = :bind1
               And M.col2 like :bind40
               And M.col15 = 0
       And M.col25 = :bind53
                                   )
                                                       )

ComplexView is a view based on two others view and these ones are in turn based on other views. The 
trick behind selecting bind variable from dual allows the construction of dynamic queries in PL/SQL 
using execute immediate, in the case where the number and order of bind variables is unknown at run 
time. 
We are experimenting somme poor execution plans with this type of query and I suppose that the 
mecanism of bind variable peeking could be the cause of wrong cardinalities shown by explain plan 
in our case.
What do you think about this Select Bind_variable from dual trick ?
PS. We have notice also that our problem seems to vanish if we are truncating the GTT but I think 
that this is more due to hazard.


Followup   June 9, 2008 - 2pm Central time zone:

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


shows a way to do this without the dual trick. It would also disable the ability to "bind peek"





5 stars Bind in PHP   June 11, 2008 - 8am Central time zone
Reviewer: Francesco from Italy
Hi Tom, 

I am executing SQL statements from a PHP script and I am binding all variable data. Suppose I issue 
a statement that contains e.g.:

substr(myfield, 1, 6)

should I also transform the "1" and "6" constants into bind variables, such as:

substr(myfield, :start, :len)

or would this not be needed to achieve the parse once execute many goal?

Thanks in advance
Francesco


Followup   June 11, 2008 - 9am Central time zone:

you only bind that which VARIES.

I presume it will always be 1 and 6 - therefore, you would not consider binding 1 and 6 - in fact, it would be "bad" to bind 1 & 6 if they are constant, we'd no longer know at parse time how wide that column would be if you selected it.



ops$tkyte%ORA10GR2> select substr( username, 1, 6 ), substr( username, :x, :y ) from all_users 
where rownum =1;

SUBSTR SUBSTR(USERNAME,:X,:Y)
------ ------------------------------
SYS    SYS

ops$tkyte%ORA10GR2>



you bind values that can change from execution to execution of a sql statement, anything that stays constant over every execution should just be "in there" as a literal
5 stars Bind variable & Peeking   June 16, 2008 - 5pm Central time zone
Reviewer: Nags from Boston
We have been having performance issue in production for last whole week, and this started right 
after we upgraded from 10.2.0.2 to 10.2.0.4.  The performance issue seems to be around bind 
variable and peeking and for some strange reason right after the upgrade.  The database has been 
working fine for last 6-8 months and this query is the most common query in our application.  I 
need some help:

If I modify the query from "and P.HOSPITALID = :3" to "and P.HOSPITALID LIKE :3" will it always use 
bind peeking and do re-parsing?  Will it help?

select
TRUNC(SYSDATE - A.DISCHARGE) AS AGE,
(TRUNC(A.DISCHARGE) - TRUNC(A.ADMIT)) AS LOS,
... more columns
count(*) over() as totalrecords
from LCLEpisode A, LCLPatientMast B, PolicyResources F, CodingQueueResource G, LCLMember D,
    EpisodeTypes C, ServiceNames E, PolicyEntity P ,FlexlistMetadata FMD
where E.HOSPITALID(+) = A.HOSPITALID
and E.SERVICECODE(+) = A.SERVICECODE
and C.HOSPITALID(+) = A.HOSPITALID
and C.ET_CODE = A.PTYPE
and A.HOSPITALID = G.HOSPITALID
and A.CHARTNO = G.CHARTNO
and A.EPISODENO = G.EPISODENO
and D.HOSPITALID(+) = G.HOSPITALID
and D.USERID(+) = G.RESPONSIBLECODER
and B.HOSPITALID = G.HOSPITALID
and B.CHARTNO = G.CHARTNO
and G.CODINGQUEUERESOURCEID = decode( 
replace(translate(trim(F.RESOURCEID),'0123456789','00000000000'),'0',null), null, 
to_number(trim(F.RESOURCEID)) )
and G.CODINGSTATUS = :1
and G.RESPONSIBLECODER = :2
and f.HOSPITALID = P.HOSPITALID
and F.POLICYID = P.POLICYID
and G.HOSPITALID=FMD.HOSPITALID
and FMD.NAME like 'daysBeforeExpiration' 
and G.FLEXLISTID=FMD.FLEXLSTID
and P.HOSPITALID = :3
and P.ENTITYID = :4
order by DISCHARGE asc


Followup   June 17, 2008 - 9am Central time zone:

...
If I modify the query from "and P.HOSPITALID = :3" to "and P.HOSPITALID LIKE
:3" will it always use bind peeking and do re-parsing?
....

no, it will generate a single plan like any other predicate would, it would peek once, build plan and then reuse it.



....The
performance issue seems to be around bind variable and peeking....

what makes you say that - give me some background, why do you suspect bind peeking is the "issue" here.
5 stars Bind variable & Peeking   June 17, 2008 - 1pm Central time zone
Reviewer: Nags from Boston
This is how it has been happening for the last 10 days.

Every day in the morning, we find that we have performance issue (CPU 100%) and this query runs 
terrible (>1hr to complete).  When we look at all the queries issued for that SQLID we find that, 
except for a few, rest all execute fine (<5 sec).   

So, we collect statistics for one of the tables (a very small table) in the query, so that a new 
plan gets generated, the next time this query is issued by the application.  We do see that a new 
PLAN HAS VALUE is generated and after that the query works just fine.  It stays like this for some 
time till a different variation of the query gets issued by the application, and the whole problem 
repeats itself.

We also think that somehow histograms got collected right after we upgraded (I have no proof except 
the symptoms we had).  And, to fix the issue we had run statistic collection which added histograms 
to other tables, which further added to our problems. 

Yesterday night we specifically collected statistics again for all the tables referred in this 
query and made sure histograms do not get generated.  We did not have a single instance today, 
where we had performance issue.

I researched and I found that several people recommended not to use bind variables and histograms.

We do have data that is very highly skewed.


Followup   June 17, 2008 - 3pm Central time zone:

so, you are fixed then, that is what you seem to be saying?
5 stars Bind variable & Peeking   June 17, 2008 - 3pm Central time zone
Reviewer: Nags from Boston
Yes, it seems like the statistics collection yesterday, has indeed fixed the issue.

Would you have recommended a different approach?


Followup   June 18, 2008 - 12pm Central time zone:

bind peeking becomes an issue in a transactional environment (9i-10gr2) when you

a) have skewed data
b) have statistics that tell the optimizer 'skewed data'
c) when a query is parsed with one set of binds - the plan is only good for those values - and HORRIBLE for others (most others typically)

You were a case of that - when first parsed, that query was parsed with some values that caused an efficient plan for THOSE VALUES but a horrible plan in general. Had the query been parsed first with a different set of values, you get a plan that is good enough for all. The bind peeking makes the plan flip flop unpredictably.

So, by removing the (b) bit above - you end up getting a single, consistent plan - one that works for you. Yes, this is one of the perfectly valid approaches (in 10gr2 and before, having histograms in a transactional system is probably not something you want in general, you would gather them very selectively - intentionally, not as a rule of thumb)


In 11gr1 with adaptive cursor sharing, this all changes - there the optimizer will recognize "whoops, that isn't a good plan, lets get a better one next time" and self correct.

5 stars Bind variable & Peeking   June 18, 2008 - 6pm Central time zone
Reviewer: Nags from Boston
What we found out today, was that the Histograms were being collected by the automatic statistics 
collection mechanism of oracle 10g.  We are looking at either 

- preventing the automatic job from collecting histograms
- or collecting statistics on most affected tables manually and then locking the statistics

Both actions have some caveats.  Which one would you recommend?

I am leaning towards controlling the automatic job.

Thank you for your suggestions.


Followup   June 19, 2008 - 9am Central time zone:

I would lead toward not using the automatic job, creating my own 'automatic' job with my 'own' set of statistics to gather.
4 stars Bind Variable in DDL command   July 29, 2008 - 5pm Central time zone
Reviewer: Robert from japan
Hi Tom,
I was trying to use bind variable in the name of the partition to create a table using create as 
select, but I got ORA-00933.

The name of the partition is always 'PARTYYYYMM':

declare
vpart            CHAR(7);
curs                  INTEGER;
begin
    select 'PART'||to_char(add_months(sysdate, -1), 'yyyymm') into vpart from dual;


    curs := dbms_sql.open_cursor;
    dbms_sql.parse(curs, 'create table SNIPER_rsm as 
    select a.id_month,a.id_cust,    from
        tab_part  partition (:vpart) a, 
        tab2 c
    where
    a.id_month = to_date(''01/06/2008'',''dd/mm/yyyy'')      
    and a.id_type_prod in (1,4,11,531,532)',dbms_sql.v7);
    dbms_sql.close_cursor(curs);

end;
/



Followup   August 1, 2008 - 10am Central time zone:

you cannot bind identifiers....


think about it, the plan HAS to be different if you change an identifier, you cannot share the sql if the identifier it different....


you know, if you have monthly partitions, all you need is:
begin
   execute immediate '
create table sniper_rsm
as
select a.id_month, a.id_cust, ...
  from tab_part a, tab2 c
 where a.partition_key >= trunc(add_months(sysdate,-1),'mm')
   and a.partition_key < trunc(sysdate)
   and a.id_month = ...
';
end;





you don't want or need the partition name, just use the right range and we'll pick the right partition.

also, do not

select f(sysdate) into var from dual;

just

var := f(sysdate);

in the future, selecting from dual is not correct there, just wastes cpu cycles.
5 stars Bind Variables   August 13, 2008 - 7pm Central time zone
Reviewer: Bob from London, UK
Hi Tom,

I'm an Oracle DBA (on Oracle 9.2) and I'm trying to help a Java developer out. I have noticed that 
we have a lot of hard parsing in our database and I tracked it down to a particular report. 
Unfortunately, the output from this report is generated via dynamic SQL from JAVA (as opposed to 
having the business logic and coding from the database - historical issues). The problem  we have 
is that we could have multiple values selected from a dropdown from the front end. These in turn 
are put into a WHERE clause 

e.g. select col1, col2 from x where x.y IN(1,2,3);

where 1,2,3 are coming from the front end. How do we use bind variables in Java so we can 
accommodate these values in the IN clause?

One other thing, is it possible to do something clever in the database so that we can limit a 
maximum number of users to concurrently run reports. At the moment, users are hogging the system by 
running these CPU intensive reports. I know I can create a PROFILE and then assign a user to that 
profile. But I don't want their connection to completely bomb out - but rather that they receive a 
user friendly message to try later...

Cheers, 
Bob


Followup   August 18, 2008 - 9am Central time zone:

http://tkyte.blogspot.com/2006/06/varying-in-lists.html


... One other thing, is it possible to do something clever in the database so that
we can limit a maximum number of users to concurrently run reports. ....

http://download.oracle.com/docs/cd/B19306_01/server.102/b14231/dbrm.htm#ADMIN027


use the resource manager to limit the number of concurrent users per resource group - the others will be placed on hold while N of them run...

http://download.oracle.com/docs/cd/B19306_01/server.102/b14231/dbrm.htm#sthref3274

5 stars Bind variables   August 21, 2008 - 8am Central time zone
Reviewer: Bob from London, UK
That's bloody fanatastic! Thank you :-)



5 stars Bind variables   August 21, 2008 - 8am Central time zone
Reviewer: Bob from London, UK
That's bloody fantastic! Thank you :-)



5 stars bitmap index bind variable   September 2, 2008 - 2pm Central time zone
Reviewer: YAN from USA
Hey Tom:

     I know for b-tree index, bind peeking happens all the time, no matter it is user bind or 
system bind. what about bitmap index? I have an example in our system.

we have bitmap index on each of the columns of table axe. when I run this query from command line, 
it is very fast, very low gets and pick up the indexes.

SELECT /*+ first_rows (100) */ axe.ad_id
FROM ax_results_enhanced axe
WHERE axe.year between '1880'
and '2009'
and axe.price between '2000'
and '2500'
and axe.latitude between '29.775122222222'
and '30.930677777778'
and axe.longitude between '81.066122222222'
and '82.221677777778'
and axe.show_image = 'Y'
and rownum <= 500
order by to_number (decode (axe.search_order, 20, 1, 50, 1, 75, 1, 90, 2, 99, 3, axe.search_order)) 
asc, to_number (decode (axe.price, 0, null, axe.price)) desc nulls last 

But when it runs from the sessions, it picked up the full table scan with huge gets. In our system, 
the cursor_sharing  is set to "FORCE". So I wonder if it is because the query like following run at 
the first time and caused the system to  pick up full table scan and though that is the best plan 
for all. But actually it is not.

for the following query, all the predicates in the where clause are not selective, almost including 
all the possible range. and even when I run from command line, it picks up the full table scan.

SELECT /*+ first_rows (100) */ axe.ad_id
FROM ax_results_enhanced axe
WHERE axe.year between '1880'
and '2009'
and axe.price between 0
and 999999
and axe.latitude between '0'
and '100'
and axe.longitude between '0'
and '180'
and axe.show_image = 'Y'
and rownum <= 500
order by to_number (decode (axe.search_order, 20, 1, 50, 1, 75, 1, 90, 2, 99, 3, axe.search_order)) 

asc, to_number (decode (axe.price, 0, null, axe.price)) desc nulls last 

if it is bind peeking, what should we do? I heard the 11g has conquered the bind peeking issue, it 
will automatically realize "this is not a good plan, let us pick another plan".
Is this true? And I know Oracle has offered 11g for a long time. My question is :

Is it mature or stable enough to be production version? 

Thank you very much!



Followup   September 2, 2008 - 4pm Central time zone:

... In our system, the cursor_sharing is set to "FORCE". So I wonder if it
is because the query like following run at the first time and caused the system
to pick up full table scan and though that is the best plan for all. ...

absolutely - before 11g - absolutely. In 11g there is adaptive cursor sharing which will change this behavior - but in 9i and 10g, this can definitely be happening.


...
and rownum <= 500
order by to_number (decode (axe.search_order, 20, 1, 50, 1, 75, 1, 90, 2, 99,
3, axe.search_order))
asc, to_number (decode (axe.price, 0, null, axe.price)) desc nulls last

.....

are you sure you meant to do that??? you get 500 random rows, and then sort them?? Are you sure you meant to do that? You know, given the same set of rows - that query could return two entirely different results for two different users - using the same inputs...


...
Is it mature or stable enough to be production version?
.....

Actually, if you have to use cursor_sharing=force, you should be asking that of your developed code. To have to use that setting means you have some really insecure code (sql injectable) - a massive bug, not only is not binding properly a performance, memory and scalability issue - but it opens your application and thus database to easy attack.


...
WHERE axe.year between '1880'
and '2009'
.....

please don't tell me you put numbers into strings :( why would you do that?

...
and axe.price between 0
and 999999
and axe.latitude between '0'
and '100'
.....

you have some numbers as numbers and other obvious numbers in .... strings.

and you know, if you did store those as strings and not fixed width, always padded strings, you lose....

ops$tkyte%ORA10GR2> create table t ( x varchar2(20) );

Table created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> insert into t values ( '0' );

1 row created.

ops$tkyte%ORA10GR2> insert into t values ( '99' );

1 row created.

ops$tkyte%ORA10GR2> insert into t values ( '100' );

1 row created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> select * from t where x between '0' and '100';

X
--------------------
0
100



you basically get strings that start with '0' or '1' that way - never 2, 3, 4, ... and so on. did you really mean to do this???!?!?


or is the data in the table really a NUMBER and you are comparing a STRING to a NUMBER, in which case there are implicit conversions happening - probably obviating the use of an index altogether.


show us your create table and your create indexes, then we can discuss further, but there are lots of RED FLAGS here.


5 stars bitmap index bind variable   September 2, 2008 - 5pm Central time zone
Reviewer: YAN from USA
Hey Tom:

    sorry, the query should look like this:

SELECT /*+ first_rows (100) */ axe.ad_id
FROM ax_results_enhanced axe
WHERE axe.year between 1880
and 2009
and axe.price between 0
and 999999
and axe.latitude between 0
and 100
and axe.longitude between 0
and 180
and axe.show_image = 'Y'
and rownum <= 500
order by to_number (decode (axe.search_order, 20, 1, 50, 1, 75, 1, 90, 2, 99, 3, axe.search_order)) 

asc, to_number (decode (axe.price, 0, null, axe.price)) desc nulls last;

we have a tool to pull all the slow queries from system. When it pulled the queries, it adds single 
quote on all value, no matter it is char or number. I modified the year but forgot the others. The 
query above is what it really looks like in the database.

And yes! I have to say we have a lot of bad queries. And it is been a long time, it is hard for us 
to track all the queries with user bind variables, where it should bind, and when it binds. We 
tried use "exact" for cursor_sharing before, but it ended up with very high load on the system. 
Finally we changed back. I don't know if there is an possible way to  fix this issue. And My 
thinking was if 11g is smarter than 9i and 10g, we may consider upgrading database directly to 11g. 
Then, we can keep using "FORCE" without bind peeking. Just concern if 11g is stable enough. 

And I will ask the web team about the order by issue. It doesn't make much sense for me too.
        


Followup   September 3, 2008 - 10am Central time zone:

I'd still need to see the CREATE statements here. table and indexes.


this is sort of whacky

to_number (decode (axe.price, 0, null, axe.price)) desc nulls last


if price is a number, that should be:

decode( price, 0, to_number(null), price ) desc nulls last


and since, well, prices are probably never negative (one would think?)
order by price desc nulls last
would do it - zero would be at the bottom of a descending list (followed by anything that was null)
5 stars bitmap index bind variable   September 5, 2008 - 3pm Central time zone
Reviewer: YAN 
Hey Tom:

    here is the DDL:

CREATE TABLE AX_RESULTS_ENHANCED ( 
"ARE_ID" NUMBER, 
"REGION_CODE" VARCHAR2(2), 
"STATE_ID" NUMBER, 
"CITY_ID" NUMBER, 
"YEAR" NUMBER, 
"PRICE" NUMBER, 
"SEARCH_ORDER" NUMBER, 
"CONSUMER_ID" NUMBER, 
"LATITUDE" NUMBER, 
"LONGITUDE" NUMBER, 
"ZIP_CODE" VARCHAR2(20), 
"CERTIFICATION" VARCHAR2(20), 
"NEW_USED" VARCHAR2(2), 
"LANGUAGE_FLAG" VARCHAR2(1), 
"NUM_PAGES" VARCHAR2(50), 
"SHOW_IMAGE" VARCHAR2(1), 
"BOOK_STYLE" VARCHAR2(50), 
"PACKAGE_STYLE" VARCHAR2(50), 
"GIFT_SIGN" VARCHAR2(50), 
"CONTACT_1" VARCHAR2(50), 
"CONTACT_2" VARCHAR2(50), 
"EXTERIOR_COLOR" VARCHAR2(50), 
"LAST_UPDATE" DATE) 
TABLESPACE "AX_DATA" ;

and bitmap indexes on single column:

price
year
latitude
longitude
show_image

so this is a query from drop down, if end users select any of the price range, year, or zip code, 
the value will win over the default value. If nothing is being selected, default value will be used 
in the query.

and another question, I changed the query according the last  talk:

order by decode (axe.search_order, 20, 1, 50, 1, 75, 1, 90, 2, 99, 3, axe.search_order) asc and 
axe.price desc nulls last 

but I couldn't see any change on the consistent gets and the time. and the plan is the same too. 
how come? and is that because all the results are cached in the memory? There should be some 
difference right?



Followup   September 5, 2008 - 5pm Central time zone:

yes, likely bind peeking, think about it, if someone does the query with

latitude between 0 and 180 -- 50% of the data...

and so on - it would say initially "wow, ton of stuff", indexes would be a bad idea. especially with all of the bitmap anding it would have to do with really large bitmap results

what do the estimated rows in v$sql_plan look like.




5 stars binding   September 5, 2008 - 7pm Central time zone
Reviewer: A reader 


5 stars bitmap index bind variable   September 6, 2008 - 4pm Central time zone
Reviewer: reader 
Hey Tom:

      sorry, I did not follow you. what do you mean by "estimated rows in v$sql_plan"?


Followup   September 8, 2008 - 3pm Central time zone:

v$sql_plan has the rows the optimizer estimated would be returned by a given step in a execution plan, like the PLAN_TABLE does.
5 stars bitmap index   September 8, 2008 - 6pm Central time zone
Reviewer: reader 
gotcha, I will check it out later. Now I have some other questions.

for regular b* tree index,  if through the equality predicate, the number of rows is limited to 
very small number, then index on this column is easily to be picked up. is this the same for bitmap 
index? 

I have a query, with the year in it:

SELECT  axe.ad_id
FROM ax_results_enhanced axe
WHERE axe.year between 1972
and 1972
and rownum <= 500;

>>>> full scan
31009  consistent gets

SELECT  AXe.ad_id
FROM ax_results_enhanced axe
WHERE axe.year between 1992
and 1992
and rownum <= 500;

>>>> full scan

SELECT axE.ad_id
FROM ax_results_enhanced axe
WHERE axe.year between 2008
and 2008
and rownum <= 500;

>>>> full scan



SELECT /*+ index(axe AXRESENH2_YEAR_BITIDX) */ Axe.ad_id
FROM ax_results_enhanced axe
WHERE axe.year between 1972
and 1972
and rownum <= 500;

>>>> pick up year bitmap index, and gets is 49

>>>> I changed the query a little bit every time, like from low case to upper case, so the query 
will be treated as different query and will not be binded. 

select count(*) from ax_results_enhanced axe where year between 1972 and 1972;
        14
select count(*) from ax_results_enhanced axe where year between 1992 and 1992;

      3096
select count(*) from ax_results_enhanced axe where year between 2008 and 2008;

    230834

>>>> sounds like no matter how many rows return, it always pick up full scan. even the year 1972 
only return 14 rows.
Why? I am confused.



5 stars bitmap index   September 11, 2008 - 3pm Central time zone
Reviewer: reader 
Hey Tom:

     Did you see my above questions?

Thank you!


Followup   September 16, 2008 - 12pm Central time zone:

no, i didn't. you are "a reader", I have no clue what two you refer to.


I'll assume you meant the one above since I addressed one two up.


One would need more information. Like a test case. If I were to create a table like the one you describe and put in just those three years, it does in fact use an index.

I sort of "presume" AD_ID is *really* ARE_ID (eg: your examples do not 'add up')

ops$tkyte%ORA10GR2> /*
ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> drop TABLE AX_RESULTS_ENHANCED ;
ops$tkyte%ORA10GR2> CREATE TABLE AX_RESULTS_ENHANCED (
ops$tkyte%ORA10GR2> "ARE_ID" NUMBER,
ops$tkyte%ORA10GR2> "REGION_CODE" VARCHAR2(2),
ops$tkyte%ORA10GR2> "STATE_ID" NUMBER,
ops$tkyte%ORA10GR2> "CITY_ID" NUMBER,
ops$tkyte%ORA10GR2> "YEAR" NUMBER,
ops$tkyte%ORA10GR2> "PRICE" NUMBER,
ops$tkyte%ORA10GR2> "SEARCH_ORDER" NUMBER,
ops$tkyte%ORA10GR2> "CONSUMER_ID" NUMBER,
ops$tkyte%ORA10GR2> "LATITUDE" NUMBER,
ops$tkyte%ORA10GR2> "LONGITUDE" NUMBER,
ops$tkyte%ORA10GR2> "ZIP_CODE" VARCHAR2(20),
ops$tkyte%ORA10GR2> "CERTIFICATION" VARCHAR2(20),
ops$tkyte%ORA10GR2> "NEW_USED" VARCHAR2(2),
ops$tkyte%ORA10GR2> "LANGUAGE_FLAG" VARCHAR2(1),
ops$tkyte%ORA10GR2> "NUM_PAGES" VARCHAR2(50),
ops$tkyte%ORA10GR2> "SHOW_IMAGE" VARCHAR2(1),
ops$tkyte%ORA10GR2> "BOOK_STYLE" VARCHAR2(50),
ops$tkyte%ORA10GR2> "PACKAGE_STYLE" VARCHAR2(50),
ops$tkyte%ORA10GR2> "GIFT_SIGN" VARCHAR2(50),
ops$tkyte%ORA10GR2> "CONTACT_1" VARCHAR2(50),
ops$tkyte%ORA10GR2> "CONTACT_2" VARCHAR2(50),
ops$tkyte%ORA10GR2> "EXTERIOR_COLOR" VARCHAR2(50),
ops$tkyte%ORA10GR2> "LAST_UPDATE" DATE)
ops$tkyte%ORA10GR2> /
ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> exec gen_data( 'AX_RESULTS_ENHANCED', 14+ 3096 + 230834 );
ops$tkyte%ORA10GR2> update AX_RESULTS_ENHANCED set year = case when rownum <= 14 then 1972 when 
rownum <= 14+3096 then 1992 else 2008 end;
ops$tkyte%ORA10GR2> create bitmap index bm_idx on AX_RESULTS_ENHANCED(year);
ops$tkyte%ORA10GR2> */
ops$tkyte%ORA10GR2> exec dbms_stats.gather_table_stats( user, 'AX_RESULTS_ENHANCED', method_opt => 
'for all indexed columns' );

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> @at
ops$tkyte%ORA10GR2> column PLAN_TABLE_OUTPUT format a72 truncate
ops$tkyte%ORA10GR2> set autotrace traceonly explain
ops$tkyte%ORA10GR2> SELECT  axe.are_id
  2  FROM ax_results_enhanced axe
  3  WHERE axe.year between 1972
  4  and 1972
  5  and rownum <= 500;

Execution Plan
----------------------------------------------------------
Plan hash value: 3845896283

------------------------------------------------------------------------
| Id  | Operation                     | Name                | Rows  | By
------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                     |     1 |
|*  1 |  COUNT STOPKEY                |                     |       |
|   2 |   TABLE ACCESS BY INDEX ROWID | AX_RESULTS_ENHANCED |     1 |
|   3 |    BITMAP CONVERSION TO ROWIDS|                     |       |
|*  4 |     BITMAP INDEX SINGLE VALUE | BM_IDX              |       |
------------------------------------------------------------------------

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

   1 - filter(ROWNUM<=500)
   4 - access("AXE"."YEAR"=1972)

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> SELECT  AXe.are_id
  2  FROM ax_results_enhanced axe
  3  WHERE axe.year between 1992
  4  and 1992
  5  and rownum <= 500;

Execution Plan
----------------------------------------------------------
Plan hash value: 3845896283

------------------------------------------------------------------------
| Id  | Operation                     | Name                | Rows  | By
------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                     |   500 | 13
|*  1 |  COUNT STOPKEY                |                     |       |
|   2 |   TABLE ACCESS BY INDEX ROWID | AX_RESULTS_ENHANCED |   501 | 13
|   3 |    BITMAP CONVERSION TO ROWIDS|                     |       |
|*  4 |     BITMAP INDEX SINGLE VALUE | BM_IDX              |       |
------------------------------------------------------------------------

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

   1 - filter(ROWNUM<=500)
   4 - access("AXE"."YEAR"=1992)

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> SELECT axE.are_id
  2  FROM ax_results_enhanced axe
  3  WHERE axe.year between 2008
  4  and 2008
  5  and rownum <= 500;

Execution Plan
----------------------------------------------------------
Plan hash value: 1922096437

------------------------------------------------------------------------
| Id  | Operation          | Name                | Rows  | Bytes | Cost
------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                     |   500 | 13000 |    16
|*  1 |  COUNT STOPKEY     |                     |       |       |
|*  2 |   TABLE ACCESS FULL| AX_RESULTS_ENHANCED |   501 | 13026 |    16
------------------------------------------------------------------------

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

   1 - filter(ROWNUM<=500)
   2 - filter("AXE"."YEAR"=2008)

ops$tkyte%ORA10GR2> set autotrace off

5 stars bind variables as input to a procedure   October 29, 2008 - 10am Central time zone
Reviewer: VLS from Bombay, India
Hi Tom,

I do understand the importance of bind variables and follow this practice in recommending to 
implement bind variables. While investigating a performance issue and executing your procedure to 
get the queries that are using literals, I could see many database procedures that are executed 
through out the day and using literals. For example,

SQL_TEXT                                                               
---------------------------------------------------------------------- 
Executions : 4223

BEGIN E_VALUES('12-MAY-2000','MP'); END;

This procedure has been executed 4223 times with differnt literals. How can it be modified to make 
use of Bind Variables ?

Would appreciate if you can help me out in implementing bind in this case.

Regards
VLS


Followup   October 29, 2008 - 1pm Central time zone:

the same way

select * from emp where empno = 1;
select * from emp where empno = 2;
..... and so on

would be - the APPLICATION WOULD USE BINDS.

and since I don't have a clue in the world what language you are using - I cannot point you to an existing code snippet, but basically - look in your code where you do bind, there is the code snippet you need.

Write a Review
 


All information and materials provided here are provided "as-is"; Oracle disclaims all express and implied warranties, including, the implied warranties of merchantability or fitness for a particular use. Oracle shall not be liable for any damages, including, direct, indirect, incidental, special or consequential damages for loss of profits, revenue, data or data use, incurred by you or any third party in connection with the use of this information or these materials.

About Oracle | Legal Notices and Terms of Use | Privacy Statement