Home>Question Details



Vinod -- Thanks for the question regarding "Tuning SQL Statements Using Explain Plan", version 8i

Submitted on 20-May-2000 4:51 Central time zone
Last updated 18-Jan-2010 16:34

You Asked

Hi Tom , I am new in tuning sql statements. Can u give a methodology of tuning the sql 
statements and to predict the output of explain plan.
When i use explain plan for a particular query, i am able to understand what the result 
means. can u pls clarify it for me in detail.

Another doubt is that while using Powerbuilder 7.0 with Oracle8i rel 8.1.5 , PB gets 
hanged while going to the datawindow SQL painter . This happens only when OPTIMIZER_MODE 
= COST. This problem is ridiculous for our development team and our team needs 
clarification from u.

Thanks
Vinod 

and we said...

As for "a methodolody to predict the output of explain plan" -- I'm not sure at all what 
you are looking for.  Do you mean "how do I interpret it"?  If so please see:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/ex_plan.htm#16972
In fact, the entire document:

http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/toc.htm
which is the server tuning guide, will be invaluable to you in learning this.  It covers 
all of the access plans and such so you'll know what a SORT MERGE JOIN versus NESTED 
LOOPS means.

As for the second problem -- it sounds like a bad query plan is being generated for the 
given query.   PB isn't really hung in all probability but the query is taking a very 
long time to complete.  You say you've set the optimizer_mode = cost, but values for 
optimizer_mode are 

o RULE
o CHOOSE (uses CBO if statistics are present, RBO otherwise)
o FIRST_ROWS (find a plan to get the first row the fastest using 
              the CBO)
o ALL_ROWS (find a plan to get the last row the fastest using
            the CBO)


So, I'll assume you've set the optimizer_mode to FIRST_ROWS or ALL_ROWS.  My question 
back to you would be -- have you analyzed the tables or have you just set the 
optimizer_mode.  If you have not analyzed the tables recently -- with their current set 
of data, then the plans generated by the optimizer can be quite bad indeed.


I would suggest as a way to see what is really happening -- to read the server tuning 
manual and find all about SQL_TRACE, TIMED_STATISTICS, and TKPROF.  Those three 
things are the most powerful application tuning tools out there.  They will show you the 
SQL your application is submitting to the database, the plans used to run it, how many 
rows flowed through each step of the plan, how many rows were returned to the client, how 
many fetches took place to get those rows, how much CPU it took and how much wall clock 
time it took (plus lots more information).

 

Reviews    
5 stars Tuning in development, test, production   April 23, 2002 - 12pm Central time zone
Reviewer: Schesser 
Tom, I'm a regular visitor toyour website, and have quite a bit on tuning on your forum.

I was in a meeting today, and was surprised to hear from a lot of developers that they are under 
the opinion that a sql tuned in development will work the same way in test and in production. This 
is in a case where the dev and test database is refreshed everyday.

I disagree with them, even though the database is refreshed every day.

My point of view is that  even though everything is the same datawise in d1 , t1 and p1, the 
OPTIMIZER will not necessarily decide on the same execution path in all the 3 instances.

My standpoint is based ont he facts

1.The hardware is not the same for the 3 instances.
2.More number of users access the production database. If the number of users in development is 
100, then it is 1000 in production.
3.When contention for objects increases(which is the case in production) the OPTIMIZER will for 
sure take different  decesions as far as execution path is concerned.

Can you give your valuable expert opinion on the above.
1.Is my contention right? If so can you give me some more factors which make the up the difference 
in performance between devolopment and production.
2.How can I benchmark my standpoint and show it on paper that tuning in development not necessarily 
meants the sql will do fine in Production. 


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

If you use RBO -- the plans will probably never change.  It depends -- most likely they will be the 
same (but create your indexes in a different order and we might use different indexes)


If you use CBO -- the plans can and will change.  They can change from day to day.
 

4 stars Query plan chaging question?   April 28, 2002 - 10pm Central time zone
Reviewer: William from Australia
Tom,

Would the query plan change if the table/index statistics
are not re-gathered or any initialisation parameters changed? 


Followup   April 29, 2002 - 7am Central time zone:

The plans should stay constant if all other things are constant (the software is deterministic, 
given the same inputs, it'll come to the same conclusion)

 

4 stars   April 29, 2002 - 12pm Central time zone
Reviewer: Mike 
without the stats, and set hint with FIRST_ROWS. The explain still shown the cost=something, which 
the costs come? I assume Oracle will use rule, if no stats:

SQL>  select /*+ first_rows */ count(*) from tt where object_id=1000;


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1 Card=1 B
          ytes=13)

   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'TT_IO_IDX' (NON-UNIQUE) (Cost=1 C
          ard=20 Bytes=260)
 


Followup   April 29, 2002 - 12pm Central time zone:

Your assumption is wrong.  Oracle makes up statistics when you demand the use of the CBO.  It does 
this based on cached data dictionary information (how many extents, whats the high water mark and 
so on)


Watch the cost/card go up and down (notice that a flush must take place, else the cached data 
dictionary info is used)

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t as select * from all_objects where 1=0;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ first_rows */ * from t t1;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1 Card=82 Bytes=10496)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=82 Bytes=10496)



ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from all_objects;

22907 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from all_objects;

22907 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert /*+ append */ into t select * from all_objects;

22907 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> alter system flush shared_pool;

System altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ first_rows */ * from t t2;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=28 Card=77188 Bytes=9880064)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=28 Card=77188 Bytes=9880064)



ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from t;

68721 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter system flush shared_pool
  2  /

System altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ first_rows */ * from t t3;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=28 Card=77188 Bytes=9880064)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=28 Card=77188 Bytes=9880064)



ops$tkyte@ORA817DEV.US.ORACLE.COM> truncate table t;

Table truncated.

ops$tkyte@ORA817DEV.US.ORACLE.COM> alter system flush shared_pool;

System altered.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ first_rows */ * from t t4;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=1 Card=82 Bytes=10496)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=82 Bytes=10496)
 

4 stars   August 8, 2002 - 10am Central time zone
Reviewer: Rajiv from NJ, USA
Hi Tom,

I learned a lot from your site. The way you explain things with examples is superb. Thanks for all 
your help.

I would like to ask you about tuning complex select statements with several joins and outer joins 
(the ones geneated by OLAP tools etc). Explain plan would show the access path and SQL trace the 
wait statistics. That will help us to see if the access path is optimum or if there are any 
inordinate waits. But I feel sometimes in case of complex statements, a total re-write or even 
design changes would be necessary to achieve better response time. What should be the principles of 
re-writing an SQL statement effectively? Inorder to illustrate what I am trying to ask, let me post 
the predicate of a long SQL I am trying to tune:

----------------------
where  m.material = ms.mat_sales
  and  ms.matl_grp_2 = g.matl_grp_2
  and  ms."/BIC/ZCPRDSTAT" = ps."BIC_ZCPRDSTAT"
  and  ms.salesorg = '1010'
  and  ms.distr_chan = '01'
  and  ms.mat_sales = pz.material (+)
  and  ms.plant = pz.plant (+)
  and  pz.comp_code (+) = '101'
  and  pz.val_class (+) = '7920'
  and  pz.fiscvarnt (+) = 'K4'
  and  pz.fiscper (+) = mercator.fisc_per
  and  m.division = d.division
  and  m."/BIC/ZCLANGUAE" = le."BIC_ZCLANGUAE"
  and  m."/BIC/ZCLANGUAG" = lg."BIC_ZCLANGUAG"
  and  m.matl_group = mg.matl_group
  and  m."/BIC/ZCDESIGN" = mt."BIC_ZCDESIGN"
  and  m."/BIC/ZCPRODCAT" = mp."BIC_ZCPRODCAT"
  and  m."/BIC/ZCPRDCODE" = pc."BIC_ZCPRDCODE"
  and  m."/BIC/ZCRELEASE" = mr."BIC_ZCRELEASE"
  and  m."/BIC/ZCSERIES"  = s."BIC_ZCSERIES"
  and  m."/BIC/ZCSUBCAT1" = sc1."BIC_ZCSUBCAT1"
  and  m."/BIC/ZCSUBCAT2" = sc2."BIC_ZCSUBCAT2"
  and  m."/BIC/ZCSUBCAT3" = sc3."BIC_ZCSUBCAT3"
  and  m.material = mu.material (+)
  and  mu.mat_unit (+) = 'CS'
  and  m."/BIC/A_ISBN" || '.1' = w.wbs_elemt (+)

And part of the Explain plan on a select count(*) using this query as an inline view is:

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=21521004692346200000
          0 Card=1 Bytes=282)

   1    0   SORT (AGGREGATE)
   2    1     HASH JOIN (OUTER) (Cost=215210046923462000000 Card=57211
          36614938280000000000 Bytes=1613360525412600000000000000)

   3    2       NESTED LOOPS (Cost=5559474405851140 Card=7711673875745
          780000000 Bytes=2066728598699870000000000)

   4    3         HASH JOIN (Cost=5559474405851140 Card=77116738757457
          80000000 Bytes=2028170229321140000000000)

   5    4           VIEW OF 'BIC_TZCSUBCAT3_BO_VW' (Cost=8 Card=732 By
          tes=4392)

   6    5             SORT (UNIQUE) (Cost=8 Card=732 Bytes=13000)
   7    6               UNION-ALL
   8    7                 TABLE ACCESS (FULL) OF '/BIC/TZCSUBCAT3' (Co
          st=1 Card=650 Bytes=13000)

----------

Except one or two, the tables are'nt that big. Also, eventhough we have indexes on most of the join 
columns, many of them are not being used. We have many such queries and we will have more data in 
Production. What can we do to to speed up such queries. I hope I am clear in what I am asking. 
Wating for your reply.

Rajiv.

 


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

Are those cardinality counts right?  

Indexes do not imply, mean, infer or suggest "faster".  In this query, it appears most of the rows 
in most of the tables would be accessed --hashing is better for that then slower index reads.

Is that the full plan?  

(bummer on those table and column names -- cannot believe someone would do that do you, slashes and 
all...)

 

5 stars   August 8, 2002 - 1pm Central time zone
Reviewer: A reader 
Ya, those CARD numbers were scary. It was not the full plan. Let me post both the query and the 
plan here.

About the slashes on the table names: This is SAP Business Warehouse. This is the way they name the 
tables and columns. It was hard for me too to believe when I saw it first.

We have a number of small views (union of 1 or 2 tables with dual) used in the query. (the ones 
with _VW are views).

Here's the query and plan:

SQL> set autot traceonly explain
SQL> 
SQL> 
SQL> ed
Wrote file afiedt.buf

  1  select
  2    m.material, m."/BIC/A_ISBN",
  3    m."/BIC/ZKAGERAFR", m."/BIC/ZKAGERATO",
  4    m."/BIC/ZCAUDIENC", m."/BIC/ZCARTIST", m."/BIC/ZCDISPOSA",
  5    m.EANUPC, m.DIVISION, m."/BIC/ZKEXTENT",
  6    m."/BIC/ZKGRARAFR", m."/BIC/ZKGRARATO",
  7    m."/BIC/ZCLANGUAE", m."/BIC/ZCLANGUAG",
  8    m."/BIC/ZCTXTGRUN", m."/BIC/ZCTXTGRU1", m."/BIC/ZCTXTGRU2",
  9    m.MATL_TYPE, m.MATL_GROUP, m."/BIC/ZCDESIGN", m."/BIC/ZCOUTPRDT",
 10    m."/BIC/ZCPRODCAT", ms.PROD_HIER, m."/BIC/ZCPRDCODE", m."/BIC/ZCPUBLDAT",
 11    m."/BIC/ZCRELEASE", m."/BIC/ZC1SHPDAT",
 12    m."/BIC/ZCSUBCAT1", m."/BIC/ZCSUBCAT2", m."/BIC/ZCSUBCAT3",
 13    m.LENGHT, m."/BIC/ZCSUBTIT1", m."/BIC/ZCSUBTIT2", m."/BIC/ZCSUBTIT3",
 14    m.HEIGHT, m.WIDTH, m. "/BIC/ZKVOLNUM", m.NET_WEIGHT,
 15    m."/BIC/ZCSERIES", ms.matl_grp_1, ms.matl_grp_2,
 16    ms."/BIC/ZCONSALED", ms."/BIC/ZCPRDSTAT", ms.PLANT,
 17    d.DIVISION_DESC, le.BIC_ZCLANGUAE_DESC, lg.BIC_ZCLANGUAG_DESC,
 18    mg.MATL_GROUP_DESC, mt.BIC_ZCDESIGN_DESC,
 19    mp.BIC_ZCPRODCAT_DESC, pc.BIC_ZCPRDCODE_DESC, mr.BIC_ZCRELEASE_DESC,
 20    g.MATL_GRP_2_DESC, s.BIC_ZCSERIES_DESC,
 21    ps.BIC_ZCPRDSTAT_DESC, sc1.BIC_ZCSUBCAT1_DESC, sc2.BIC_ZCSUBCAT2_DESC,
 22    sc3.BIC_ZCSUBCAT3_DESC,
 23    mu.NUMERATOR, w.PS_RESPNO, m."/BIC/ZCSERIES2", m."/BIC/ZCSERIES3",
 24    pz.PLANT, pz.PRICE_MAT, m."/BIC/ZKPRICECA"
 25  from  sapr3."/BI0/PMATERIAL" m,
 26    sapr3."/BI0/PMAT_SALES" ms,
 27    BOADMIN.BI0_TDIVISION_BO_VW d,
 28    BOADMIN.BIC_TZCLANGUAE_BO_VW le,
 29    BOADMIN.BIC_TZCLANGUAG_BO_VW lg,
 30    BOADMIN.BI0_TMATL_GROUP_BO_VW mg,
 31    BOADMIN.BIC_TZCDESIGN_BO_VW mt,
 32    BOADMIN.BIC_TZCPRODCAT_BO_VW mp,
 33    BOADMIN.BIC_TZCPRDCODE_BO_VW pc,
 34    BOADMIN.BIC_TZCRELEASE_BO_VW mr,
 35    BOADMIN.BI0_TMATL_GRP_2_BO_VW g,
 36    BOADMIN.BIC_TZCSERIES_BO_VW s,
 37    BOADMIN.BIC_TZCPRDSTAT_BO_VW ps,
 38    BOADMIN.BIC_TZCSUBCAT1_BO_VW sc1,
 39    BOADMIN.BIC_TZCSUBCAT2_BO_VW sc2,
 40    BOADMIN.BIC_TZCSUBCAT3_BO_VW sc3,
 41    sapr3."/BI0/PMAT_UNIT" mu,
 42    sapr3."/BI0/PWBS_ELEMT" w,
 43    sapr3."/BIC/AP1OSCIMV00" pz
 44  where  m.material = ms.mat_sales
 45    and  ms.matl_grp_2 = g.matl_grp_2
 46    and  ms."/BIC/ZCPRDSTAT" = ps."BIC_ZCPRDSTAT"
 47    and  ms.salesorg = '1010'
 48    and  ms.distr_chan = '01'
 49    and  ms.mat_sales = pz.material (+)
 50    and  ms.plant = pz.plant (+)
 51    and  pz.comp_code (+) = '101'
 52    and  pz.val_class (+) = '7920'
 53    and  pz.fiscvarnt (+) = 'K4'
 54    and  pz.fiscper (+) = mercator.fisc_per
 55    and  m.division = d.division
 56    and  m."/BIC/ZCLANGUAE" = le."BIC_ZCLANGUAE"
 57    and  m."/BIC/ZCLANGUAG" = lg."BIC_ZCLANGUAG"
 58    and  m.matl_group = mg.matl_group
 59    and  m."/BIC/ZCDESIGN" = mt."BIC_ZCDESIGN"
 60    and  m."/BIC/ZCPRODCAT" = mp."BIC_ZCPRODCAT"
 61    and  m."/BIC/ZCPRDCODE" = pc."BIC_ZCPRDCODE"
 62    and  m."/BIC/ZCRELEASE" = mr."BIC_ZCRELEASE"
 63    and  m."/BIC/ZCSERIES"  = s."BIC_ZCSERIES"
 64    and  m."/BIC/ZCSUBCAT1" = sc1."BIC_ZCSUBCAT1"
 65    and  m."/BIC/ZCSUBCAT2" = sc2."BIC_ZCSUBCAT2"
 66    and  m."/BIC/ZCSUBCAT3" = sc3."BIC_ZCSUBCAT3"
 67    and  m.material = mu.material (+)
 68    and  mu.mat_unit (+) = 'CS'
 69*   and  m."/BIC/A_ISBN" || '.1' = w.wbs_elemt (+)
 70  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=74990665314992100000
          0 Card=5721136614938280000000000 Bytes=530349364204779000000
          0000000)

   1    0   HASH JOIN (OUTER) (Cost=749906653149921000000 Card=5721136
          614938280000000000 Bytes=5303493642047790000000000000)

   2    1     HASH JOIN (Cost=33776078474649500 Card=77116738757457800
          00000 Bytes=6994488205301420000000000)

   3    2       VIEW OF 'BIC_TZCSUBCAT3_BO_VW' (Cost=8 Card=732 Bytes=
          13176)

   4    3         SORT (UNIQUE) (Cost=8 Card=732 Bytes=13000)
   5    4           UNION-ALL
   6    5             TABLE ACCESS (FULL) OF '/BIC/TZCSUBCAT3' (Cost=1
           Card=650 Bytes=13000)

   7    5             TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82)
   8    2       HASH JOIN (Cost=24056545335293200 Card=105350736007456
          00000 Bytes=9365680431062840000000)

   9    8         VIEW OF 'BIC_TZCSUBCAT2_BO_VW' (Cost=8 Card=732 Byte
          s=13176)

  10    9           SORT (UNIQUE) (Cost=8 Card=732 Bytes=13000)
  11   10             UNION-ALL
  12   11               TABLE ACCESS (FULL) OF '/BIC/TZCSUBCAT2' (Cost
          =1 Card=650 Bytes=13000)

  13   11               TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82)
  14    8         HASH JOIN (Cost=24043532550830800 Card=1439217705019
          8900 Bytes=12535586210723300000)

  15   14           VIEW OF 'BIC_TZCSERIES_BO_VW' (Cost=11 Card=1074 B
          ytes=31146)

  16   15             SORT (UNIQUE) (Cost=11 Card=1074 Bytes=30752)
  17   16               UNION-ALL
  18   17                 TABLE ACCESS (FULL) OF '/BIC/TZCSERIES' (Cos
          t=1 Card=992 Bytes=30752)

  19   17                 TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=8
          2)

  20   14           HASH JOIN (Cost=24043509114311200 Card=13400537290
          688 Bytes=11283252398759300)

  21   20             VIEW OF 'BIC_TZCPRODCAT_BO_VW' (Cost=6 Card=152
          Bytes=2280)

  22   21               SORT (UNIQUE) (Cost=6 Card=152 Bytes=1190)
  23   22                 UNION-ALL
  24   23                   TABLE ACCESS (FULL) OF '/BIC/TZCPRODCAT' (
          Cost=1 Card=70 Bytes=1190)

  25   23                   TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card
          =82)

  26   20             HASH JOIN (Cost=24043509089064700 Card=881614295
          44 Bytes=72909502232888)

  27   26               TABLE ACCESS (FULL) OF '/BI0/TMATL_GRP_2' (Cos
          t=1 Card=237 Bytes=6399)

  28   26               HASH JOIN (OUTER) (Cost=24043509040196500 Card
          =88161429544 Bytes=70529143635200)

  29   28                 NESTED LOOPS (OUTER) (Cost=24043435323438600
           Card=744931389402 Bytes=575831964007746)

  30   29                   HASH JOIN (Cost=24043435323438600 Card=168
          706101620750000 Bytes=122311923675044000000)

  31   30                     VIEW OF 'BIC_TZCPRDSTAT_BO_VW' (Cost=6 C
          ard=90 Bytes=1350)

  32   31                       SORT (UNIQUE) (Cost=6 Card=90 Bytes=13
          6)

  33   32                         UNION-ALL
  34   33                           TABLE ACCESS (FULL) OF '/BIC/TZCPR
          DSTAT' (Cost=1 Card=8 Bytes=136)

  35   33                           TABLE ACCESS (FULL) OF 'DUAL' (Cos
          t=1 Card=82)

  36   30                     HASH JOIN (Cost=24040201730831000 Card=1
          3121585681613900 Bytes=9316325833945870000)

  37   36                       TABLE ACCESS (FULL) OF '/BI0/PMAT_SALE
          S' (Cost=481 Card=56618 Bytes=3906642)

  38   36                       HASH JOIN (Cost=13709136541766 Card=19
          2498514928967000 Bytes=123391548069468000000)

  39   38                         VIEW OF 'BIC_TZCDESIGN_BO_VW' (Cost=
          6 Card=215 Bytes=3440)

  40   39                           SORT (UNIQUE) (Cost=6 Card=215 Byt
          es=2128)

  41   40                             UNION-ALL
  42   41                               TABLE ACCESS (FULL) OF '/BIC/T
          ZCDESIGN' (Cost=1 Card=133 Bytes=2128)

  43   41                               TABLE ACCESS (FULL) OF 'DUAL'
          (Cost=1 Card=82)

  44   38                         HASH JOIN (Cost=1250511513346 Card=5
          7301883513739100 Bytes=35813677196087000000)

  45   44                           VIEW OF 'BIC_TZCLANGUAG_BO_VW' (Co
          st=6 Card=137 Bytes=2192)

  46   45                             SORT (UNIQUE) (Cost=6 Card=137 B
          ytes=880)

  47   46                               UNION-ALL
  48   47                                 TABLE ACCESS (FULL) OF '/BIC
          /TZCLANGUAG' (Cost=1 Card=55 Bytes=880)

  49   47                                 TABLE ACCESS (FULL) OF 'DUAL
          ' (Cost=1 Card=82)

  50   44                           HASH JOIN (Cost=9345853174 Card=58
          55666928411300 Bytes=3566101159402480000)

  51   50                             VIEW OF 'BIC_TZCSUBCAT1_BO_VW' (
          Cost=6 Card=130 Bytes=2340)

  52   51                               SORT (UNIQUE) (Cost=6 Card=130
           Bytes=960)

  53   52                                 UNION-ALL
  54   53                                   TABLE ACCESS (FULL) OF '/B
          IC/TZCSUBCAT1' (Cost=1 Card=48 Bytes=960)

  55   53                                   TABLE ACCESS (FULL) OF 'DU
          AL' (Cost=1 Card=82)

  56   50                             HASH JOIN (Cost=75162061 Card=45
          043591757010 Bytes=26620762728392900)

  57   56                               VIEW OF 'BIC_TZCLANGUAE_BO_VW'
           (Cost=6 Card=119 Bytes=2856)

  58   57                                 SORT (UNIQUE) (Cost=6 Card=1
          19 Bytes=814)

  59   58                                   UNION-ALL
  60   59                                     TABLE ACCESS (FULL) OF '
          /BIC/TZCLANGUAE' (Cost=1 Card=37 Bytes=814)

  61   59                                     TABLE ACCESS (FULL) OF '
          DUAL' (Cost=1 Card=82)

  62   56                               HASH JOIN (Cost=357783 Card=37
          8517577790 Bytes=214619466606930)

  63   62                                 VIEW OF 'BIC_TZCRELEASE_BO_V
          W' (Cost=6 Card=102 Bytes=1632)

  64   63                                   SORT (UNIQUE) (Cost=6 Card
          =102 Bytes=360)

  65   64                                     UNION-ALL
  66   65                                       TABLE ACCESS (FULL) OF
           '/BIC/TZCRELEASE' (Cost=1 Card=20 Bytes=360)

  67   65                                       TABLE ACCESS (FULL) OF
           'DUAL' (Cost=1 Card=82)

  68   62                                 HASH JOIN (Cost=11746 Card=3
          710956645 Bytes=2044737111395)

  69   68                                   VIEW OF 'BIC_TZCPRDCODE_BO
          _VW' (Cost=6 Card=101 Bytes=1414)

  70   69                                     SORT (UNIQUE) (Cost=6 Ca
          rd=101 Bytes=304)

  71   70                                       UNION-ALL
  72   71                                         TABLE ACCESS (FULL)
          OF '/BIC/TZCPRDCODE' (Cost=1 Card=19 Bytes=304)

  73   71                                         TABLE ACCESS (FULL)
          OF 'DUAL' (Cost=1 Card=82)

  74   68                                   HASH JOIN (Cost=4855 Card=
          36742145 Bytes=19730531865)

  75   74                                     VIEW OF 'BI0_TMATL_GROUP
          _BO_VW' (Cost=6 Card=94 Bytes=1692)

  76   75                                       SORT (UNIQUE) (Cost=6
          Card=94 Bytes=228)

  77   76                                         UNION-ALL
  78   77                                           TABLE ACCESS (FULL
          ) OF '/BI0/TMATL_GROUP' (Cost=1 Card=12 Bytes=228)

  79   77                                           TABLE ACCESS (FULL
          ) OF 'DUAL' (Cost=1 Card=82)

  80   74                                     HASH JOIN (Cost=3645 Car
          d=6644856 Bytes=3448680264)

  81   80                                       VIEW OF 'BI0_TDIVISION
          _BO_VW' (Cost=6 Card=88 Bytes=1320)

  82   81                                         SORT (UNIQUE) (Cost=
          6 Card=88 Bytes=102)

  83   82                                           UNION-ALL
  84   83                                             TABLE ACCESS (FU
          LL) OF '/BI0/TDIVISION' (Cost=1 Card=6 Bytes=102)

  85   83                                             TABLE ACCESS (FU
          LL) OF 'DUAL' (Cost=1 Card=82)

  86   80                                       TABLE ACCESS (FULL) OF
           '/BI0/PMATERIAL' (Cost=3621 Card=830607 Bytes=418625928)

  87   29                   TABLE ACCESS (BY INDEX ROWID) OF '/BIC/AP1
          OSCIMV00'

  88   87                     INDEX (RANGE SCAN) OF '/BIC/AP1OSCIMV00~
          0' (UNIQUE)

  89   28                 TABLE ACCESS (FULL) OF '/BI0/PMAT_UNIT' (Cos
          t=234 Card=98301 Bytes=2654127)

  90    1     TABLE ACCESS (FULL) OF '/BI0/PWBS_ELEMT' (Cost=101 Card=
          74188 Bytes=1483760)


Thanks for your time.

Rajiv. 


Followup   August 8, 2002 - 2pm Central time zone:

Well, I do believe that a cost of Seven Hundred Forty-Nine quintillion Nine Hundred Six quadrillion 
Six Hundred Fifty-Three trillion One Hundred Forty-Nine billion Nine Hundred Twenty-One million is 
the largest cost I've ever seen!


The plan looks appropriate however, given the predicate -- you don't have many predicates other 
then joins in there meaning the full table scans are appropriate.

You might want to look at upping the hash_area_size and utilizing parallel query to help with these 
objects.
 

4 stars   August 8, 2002 - 4pm Central time zone
Reviewer: A reader 
Oh! I didn't know I was working on the costliest query in the whole world!!

Thanks for the suggestion. I was alerady trying to use parallel excution on this query. I will 
increase the hash area too. 

I am also trying to re-write the query by creating a table/mv replacing those views: they are views 
on lookup tables where the data does not change. Do you think it will help?

Also would you suggest any other re-writing tips: like replace the outer join with subqueries or 
anything like that? Thanks for your help.

Rgds,
Rajiv. 


Followup   August 9, 2002 - 7am Central time zone:

You might try selecting a select, instead of:

  1  select
....
 23    mu.NUMERATOR, w.PS_RESPNO, m."/BIC/ZCSERIES2", m."/BIC/ZCSERIES3",
....
 69*   and  m."/BIC/A_ISBN" || '.1' = w.wbs_elemt (+)
 70  /

You can 


  1  select
....
 23    mu.NUMERATOR, 
      (select w.PS_RESPNO 
           from sapr3."/BI0/PWBS_ELEMT" w 
          where w.wbs_elemt = m."/BIC/A_ISBN" ), 
      m."/BIC/ZCSERIES2", m."/BIC/ZCSERIES3",
....
 69*   
 70  /


and remove W from the "from" list in the major query.  You might try that for some of the other 
outer-joined to things as well.


Additionally -- determine IF in fact you TRULY NEED the outer joins in all cases -- I've found many 
times they are there simply "in case".  outer joins can be very expensive.

Doing that select of a select with the SC1, SC2, SC3 tables might be dramatic as well -- may find 
that better then a join join join join (then again, might not be, give it a try) 

5 stars   August 10, 2002 - 5pm Central time zone
Reviewer: A reader 
Tom, I tried with the subquery, but it didnt make much difference. The query runs for about half 
hour still.

I loaded all the tables in another system and tried to run the query there. Amazingly, it was done 
in less than 3mins. The plan looks mostly similar, but the cost and card numbers are far less. I 
compared the buffers and other init parameters of the two systems. While they are different, they 
were not excessively different. Also the opt* init parameters were same. On both the systems the 
tables involved were of same size and the stats were current. The first (slow) system was a 12 CPU 
RS/6000 AIX 4.3. There are 2 other instances running on this box. The second system was a 24 CPU 
RS/6000 AIX 5 with only one database running. 

If you are interested in moving this thread, I can post the details of comparison. Else, I would 
like to have your colsing comments on why we had such excessive costs on the first system. Can it 
be attributed to the smaller CPU strength alone? How can I find more info on what is going on?

Thanks for your time and valuable suggestions. Appreciate it a lot.

Rgds,
Rajiv. 


Followup   August 11, 2002 - 9am Central time zone:

Are the STATS the same (up to date) in both boxes.   

3 stars Explain Plan document.   January 16, 2003 - 1pm Central time zone
Reviewer: Kashif from Houston, TX
Hi Tom,

The Explain Plan document you provided the link to (to the original poster) does not adequately 
explain how the Explain Plan is actually supposed to be interpreted. For example, I understand that 
an explain is supposed to be read innermost to outermost, but that info is not mentioned in the 
document. I also tried looking for this info in your book but was unable to get much information. 
Do you have any other documents/links which might explain how to interpret the Explain Plan more 
thoroughly? Thanks.

Kashif 


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

Ok, tell me if this helps.  It is an excerpt from my forth coming book:


1.3.3 Reading an Explain Plan

This is a frequently asked question - how exactly do you read an explain plan.  Here I will present 
my 'simple' approach to reading the plan.  I do suggest however that a quick read of the chapter in 
the Oracle Performance Guide - the chapter on the explain plan command - would be very useful as 
well.

For all of the details on reading an explain plan, please refer to the Oracle Performance Guide. 
There are complete details on how to read the query plan and interpret the results.

We'll take a look at a query plan resulting from a query against the SCOTT/TIGER tables (note, I 
add primary keys to the EMP and DEPT tables - hence, they are indexed):

scott@ORA920> delete from plan_table;
7 rows deleted.

scott@ORA920> explain plan for
  2  select ename, dname, grade
  3    from emp, dept, salgrade
  4   where emp.deptno = dept.deptno
  5     and emp.sal between salgrade.losal and salgrade.hisal
  6  /
Explained.

scott@ORA920> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------
| Id  | Operation                    |Name     |Rows|Bytes|Cost |
-----------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |    |     |     |
|   1 |  NESTED LOOPS                |         |    |     |     |
|   2 |   NESTED LOOPS               |         |    |     |     |
|   3 |    TABLE ACCESS FULL         | SALGRADE|    |     |     |
|*  4 |    TABLE ACCESS FULL         | EMP     |    |     |     |
|   5 |   TABLE ACCESS BY INDEX ROWID| DEPT    |    |     |     |
|*  6 |    INDEX UNIQUE SCAN         | DEPT_PK |    |     |     |
-----------------------------------------------------------------

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

   4 - filter("EMP"."SAL"<="SALGRADE"."HISAL" AND 
              "EMP"."SAL">="SALGRADE"."LOSAL")
   6 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")

Note: rule based optimization

21 rows selected.

Now - what happens first?  How does that plan actually get evaluated?  First I'll show you the 
psuedo code for how the plan is evaluated and then we'll discuss how I arrived at this conclusion:

For salgrade in (select * from salgrade) 
Loop
   For emp in ( select * from emp )
   Loop
      If ( emp.sal between salgrade.losal and salgrade.hisal )
      Then
          Select * into dept_rec
            From dept
           Where dept.deptno = emp.deptno;

          OUTPUT RECORD with fields from salgrade,emp,dept
      End if;
   End loop;
End loop; 
   
The way I read the plan was to turn it into a graph of sorts - an evaluation tree.  In order to do 
that, we need to understand something about access paths.

For detailed information on the access paths available to Oracle, please see the Oracle Performance 
and Tuning Guide.

In order to build the tree - we can start at the top, with step 1.  That will be our "root node" in 
the tree.  Next, we need to find the things that "feed" this root node - that will be steps 2 and 5 
- as you can see - 2 and 5 are at the same level of indentation - they "feed" into step 1.  
Further, we can see that steps 3 and 4 feed step 2 and that step 6 feeds step 5.  Putting that 
together iteratively - we would draw:


                                1
                               / \
                              2   5
                             / \   \
                            3   4   6
                    
 
And then just read the tree.  In order to get 1 we need 2 and 5 - 2 is "first".  In order to get 2, 
we need 3 and 4.  3 is "first".  That is how I arrived at the psuedo code for:

For salgrade in (select * from salgrade) 
Loop
   For emp in ( select * from emp )
   Loop

Full scan SALGRADE is step 3, full scan EMP is step 4 and step 2 is a nested loop - which is 
roughly equivalent to two "for loops".  Once we get step 2 going like that - we can look at step 5. 
 Step 5 runs step 6 first - step 6 is the index scan step.  We are taking the output of step 2 here 
and using that to "feed" this part of the query plan.  So, the output from step 2 is used to 
perform an index scan - that index scan output is used to TABLE ACCESS BY ROWID the DEPT table and 
that result is the output of step 1 - our result set.

Now, to make this "interesting", we will run an equivalent query - but we'll mix up the order of 
the tables in the from clause this time.  Since I am using the rule based optimizer - this will 
affect the generated query plan (and is just one reason why you DON'T want to use the rule based 
optimizer! We'll cover more reasons in a later section).  We'll use the same logic to build its 
query plan tree and evaluate how it processed the query:

scott@ORA920> delete from plan_table;
7 rows deleted.

scott@ORA920> explain plan for
  2  select ename, dname, grade
  3    from salgrade, dept, emp
  4   where emp.deptno = dept.deptno
  5     and emp.sal between salgrade.losal and salgrade.hisal
  6  /
Explained.

scott@ORA920> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------
| Id  | Operation                     |  Name       | Rows  | Bytes
-------------------------------------------------------------------
|   0 | SELECT STATEMENT              |             |       |      
|   1 |  NESTED LOOPS                 |             |       |      
|   2 |   NESTED LOOPS                |             |       |      
|   3 |    TABLE ACCESS FULL          | EMP         |       |       
|   4 |    TABLE ACCESS BY INDEX ROWID| DEPT        |       |       
|*  5 |     INDEX UNIQUE SCAN         | DEPT_PK     |       |       
|*  6 |   TABLE ACCESS FULL           | SALGRADE    |       |       
-------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")
   6 - filter("EMP"."SAL"<="SALGRADE"."HISAL" AND 
              "EMP"."SAL">="SALGRADE"."LOSAL")
Note: rule based optimization

21 rows selected.

Here we see that steps 2 and 6 feed 1, steps 3 and 4 feed 2, and step 5 feeds 4.  Drawing the tree:
                                1
                               / \
                              2   6
                             / \   
                            3   4
                                 \
                                 5   
 
So, the psuedo code logic here is - starting with steps 3 and 4:

For emp in ( select * from emp )
Loop
   -- using the index
   Select * from dept where dept.deptno = emp.deptno
  
   For salgrade in (select * from salgrade )
   Loop
      If ( emp.sal between salgrade.losal and salgrade.hisal ) 
      Then
         OUTPUT RECORD;
      End if;
   End loop
End loop;

And that is it - if you draw the graphical tree like that and then read it bottom up, left to 
right, you'll get a good understanding of the "flow" of the data.  
 

5 stars Fabulous!   January 17, 2003 - 6pm Central time zone
Reviewer: Kashif from Houston, TX
Hi Tom,

Thanks for that excellent easy-to-follow explanation. I understand the plan much better now. I'm 
just not quite getting my particular plan:

batchcode@QGF1> get ins2cpi.sql
  1  insert into hmc.cpi_lnum (lnum, clnum, Loan_Type, Loan_amount,
  2                            RESEND_FLAG, PRVS_LOAD, Error_flag)
  3  (
  4  select /*+ ORDERED PUSH_SUBQ INDEX (dt pk_dates) */
  5         dt.lnum, tr.clnum, pr.s_ltype, pr.ola , 'F', 1, 'N'
  6    from chk_screen c,
  7         hmc_dates hmc,
  8         dates dt,
  9         hmv_current_status hmv,
 10         tracking tr,
 11         product pr
 12   where c.lnum = hmc.lnum (+)
 13     and c.lnum = dt.lnum
 14     and c.lnum = hmv.lnum
 15     and c.lnum = tr.lnum
 16     and c.lnum = pr.lnum
 17     and dt.lnfundat >= to_date ( '10111999', 'MMDDYYYY' )
 18     and dt.lnfundat <= to_date ( '12182002', 'MMDDYYYY' )
 19     and hmc.send_to_cpi is null
 20     and c.s_checkstatus = 'CS_CMPT'
 21     and hmv.status_code||'' >= 700
 22     and hmv.status_code||'' <= 2300
 23     and length (tr.clnum) >= 10
 24     and not exists (select null
 25                       from wireinfo w
 26                      where w.lnum = c.lnum)
 27* )
batchcode@QGF1> get queryplan.txt
        ID Query_Plan
---------- -------------------------------------------------------
         0 INSERT STATEMENT   Cost = 71
         1   NESTED LOOPS
         2     NESTED LOOPS
         3       NESTED LOOPS
         4         NESTED LOOPS
         5           FILTER
         6             NESTED LOOPS OUTER
         7               TABLE ACCESS FULL CHK_SCREEN
         8                 INDEX RANGE SCAN PK_WIREINFO
         9               TABLE ACCESS BY INDEX ROWID HMC_DATES
        10                 INDEX UNIQUE SCAN HMC_DATES
        11           TABLE ACCESS BY INDEX ROWID DATES
        12             INDEX UNIQUE SCAN PK_DATES
        13         TABLE ACCESS BY INDEX ROWID HMV_CURRENT_STATUS
        14           INDEX UNIQUE SCAN PK_HMV_CURRENT_STATUS
        15       TABLE ACCESS BY INDEX ROWID TRACKING
        16         INDEX UNIQUE SCAN PK_TRACKING
        17     TABLE ACCESS BY INDEX ROWID PRODUCT
        18       INDEX UNIQUE SCAN PK_PRODUCT

19 rows selected.

batchcode@QGF1> 

It all makes sense to me, except for step 8, where there is a range scan on the PK_WIREINFO index. 
From what you explained, this step is feeding step 7, which is the FTS of chk_screen. But that 
seems counter-intuitive. How can there be a range scan on pk_wireinfo, and it also be the starting 
point for the plan? Alternatively, if I think that chk_screen was full table scanned first, 
followed by a range scan on pk_wireinfo for the ids produced from the FTS of chk_screen, that makes 
complete intuitive sense to me, but then it fails your explanation. I know this is caused by the 
PUSH_SUBQ hint used, which is causing the subquery to be evaluated first. Any feedback would be 
helpful. Thanks in advance.

Kashif 


Followup   January 17, 2003 - 6pm Central time zone:

not knowing the tables and what indexes go where -- well -- that makes it sort of difficult. (you 
know my opinion of hints too don't you?)


 

5 stars For anyone interested...   January 28, 2003 - 1pm Central time zone
Reviewer: Kashif from Houston, TX
Thanks Tom, for the explanation. For anyone interested, Chapter 9 of the Performance Tuning Guide 
for 9i, "Using EXPLAIN PLAN", is significantly better than previous releases (pre-9i), especially 
when it comes to explaining how to read the EXPLAIN PLAN in the section, "Reading EXPLAIN PLAN 
Output". This coupled with Tom's explanation above provided me with a comprehensive understanding 
of the EXPLAIN PLAN. Keep up the good work Tom.

Kashif 


5 stars 2 Points   March 26, 2003 - 12pm Central time zone
Reviewer: Robert from Memphis, USA
Tom,

1) Tom, your explaination of reading an explain plan is, I think, the best and most succinct 
explaination I have seen to date... also, I hope to study your psuedo-code to completely understand 
Oracle's workings.

2) Thanks to Kashif for the heads-up on the Performance Tuning Guide for 9i. I will check this out. 
I agree with the previous folks... the explain plan explaination in the 8i Performance and Tuning 
Guide was weak, and didn't explain it clearly as Tom has.

Thanks,

Robert 


5 stars   November 7, 2003 - 5pm Central time zone
Reviewer: A reader 
Hi Tom,
I've ran below SQL 2hr. after starting database 

  1  select a.file#,b.name,MAXIORTM,MAXIOWTM from v$datafile a,
  2   v$tablespace b,v$filestat c
  3  where a.ts#=b.ts#
  4    and c.file# = a.file#
  5*   and B.NAME = 'TEMP'
SQL> /

     FILE# NAME                             MAXIORTM   MAXIOWTM
---------- ------------------------------ ---------- ----------
         5 TEMP                              1685945       1263  

Is writing in TEMP taking too much time??
Please advice.

We are using oracle 8.1.7.4

Thanks
 


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

you tell us?  i don't know, is it? 

4 stars Reading Explain Plan   November 7, 2003 - 6pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

I have read books on tuning that simply states in statements about how each loop joins are 
executed. But you have given a pseudo code for how the plan gets executed which is really very very 
helpful. I would appreciate if you could suggest some books that explain each of these execution 
steps as pseudocodes rather than lengthy discussions/explanations.

As always thanks much for your clear and concise explanation 


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

well, i do that for some (not all) of the access paths in my new book "Effective Oracle by Design" 
-- hash joins, sort merges, cartesian joins and so on.  they are a mix of "lengthly discussion" and 
psuedo code snippets. 

4 stars Forth coming book   November 7, 2003 - 6pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

In this discussion you mention about your forth coming book. Is the one you are referring to is 
"Expert Oracle by Design" or is that any other new book that you are coming up with. I would be 
excited to see if you can come up with a new book explaining all of the Oracle tuning tools and 
tips/techniques 


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

It is called "Effective Oracle by Design", releases in August 2003 (see homepage) 

4 stars cpu costing   November 8, 2003 - 6am Central time zone
Reviewer: Prince from Pakistan
Respected Tom!

You're really performing a great valuable service. I have tried to use the expalin plan according 
to your instructions. But it doesn't give plan_table_output but 'Note: cpu costing is off, 
'plan_table' is old version' as follows. My question is 'What is cpu costing and how it can be 
enabled or disabled?' (I am using Oracle9i Enterprise Edition Release 9.2.0.1.0 on MS Windows 2000 
Advanced Server.)

SQL> delete from plan_table;

6 rows deleted.

SQL> explain plan for
  2  SELECT designation.designationcode, designation.name, designation.grade,
  3         designation.pricode
  4      FROM hrms.designation designation, hrms.employees employees;

Explained.

SQL> @E:\Ora92\rdbms\admin\utlxpls

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------


---------------------------------------------------------------------------------------------
| Id  | Operation                      |  Name                      | Rows  | Bytes | Cost  |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                            |  8521K|   186M|  1360 |
|   1 |  MERGE JOIN CARTESIAN          |                            |  8521K|   186M|  1360 |
|   2 |   TABLE ACCESS FULL            | DESIGNATION                |   451 | 10373 |     3 |
|   3 |   BUFFER SORT                  |                            | 18895 |       |  1357 |
|   4 |    BITMAP CONVERSION TO ROWIDS |                            |       |       |       |
|   5 |     BITMAP INDEX FAST FULL SCAN| EMPLOYEE_EMPLOYEETYPE_IDX  |       |       |       |
---------------------------------------------------------------------------------------------

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------


Note: cpu costing is off, 'plan_table' is old version

13 rows selected. 


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

drop and recreate your plan table using the current script (should be done with each upgrade -- 
recreate the plan tables)


currently, in 9i, cpu costing is off by default, this will change in 10g.  it is an undocumented 
init.ora parameter for cpu costing right now, so just ignore it.
 

5 stars Cardinality in EXPLAIN PLAN   November 8, 2003 - 9am Central time zone
Reviewer: A reader from NJ,USA
Dear Tom,

Thanks for sharing your valuable knowledge with us.

Could you kindly explain how the cardinality is calculated in explain plan output?

In my below query,
(1)For predicator first_name like 'Sa%' ==> 3 rows returned but Card=1
(2)For predicator first_name like 'S%' ==> 13 rows returned but Card=12


SQL> analyze table employees compute statistics for table for all indexes for all indexed columns;

Table analyzed.

SQL> set autotrace on
SQL> select employee_id from employees where first_name like 'Sa%';

EMPLOYEE_ID
-----------
        161
        192
        194


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=9)
   1    0   TABLE ACCESS (FULL) OF 'EMPLOYEES' (Cost=2 Card=1 Bytes=9)




Statistics
----------------------------------------------------------
          7  recursive calls
          0  db block gets
         10  consistent gets
          0  physical reads
          0  redo size
        428  bytes sent via SQL*Net to client
        503  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          3  rows processed

SQL> select employee_id from employees where first_name like 'S%';

EMPLOYEE_ID
-----------
        100
        116
        117
        123
        128
        138
        161
        166
        173
        192
        194
        203
        205

13 rows selected.


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=12 Bytes=108)
   1    0   TABLE ACCESS (FULL) OF 'EMPLOYEES' (Cost=2 Card=12 Bytes=1
          08)


Statistics
----------------------------------------------------------
          7  recursive calls
          0  db block gets
         10  consistent gets
          0  physical reads
          0  redo size
        517  bytes sent via SQL*Net to client
        503  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         13  rows processed

SQL>
 


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

the cardinality is a best guess ESTIMATE.  It was very close to dead on in this case, pretty good 
for a guess no? 

4 stars Re: cpu costing   November 9, 2003 - 2am Central time zone
Reviewer: Prince Faran from Pakistan
Ahaa! thats nice Tom. I have followed your instructions as under:-

SQL> drop table plan_table;

Table dropped.

SQL> @E:\Ora92\rdbms\admin\utlxplan.sql

Table created.

New plan_table is created. May I know plz.. Had the old plan_table any performance impact or 
anyother issue until it is not used explicitily by the user?

Thanks. 


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

it just had less "functionality" 

5 stars EXPLAIN PLAN Restrictions   November 18, 2003 - 8am Central time zone
Reviewer: Kamal Kishore from New Jersey, USA
Hi Tom,
In the following document at
http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/ex_plan.htm#15821
It says that:
<quote>
Oracle does not support EXPLAIN PLAN for statements performing implicit type conversion of date 
bind variables. With bind variables in general, the EXPLAIN PLAN output might not represent the 
real execution plan.

From the text of a SQL statement, TKPROF cannot determine the types of the bind variables. It 
assumes that the type is CHARACTER, and gives an error message if this is not the case. You can 
avoid this limitation by putting appropriate type conversions in the SQL statement.
</quote>

Can you please elaborate on this and explain what the implications might be for someone trying to 
tune the application and any precautions/guidelines that must be kept in mind (while coding and/or 
while tuning).
Thanks,
 


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

it should have said something like:

"if you do the really bad practice of relying on implicit conversions, you should expect bad things 
to happen"


IMO, you should code:


select * from t where date_column = to_date( :bind, 'date format' )

you should NEVER NEVER code

select * from t where date_column = :bind;


the second query is a bug waiting to happen.  implicit conversions should always be avoided. 

2 stars I think the response could do with some extra info   November 23, 2003 - 5pm Central time zone
Reviewer: Gary from Sydney, Aus
If you've got a PL/SQL block on the lines of
DECLARE
  v_start_date date;
  v_end_date date;
BEGIN
  SELECT result_col FROM <very_complex query>
  WHERE col_date BETWEEN v_start_date AND v_end_date
END;

If you want an explain plan of the query, you should not simply cut and paste the SQL to do an 
explain of
  SELECT result_col FROM <very_complex query>
  WHERE col_date BETWEEN :v_start_date AND :v_end_date
because, as stated in the reference, the explain plan doesn't know that the variables are defined 
as dates.
You have to go in and put the to_dates...
  SELECT result_col FROM <very_complex query>
  WHERE col_date BETWEEN to_date(:v_start_date,'format') AND to_date(:v_end_date,'format')

Personally, I'd love an oracle initialization parameter to turn off implicit data conversions 


3 stars Unique scans in a NESTED Loop   December 15, 2003 - 6pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

I have the following questions :

If I have an EXPLAIN PLAN that shows the following plan :
   NESTED LOOPS
      TABLE ACCESS FULL OF A
      TABLE ACCESS BY ROWID OF B
         INDEX UNIQUE SCAN B_IDX_UQ

1. Suppose A has around say 20000 rows. For each row in A, is it a good idea to perform index 
lookup followed by a table access of B (even if the index is unique). If so how to avoid this
2. Suppose the index B_IDX_UQ is such that each index entry points to a different table block, this 
will be almost equivalent to reading all of the blocks in B that are below HWM. 
    2a.How to improve the above EXPLAIN PLAN and will be better to use a FTS in that case? 
    2b.Does block selectivity matter (my understanding is block selectivity is only for index range 
scans. Is that true)?  
    2c.Does clustering factor also relevant in the above case (I remember you mentioning in one of 
your discussion that clustering_factor is for index range scans only and not for index unique 
scans). 
3. The examples I see in books and the one on your site are showing index-range scans for queries 
that use single tables. If I have a table-join and an EXPLAIN PLAN like say,
      <Some join method such as Nested Loops or Hash Join>
          TABLE ACCESS BY INDEX ROWID C
              INDEX RANGE SCAN C_IDX
          TABLE ACCESS BY INDEX ROWID D
              INDEX RANGE SCAN D_IDX
and the D_IDX and C_IDX have both very poor clustering_factors how can we tune the query above and 
resolve the problem. Can you provide an example showing importance of clustering_factor in such 
kind of joins above.
4. Suppose a query is doing a FTS as opposed to index scan (and I know that you always emphasize 
the fact that FTS is not a evil and index lookups are not always good), is there any way to examine 
why it is performing a FTS. ie. What things to look at (basically the steps/methods) to conclude 
that the CBO has arrived at the execution plan of using a FTS. ie to basically conclude that CBO 
has arrived at the FTS execution path because of such and such reasons... 


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

1) the answer is:

    yes
    no
    maybe 
    sometimes

depends -- do you want the first row first?  if so, that NL join is great.
is B HUGE -- if so, NL might be really appropriate

tell you what -- analyze the tables, use the cbo and let it decide.

2) again, ANALYZE (and it depends on the size of B as well -- you are making the unstated 
assumption that B is smaller or equal in size to A)

so 2a -- use cbo
   2b -- yes it does -- look at the last pair of plans below
   2c -- it comes into play in joins as well (they are almost like index range 
         scans in a fashion)


ops$tkyte@ORA9IR2> /*
DOC>
DOC>drop table a;
DOC>drop table b1;
DOC>drop table b2;
DOC>
DOC>create table a
DOC>as
DOC>select rownum id, all_objects.* from all_objects;
DOC>
DOC>create table b1
DOC>as
DOC>select rownum id, rpad('*',2000,'*') data from all_objects;
DOC>alter table b1 add constraint b1_pk primary key(id);
DOC>
DOC>create table b2
DOC>as
DOC>select * from b1 order by reverse(id);
DOC>alter table b2 add constraint b2_pk primary key(id);
DOC>*/
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> analyze table a delete statistics;
 
Table analyzed.
 
ops$tkyte@ORA9IR2> analyze table b1 delete statistics;
 
Table analyzed.
 
ops$tkyte@ORA9IR2> analyze table b2 delete statistics;
 
Table analyzed.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select * from a, b1 where a.id = b1.id;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (FULL) OF 'A'
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'B1'
   4    3       INDEX (UNIQUE SCAN) OF 'B1_PK' (UNIQUE)
 
 
 
ops$tkyte@ORA9IR2> select * from a, b2 where a.id = b2.id;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (FULL) OF 'A'
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'B2'
   4    3       INDEX (UNIQUE SCAN) OF 'B2_PK' (UNIQUE)
 
 
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> analyze table a compute statistics;
 
Table analyzed.
 
ops$tkyte@ORA9IR2> analyze table b1 compute statistics;
 
Table analyzed.
 
ops$tkyte@ORA9IR2> analyze table b2 compute statistics;
 
Table analyzed.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from a, b1 where a.id = b1.id;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2524 Card=29352 Bytes=61463088)
   1    0   HASH JOIN (Cost=2524 Card=29352 Bytes=61463088)
   2    1     TABLE ACCESS (FULL) OF 'A' (Cost=44 Card=29352 Bytes=2641680)
   3    1     TABLE ACCESS (FULL) OF 'B1' (Cost=956 Card=29353 Bytes=58823412)
 
 
 
ops$tkyte@ORA9IR2> select * from a, b2 where a.id = b2.id;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2524 Card=29352 Bytes=61463088)
   1    0   HASH JOIN (Cost=2524 Card=29352 Bytes=61463088)
   2    1     TABLE ACCESS (FULL) OF 'A' (Cost=44 Card=29352 Bytes=2641680)
   3    1     TABLE ACCESS (FULL) OF 'B2' (Cost=956 Card=29353 Bytes=58823412)
 
 
 
ops$tkyte@ORA9IR2> select /*+ index(b1 b1_pk) */ * from a, b1 where a.id = b1.id;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10674 Card=29352 Bytes=61463088)
   1    0   MERGE JOIN (Cost=10674 Card=29352 Bytes=61463088)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'B1' (Cost=9846 Card=29353 Bytes=58823412)
   3    2       INDEX (FULL SCAN) OF 'B1_PK' (UNIQUE) (Cost=61 Card=29353)
   4    1     SORT (JOIN) (Cost=828 Card=29352 Bytes=2641680)
   5    4       TABLE ACCESS (FULL) OF 'A' (Cost=44 Card=29352 Bytes=2641680)
 
 
 
ops$tkyte@ORA9IR2> select /*+ index(b2 b2_pk) */ * from a, b2 where a.id = b2.id;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=29396 Card=29352 Bytes=61463088)
   1    0   NESTED LOOPS (Cost=29396 Card=29352 Bytes=61463088)
   2    1     TABLE ACCESS (FULL) OF 'A' (Cost=44 Card=29352 Bytes=2641680)
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'B2' (Cost=1 Card=1 Bytes=2004)
   4    3       INDEX (UNIQUE SCAN) OF 'B2_PK' (UNIQUE)
 
 
 
ops$tkyte@ORA9IR2> set autotrace off
ops$tkyte@ORA9IR2>


3) use the CBO!

4) search for 10053 on this site and if you have my new book Effective Oracle by Design -- see 
chapter 6. 

2 stars Pls help   December 16, 2003 - 1pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

Thanks much for your response. However, I am not able to understand what you are trying to explain 
in that example and what numbers to look for in that example. Is it that you are illustrating how 
the explain plan changes after the analyze or is it something more to that that I am not clear of. 
Pls. help 
Also, for Qn 3, you say use CBO. Can you pls. explain. 

So are you saying that if you use CBO and appropriately analyze the tables and indexes, it will 
automatically come up with a good optimal plan and if it is then how to ensure that it is the 
correct optimal plan? 


Followup   December 16, 2003 - 2pm Central time zone:

look at the plans -- the plans are different for the two queries and the ONLY difference between 
the two is the cluster factor for the indexes.  so, in answer "Does clustering factor also relevant 
in the above case" - the answer is "yes" and "here is an example showing it"

my point for #3 was -- use the CBO and let it pick.  It has the rules for choosing which to use.  
Your question:

3. The examples I see in books and the one on your site are showing index-range 
scans for queries that use single tables. If I have a table-join and an EXPLAIN 
PLAN like say,
      <Some join method such as Nested Loops or Hash Join>
          TABLE ACCESS BY INDEX ROWID C
              INDEX RANGE SCAN C_IDX
          TABLE ACCESS BY INDEX ROWID D
              INDEX RANGE SCAN D_IDX
and the D_IDX and C_IDX have both very poor clustering_factors how can we tune 
the query above and resolve the problem. Can you provide an e


doesn't really "make sense".   poor clustering factors (don't really exist, they are facts -- they 
are not "good" or "poor" really, they are just facts) do not lead to poor performance.  I cannot 
tell you have to tune a hypothetical query with "some join technique"

that is the job of the optimizer. 

3 stars Poor clustering factor   December 16, 2003 - 5pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

I think I am not asking the question correctly to you. 

1. My question is that if the clustering factor is poor (say clustering factor = num of rows for 
both C and D) and the execution plan is as per shown in my question, then it would generate lot of 
logical I/Os (because of index lookup followed by a table lookup) and a FTS can be much better in 
that case. Is my understanding correct?
2. If 1 is true and if we find large number of logical I/Os, then how do we conclude from that 
execution plan that :
   Analyzing the table and indexes might provide a better plan?
   It may be because of the poor index clustering factors on those data tables or that it is just 
due to data volume (because some queries as such may hit more data). ie how to back-track from the 
explain plan back to data distribution? (because in the example you had shown, as soon as the data 
was analyzed, different execution plans were generated based on the index clustering factor and 
distribution of data). 
    So if I am just given an execution plan, where and how to start and how do I go from there to 
conclude that it is because of data distribution, clustering factor etc. ie.
How do we conclude whether the plan is optimal/correct?
Sorry for keep you asking same questions as I am getting really confused about the way the CBO is 
generating execution plans and I am not really sure how to tie-back the execution plan with data 
distribution. A detailed explanation/example on the above questions would be of great help.
3. In the NL question above, you mentioned that NL will be good if the table B is HUGE. But if the 
index unique scans for B are such that each index entry points to a different data block, then how 
does NL help in that case. My doubt is if the index entries in B point to different data blocks, 
then by the time the nested loop comes to read the same data block, it might have aged out of the 
buffer cache in which case it would have to do a physical read causing too much I/O. Your 
explanation on this would be of great help. 


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

1) no, that plan looks very innocent to me.  I do not see what you see.

consider:

ops$tkyte@ORA9IR2> select /*+ USE_HASH( t1 t2 ) */ *
  2    from t1, t2
  3   where t1.x = t2.a
  4     and t1.y = 5
  5     and t2.b = 5
  6  /
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=52)
   1    0   HASH JOIN (Cost=3 Card=1 Bytes=52)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=1 Card=1 Bytes=26)
   3    2       INDEX (RANGE SCAN) OF 'T1_IDXY' (NON-UNIQUE) (Cost=1 Card=1)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=1 Card=1 Bytes=26)
   5    4       INDEX (RANGE SCAN) OF 'T2_IDXB' (NON-UNIQUE) (Cost=1 Card=82)
 

it'll use the index to get A ROW (in this example).  so what if the clustering factor is "poor" or 
not.  

it is a function of the cardinality, not the use of the index per se.

2) if the numbers in the autotrace are way wrong (estimated cardinalities) that is what indicates 
an analyze is needed (or a "stronger analyze" with histograms or something).  You cannot go from 
"hi LIO's -> you need to analyze", there is no correlation

 

3 stars Pls. clarify   December 16, 2003 - 8pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

In the above example, you assume that only 1 row is retrieved. 
 1. Is that from the Card= column  in each step of the explain plan? If not how do you say it gets 
1 row.
 2. My question is what if a considerable number of rows are retrieved in both the steps of the 
HASH JOIN by using INDEX then TABLE approach and the clustering factor is low. In that case, my 
understanding is FTS and INDEX then TABLE approach will vary considerably. Is that correct?
 3. Out of the 3 info. Bytes= Cost= and Card= which must be taken into consideration/relevant 


Followup   December 17, 2003 - 6am Central time zone:

1) exactly, card=1 says "optimizer thinks a single row"

2) is 100,000 rows considerable?   maybe it is, maybe it is not.  It depends on the size of the 
underlying objects.  It is a function of the number of IO's the optimizer things it'll have to do.  


If the cost of doing the index IO's plus table IO's exceeds the cost of full scanning the table 
using multi-block IO, then YES (but -- that is exactly what the optimizer is tasked with doing) the 
full scan will be "cheaper".

3) all of it. 

3 stars Tuning???   December 17, 2003 - 2pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

1.If CBO is capable of deciding the right path when the tables and indexes/indexed columns are 
analyzed properly, then what is that we need to do to tune a query. How do we conclude that our 
query is not optimal? Is it by looking at the Logical I/Os
2. In some of your earlier discussions that the Cost and efficiency of a query are not 
inter-related, but here you say the Cost is also important/relevant. Can you explain/clarify on 
this.
3. If Card= and Bytes= are also relevant, what should we focus on in our queries about these 
figures? 


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

1) tune processes and applications.  

It goes like this:

user: "system is slow"
you:  "ok, the entire thing, an application, a screen, what is slow?"
user: "oh, ok -- this application is slow"
you:  "ok, the entire thing, a part of it, a specific feature?"
user: "oh, ok -- when I push this button it takes a long time, I push that button 
       alot"

you: "ok, we'll get on it"

now you have instrumented your code, so you run say a tkprof on that section of that application.  
You look for OBVIOUS low hanging fruit (i "grep ^total" in the tkprof and look for "big" totals -- 
things taking a long time).  You look at them.

You either decide "boy, that query does a ton of work -- can we do it better" or even more 
optimally "boy, we got that algorithm wrong didn't we.  who did this row by row stuff -- look, this 
entire process should be this "update" statement. "

2) cost is relevant, it helps you understand WHY the optimizer is doing what it does.  without it, 
we'd never be able to understand "why"

3) you should make sure they are in line with reality.  Small example:


ops$tkyte@ORA920> create table t as select * from all_objects;
 
Table created.
 
ops$tkyte@ORA920> create index t_idx on t(object_id);
 
Index created.
 
ops$tkyte@ORA920>
ops$tkyte@ORA920> analyze table t compute statistics
  2  for table
  3  for all indexes;
 
Table analyzed.
 
ops$tkyte@ORA920>
ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from t where object_id = 55;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=316 Bytes=30336)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=316 Bytes=30336)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=126)
 
 
Hmmm, optimizer things 316 rows -- that is OBVIOUSLY wrong for we know that object_id is near 
unique!!  something is amiss.  garbage in, garbage out.  We need to figure this out, what is wrong 
-- oh, we didn't actually take a good look at the column data -- lets do that:
 
ops$tkyte@ORA920> analyze table t compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;
 
Table analyzed.
 
ops$tkyte@ORA920> select * from t where object_id = 55;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=96)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=1 Bytes=96)
   2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=1)
 
 
 
ops$tkyte@ORA920> set autotrace off

ahh, now it got it right.  good data = better plans.  

if the card= is way off from reality and the plan is bad, there is a major problem there -- and the 
garbage data (or insufficient data) provided to the optimizer is the cause. 

3 stars Execution Plan vs Explain Plan   December 19, 2003 - 3pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

Can you pls. provide me an example of what the Card value in the Execution plan vs Explain plan 
means. My understanding is that the Execution Plan provides the actual cardinality whereas the 
Explain plan provides the estimated cardinality.
1. Is my understanding correct. It would be really helpful if you could provide me an example to 
illustrate this.
2. Pls. provide me info. on where to find the documentation for the 10053 event. I like to have a 
very introductory/basic understanding of what this event means, how to arrive at costs of various 
access paths, how to interpret the 10053 results etc. I was trying to understand some materials by 
Wolfgang Brietling but it looks too advanced to me. Please point me to very basic materials on this

Thanx 


Followup   December 20, 2003 - 9am Central time zone:

only if you tell me what the difference between an "explain" and "execution" plan are?

to me -- they are the same.

There is an explain plan that shows the execution plan.

In an explain plan, we can see estimated cardinalities, the number of rows the optimizer is 
expecting.

In a tkprof report, we can see the execution plan.  this time, the ACTUAL number of rows OBSERVED 
flowing through each phase of the plan is viewable.


Metalink has documents on 10053.

As well, my new book "effective oracle by design" has a section on it.  I did it "simple" I think. 

3 stars AUTOTRACE   December 21, 2003 - 6pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

I was going through your new book 'Expert Oracle by Design' where you are discussing about 
AUTOTRACE. In the AUTOTRACE output, you have two sections : Execution plan section and the 
statistics section :
1. Is execution plan section equivalent to the EXPLAIN PLAN and the Card= value shows only the 
estimated cardinality.
2. The 'Rows Processed' Statistics section in the Statistics section shows the actual number of 
rows processed. 
3. If I do not see the Cost=,Card= etc on the explain plan, does that mean that the CBO is not 
being used.
Is my understanding on 1,2 and 3 correct. If not please explain me with an example.
4. If I take a TKPROF report, where do I see the estimated rows and the actual rows processed.

THanks 


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

1) yes

2) yes

3) yes

4) you do not see "estimated" (thats why I often ask for AUTOTRACE *and* TKPROF's so I can see 
"actual" vs "estimated").  autotrace = estimated.  tkprof = "what actually happened".

you see the actual rows processed right in the tkprof -- edit one and it'll just be staring you in 
the face. 

4 stars Table not analyzed   December 21, 2003 - 7pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

I have now clearly understood what each section means. So my question on this is :
If the 'Rows Processed' (Card=) value from Autotrace section (Question 1 above) has a huge 
disparity with the 'Rows processed' in the Statistics section, then it means 
   1. Means data has not been properly analyzed. Is that true?
   2. Does it also mean that my query is not properly tuned?

Thanks
 


Followup   December 21, 2003 - 7pm Central time zone:

it COULD mean #1.  It definitely means "something is wrong".

it could be insufficient stats (eg: add histograms and all of a sudden the optimizer "knows more"). 
 

it could be a "product issue" (aka: a bug)

it definitely does not imply or mean #2! 

4 stars Pls. clarify   December 21, 2003 - 10pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

In one of the discussion title "Physical Reads vs Logical Reads" where you compare colocated vs. 
disorganized tables, you are stating the following :

Now, going higher on the array size (this is the sqlplus default size):

ops$tkyte@ORA920.LOCALHOST> set arraysize 15
ops$tkyte@ORA920.LOCALHOST> select * from colocated where x > 0;

29319 rows selected.

Statistics
----------------------------------------------------------
...
       5047  consistent gets
...
      29319  rows processed

ops$tkyte@ORA920.LOCALHOST> select * from disorganized where x > 0;

29319 rows selected.

Statistics
----------------------------------------------------------
...
      31325  consistent gets
...
      29319  rows processed

the colocated table is now a fraction of the LIO's.  We did 15 row array 
fetches, meaning we did about 2,000 "fetch" calls.  Apparently we did about 2.5 
LIO's per fetch.  How so?  Well we did this:

- get index block (1 LIO)
- get row 1 from table for this index block (1 LIO for block X)
- get rows 2..15 from table for this index block (maybe 0 LIO's - all on block X, maybe 1 
additional LIO cause we just read all of the rows on block X)

So, we do 2-3 LIO's for every 15 rows -- 5k LIOS.

In the above explanation, could you pls. explain me as to what you mean by 
"- get rows 2..15 from table for this index block (maybe 0 LIO's - all on block X, maybe 1 
additional LIO cause we just read all of the rows on block X)"

2. In your book Expert-one-on-one Oracle, you mention the following (page 279)
<Quote>
Suppose you have a table where the rows have a primary key populated by a sequence. As data is 
added to the table, rows with sequential sequence numbers are in general 'next' to each other. The 
table is naturally clustered, in order, by the primary key (since the data is added in more or less 
that order). It will not be strictly clustered in order by the key of course(we would have to use 
an IOT to acheive that) but in general....
</Quote>
In the above para, can you pls clarify this with an example as to what you mean by :
"It will not be strictly clustered in order by the key of course(we would have to use an IOT to 
acheive that"

3. What is meant by naturally clustered vs. automatically clustered vs. uniformly distributed? 


Followup   December 22, 2003 - 9am Central time zone:

what I was saying is -- if the rows are next to eachother on the block -- then the first row we 
fetched will perform the logical IO to retrieve the block from the buffer cache.  Rows 2-15 will be 
read from that same block -- no need to go back to the buffer cache for the next row, we already 
have it.  Hence, we do not need to do anymore LIO's to get the rows from the table.  If the 2cnd 
row WAS NOT next to the first row on that block -- we would have discarded that block and done 
another LIO to get row 2.


I need no example really, you just need to know that rows will go in where they fit.  This is 
typically at the "end" of the table.  The rows will NOT be in order of the sequence - they will be 
CLOSE, but they will not be in order of the sequence.  (think of the session that retrieves a 
sequence.nextval -- waits 10 seconds -- then inserts the row.  In that 10 seconds, perhaps 100 
other rows were inserted using sequence.nextval.  This row will be "100 away from its prior/next 
values).  Unless you use an IOT or some other structure that enforces placement of data -- you are 
using a HEAP table and in a HEAP, rows just go where they fit.

Naturally clustered means that due to the NATURE of the way the data is inserted, the data will 
"naturally" (as a side effect, because of) be ordered nicely.  If you insert 1, 2, 3, 4, 5, 6, 7, 
8, .... infinity into a HEAP table, in order, most of the times you'll observe the data is 
physically organized that way. "it naturally" did it.

the other two terms, not familiar with them.


 

3 stars How to determine if data colocated   January 6, 2004 - 12pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

In the case of heap tables, we can quickly determine whether a column say X populated by a sequence 
generated is naturally clustered (or atleast in close proximity) or not by using the block number 
of the rowid using the DBMS_ROWID package. However since IOTs do not have the concept of ROWID, how 
can we determine that the data is actually clustered in case of IOT.

Thanks much 


Followup   January 6, 2004 - 2pm Central time zone:

well, the dbms_rowid thingy isnt very "scalable" is it?  I mean, you have to look back a row and 
forward a row for each row.  how do you do that for say 100,000 rows?

In an IOT, the data is ALWAYS clustered by the primary key (thats the point of the structure 
entirely!).  It is the other columns that might be clustered and for that, I would use analyze -- 
just like I would for a "real" table

ops$tkyte@ORA920PC> create table t ( x int primary key, y int, z int, data char(80) default 'x' )
  2  organization index;
 
Table created.
 
ops$tkyte@ORA920PC> insert into t select rownum, rownum, dbms_random.random, 'x' from all_objects;
 
30197 rows created.
 
ops$tkyte@ORA920PC> create index t_idx1 on t(y);
 
Index created.
 
ops$tkyte@ORA920PC> create index t_idx2 on t(z);
 
Index created.
 
ops$tkyte@ORA920PC> analyze table t compute statistics for table for all indexes;
 
Table analyzed.
 
ops$tkyte@ORA920PC> select index_name, clustering_factor from user_indexes where table_name = 'T';
 
INDEX_NAME                     CLUSTERING_FACTOR
------------------------------ -----------------
SYS_IOT_TOP_42421                              0
T_IDX1                                       400
T_IDX2                                     30146
 
 

3 stars Row proximity   January 6, 2004 - 8pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

In a heap table what would be then the best way to determine if a table is naturally clustered 
other than the query using DBMS_ROWID package as per my question above? 


Followup   January 6, 2004 - 8pm Central time zone:

tell me how you would tell with dbms_rowid in the first place!  for a non-trivial sized table.

analyze the index (if you don't have an index - you don't care). look at the clustering factor. 

5 stars can we ignore the exec plan?   January 30, 2004 - 4pm Central time zone
Reviewer: steve 
Hi Tom,

If we should account on CBO to find best execution plan,
1) should we care about the plan while tuning the sql?  
2) can we just focus on statistics generated by SQL_TRACE such as LIO, PIO, or WAIT number and 
ignore the plan while tuning the sql?

Thanks!

Steve 
  


Followup   January 30, 2004 - 8pm Central time zone:

1) yes
2) no

the cbo is a mathematical model.  it takes inputs - generates outputs.  You need to sometimes look 
at a plan and say "gee, thats not right, lets hint to see if we are right and it is wrong and if so 
-- look to see why it might be so, perhaps optimizer_index_caching or optimizer_index_cost_adj 
needs to be tweaked" 

5 stars Have you heard of this ... ?   February 13, 2004 - 1pm Central time zone
Reviewer: Gabe from Internet
Tom,

Can you please express your opinion regarding the following approach to “performance tuning”?

Background:
Big Java/Oracle web-based implementation for mission critical application (core functionality for 
the business) … RUP methodology (tones of “artifacts” lots of them out of date by now) … brand new 
data model/architecture Â… large data migration from legacy which they only started to analyze 
(build use-cases) Â… project late Â… dictatorial (alternative opinions not tolerated) Â… sweat-shop. 
Extensive use of a pl/sql (a good architectural decision) Â… all access is supposed to be though the 
pl/sql layer Â… but performance tuning was perceived to be just another effort at the end of the 
project.

Task (in the name of performance tuning):
I was asked to extract a unique (???) list of all the selects (no updates or deletes) from the 
database, run explain plan for all of them and produce a list of top-n by cost.

Note: the Selects are to be extracted from the pl/sql code, view definitions (????), etc. Â… not 
captured with sql_trace or from v$sql or from v$sql_plan or whatever (the application is not very 
stable and quite buggy at this time).

I think it is a complete waste of time because:
1.    The volume of data in the test database is only a fraction of the real data after migration 
(half of tables are actually empty)
2.    There is no assurance the environment is isolated
3.    Short of writing a parser Â… I have to manually extract and edit the sql (packaged functions 
not listed in the package spec used in the select list, convert pl/sql variable to actual values or 
placeholders for bind variables, remove the into clause, etc.) Â… the code is still changing and 
hence there is no assurance the statements IÂ’m extracting will actually be there later on Â… unless 
IÂ’m automating the whole thing the process is not repeatable (and I was only given 2-3 days to go 
though all the code Â… +125K lines in user_source)
4.    etc.

Am I missing something? My understanding is that the cost is a formula of a cpu cost and io cost 
calculated at the time the explain plan is executed Â… the cost of one sql doesnÂ’t have anything to 
do with the cost of another sql Â… so, all this comparison of hundreds and hundreds of painfully 
extracted (and doctored) sql statements means (imho) Â… zilch, nada. As I was going through the code 
I did identify few bugs Â… so the byproducts of this effort could be positive Â… but the whole thing 
seems to me totally dumb.

Thanks 


Followup   February 13, 2004 - 1pm Central time zone:

well, forgetting for a moment that "explain plan lies" and "tkprof/v$sql_plan can only tell the 
truth" (and would be tons easier than extracting sql from plsql....)

and forgetting for a moment that the cost of a query only indirectly relates to its performance (it 
should be direct, but hey -- reality -- it is not)

and forgetting that unless you have stats that represent reality (and you seem not) the cost is 
just a random number.....


Nah, this is a total and utter waste of time.  You must 

a) get a stable, not so buggy thing
b) trace the "slow parts"
c) fix their algorithms and or sql (sql isn't always the culprit, it is as often, if not more often 
the algorithms employed that are the issue)


This is a really "bad idea" 

5 stars Question on tuning   February 13, 2004 - 2pm Central time zone
Reviewer: PRS from USA
I have following query.
SELECT NVL (SUM (DECODE (rc_status, 'NEW', 1, 0)), 0)
      ,NVL (SUM (DECODE (rc_status, 'PEND', 1, 0)), 0)
  FROM ps_rc_case a, ps_wsi_rccase_flds b
 WHERE a.case_id = b.case_id
   AND a.business_unit = b.business_unit
   AND b.wsi_sub_assign_to = '50003540'
   AND a.rc_status IN ('NEW', 'PEND')

Table ps_rc_case and ps_wsi_rccase_flds have 7.5 million rows. It is using the right index. But 
this query takes 13 seconds which is not acceptable to online application.
 We have ORACLE 9.2.0.4.0 on Sun-solaris 9 (E4500). Each indexe has 1924 extenets on a LMT defined 
as 4MB uniform size. Any idea will be appereciated as we want to have less than a second turnaround 
time required for this.
Follwing is the trace with explain plan.
NVL(SUM(DECODE(RC_STATUS,'NEW',1,0)),0) NVL(SUM(DECODE(RC_STATUS,'PEND',1,0)),0)
--------------------------------------- ----------------------------------------
                                      3                                        6


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3740 Card=1 Bytes=33
          )

   1    0   SORT (AGGREGATE)
   2    1     HASH JOIN (Cost=3740 Card=12046 Bytes=397518)
   3    2       INDEX (RANGE SCAN) OF 'PSEWSI_RCCASE_FLDS' (NON-UNIQUE
          ) (Cost=308 Card=88831 Bytes=1332465)

   4    2       INDEX (FAST FULL SCAN) OF 'PS7RC_CASE' (NON-UNIQUE) (C
          ost=2934 Card=1011449 Bytes=18206082)





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


Followup   February 13, 2004 - 2pm Central time zone:

well, hows about you tell us

a) what indexes go with what tables
b) what indexes in their entirety (names, list of columns, and the table to which they belon) exist 
to be used....


c) AND b.wsi_sub_assign_to = '50003540' -- is that a constant for real or a bind variable?  if a 
constant -- how many rows match that.  if a bind, what is 


select min( count(*) ), max(count(*)), avg(count(*))
  from ps_wsi_rccase_flds
group by wsi_sub_assign_to

d) what is  select count(*) from the other table where a.rc_status IN ('NEW', 'PEND') 

4 stars Answers to the questions you asked   February 13, 2004 - 3pm Central time zone
Reviewer: PRS from USA
Tom,
   Following are the answers for the questions you asked.

a. Table PS_RC_CASE use the index PS7RC_CASE. Table PS_WSI_RCCASE_FLDS uses the index 
PSEWSI_RCCASE_FLDS.

b. Index PS7RC_CASE is defined on columns CASE_ID,BUSINESS_UNIT,RC_STATUS. Index PSEWSI_RCCASE_FLDS 
defined on columns wsi_sub_assign_to,case_id, business_unit.

Statistics for both the index shown below.
For Index PS7RC_CASE on PS_RC_CASE
select * from dba_indexes where index_name = 'PS7RC_CASE'
LEAF_BLOCKS30496                                            DISTINCT_KEYS 7561526
AVG_LEAF_BLOCKS_PER_KEY 1
AVG_DATA_BLOCKS_PER_KEY 1
CLUSTERING_FACTOR420853                                     NUM_ROWS7561526                         
                    SAMPLE_SIZE 7561526
LAST_ANALYZED 2/13/2004 3:07:50 PM
For Index psewsi_rccase_flds on PS_wsi_RCCASE_flds
LEAF_BLOCKS 26316
DISTINCT_KEYS 7561525
AVG_LEAF_BLOCKS_PER_KEY 1
AVG_DATA_BLOCKS_PER_KEY 1
CLUSTERING_FACTOR5837952                                    NUM_ROWS7561525                         
                    SAMPLE_SIZE 7561525
LAST_ANALYZED 2/13/2004 1:59:49 PM

c. It is a bind variable. Following is the query result.
select min( count(*) ), max(count(*)), avg(count(*))
  from ps_wsi_rccase_flds
group by wsi_sub_assign_to
MIN(COUNT(*)) MAX(COUNT(*))     AVG(COUNT(*))
------------- ------------- ---------------
1             7477302        72014.533

D. The counts are as inder for the query you asked.
SELECT COUNT(*)
FROM PS_RC_CASE WHERE 
rc_status IN ('NEW', 'PEND')
COUNT(*)
--------
6929

      Any help is appreciated.
Thanks,
PRS 


Followup   February 13, 2004 - 4pm Central time zone:

well, not much is going to help this puppy (except if you only go after the count(*)'s = 1 that 
is!)

with an average of 72k rows per wsi_sub_assign_to records per value, well, it's got tons of stuff 
to shift through there doesn't it.  or 7,000 index probes (if we indexed differently) from 
ps_rc_case into the other table.

Either way, it won't be brilliantly fast -- it is pure "math" at work here.


We don't want to drive from B into A (72k rows is too big).  We'd  have to drive from A into B -- 
but even then, it is nasty with 7k index probes (if you indexed 
case_id,business_unit,wsi_sub_assign_to in ps_wsi_rccase_flds in that order (well, the last field 
being last is the relevant one) it could index probe but I see that as being a SLOWER alternative 
to what you have probably)


sorry -- but without changing the underlying structures, setting them up specifically to answer 
this sort of question, I do not see you getting there from here.  Think about (after looking at the 
above numbers) what work must be done in order to get that very simple looking row.

We either find 72k rows in table B and then goto table A row by row
or
we have 7k rows in table A and then goto table B row by row
or 
we do what it is doing which is to goto both and slam them together in bulk (most likely the 
fastest).


You can try this:

index on a(rc_status,business_unit,case_id)
index on b(business_unit,case_id,wsi_sub_assign_to)

it can then use these two indexes to answer the question,just a thought.  But look at the data, use 
your knowledge of it to answer the question "what could it do to do this fast".  If you come up 
with an idea, let us know and I'll try to show you the way to the sql that does that.


 

5 stars Howto explain plan sql with type casting ?   February 16, 2004 - 6pm Central time zone
Reviewer: A reader 
(Oracle 9.2)

I have a statement like ...

select * from t where id in (SELECT * FROM TABLE(CAST(arr AS NUMBER_ARRAY)));

How do I run an explain plan on it?

I even took the _transformed_ sql from v$sqltext:

SELECT * from t where id = (SELECT * FROM TABLE(CAST(:b1 AS NUMBER_ARRAY)))

but still ...

abc@abc> explain plan for
  2  SELECT * from t where id = (SELECT * FROM TABLE(CAST(:b1 AS NUMBER_ARRAY)));
SELECT * from t where id = (SELECT * FROM TABLE(CAST(:b1 AS NUMBER_ARRAY)))
                                                      *
ERROR at line 2:
ORA-00932: inconsistent datatypes: expected - got CHAR

Thank you.

 


Followup   February 17, 2004 - 8am Central time zone:

if you want to use explain plan -- which assumes ALL binds are character strings regardless -- 
you'll have to put in a hard coded value there:


ops$tkyte@ORA920PC> explain plan for
  2  select *
  3    from t
  4   where id = (select * from table( cast( number_array(1,2,3) as number_array ) ))
  5  /
 
Explained.
 
ops$tkyte@ORA920PC> select * from table(dbms_xplan.display);
 
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
-------------------------------
 
-------------------------------------------------------------------------------
| Id  | Operation                               |  Name        |Rows|Bytes|Cst|
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |              |  1 | 100 |  2|
|   1 |  TABLE ACCESS BY INDEX ROWID            | T            |  1 | 100 |  2|
|*  2 |   INDEX UNIQUE SCAN                     | SYS_C007505  |100 |     |  1|
|   3 |    COLLECTION ITERATOR CONSTRUCTOR FETCH|              |    |     |   |
-------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("T"."ID"= (SELECT /*+ */ VALUE(KOKBF$) FROM
              TABLE(CAST("NUMBER_ARRAY"(1,2,3) AS "NUMBER_ARRAY") ) "KOKBF$"))
 
Note: cpu costing is off
 
17 rows selected.
 

5 stars Howto explain plan sql with type casting ?   February 17, 2004 - 9am Central time zone
Reviewer: A reader 
I did try a to_number(:c) ... guess that did not get automagically cast to a number_array with one 
element.

Thank you again. 


4 stars question   February 17, 2004 - 1pm Central time zone
Reviewer: PRS from USA
Hi Tom,
   I have following query. It does a full table scan on one of the table inspite of having an 
index. There are only 63 rows. Looks like oracle optimizer behaving really odd.

Query:
SQL> l
  1  SELECT SUM(CLAIM_AMOUNT)
  2    FROM PS_RCFCASE_COMPL A,
  3         PS_WSI_RCCASE_FLDS B
  4   WHERE A.CASE_ID = B.CASE_ID
  5     and a.business_unit = b.business_unit
  7*    AND B.WSI_SYSTM_DWNTM_ID = '91'
SQL> /

SUM(CLAIM_AMOUNT)
-----------------
        -37228.56


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=13577 Card=1 Bytes=2
          9)

   1    0   SORT (AGGREGATE)
   2    1     HASH JOIN (Cost=13577 Card=101202 Bytes=2934858)
   3    2       INDEX (FAST FULL SCAN) OF 'PSDWSI_RCCASE_FLDS' (NON-UN
          IQUE) (Cost=2525 Card=100952 Bytes=1514280)

   4    2       TABLE ACCESS (FULL) OF 'PS_RCFCASE_COMPL' (Cost=8016 C
          ard=7580277 Bytes=106123878)





Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     110131  consistent gets
      82882  physical reads
       5400  redo size
        209  bytes sent via SQL*Net to client
        275  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed


   Indexes defined as under.
   Table Name PS_WSI_RCCASE_FLDS - 
   Index Name - PSDWSI_RCCASE_FLDS  
   COlumn names - (CASE_ID,BUSINESS_UNIT,WSI_SYSTM_DWNTM_ID)

   Table Name - PS_RCFCASE_COMPL
   Index Name - PS_RCFCASE_COMPL
   Column names -(CASE_ID,BUSINESS_UNIT)
  It is not using the index on PS_RCFCASE_COMPL. Both the tables have 7.5 millions rows. Index and 
tables are analayzed. Index 100% analyzed and table 35% analyzed.

   This query takes 2 minutes to come back. It is just oracle optimizer not making the use of an 
index.

Any help is appreciated. 


Followup   February 17, 2004 - 3pm Central time zone:

why would you want it to use the index?  did you want it to run *really truly slow*???


tell me -- what index do you believe would be useful here and more importantly -- WHY?


I see this query:

 1  SELECT SUM(CLAIM_AMOUNT)
  2    FROM PS_RCFCASE_COMPL A,
  3         PS_WSI_RCCASE_FLDS B
  4   WHERE A.CASE_ID = B.CASE_ID
  5     and a.business_unit = b.business_unit
  7*    AND B.WSI_SYSTM_DWNTM_ID = '91'


Ok, either A or B could be used to "drive" the query.


So, if we use A, we can index into B but we would have to index into B 7,580,277 times!!!  (since 
there is no predicate on A, every row in A has to be considered, we'd have to look into B's index 
7.5 MILLION times.  Not very efficient)

If we choose B to drive, the optimizer is thinking "after predicate of ..._id = '91' is applied" 
there will be 100,952 rows that we would have to perform an index range scan on into A (3 LIO's to 
read the index plus one table access by index rowid -- 400,000 IO's for that).


So, are these numbers accurate.
If you "hint it" to use the plan you think is much much better -- is it?
If it is, have you looked at optimizer_index_* init.ora parameters (search for them on this site to 
read about them)
 

2 stars Answer   February 17, 2004 - 3pm Central time zone
Reviewer: PRS from USA
Tom,
   I changed the same query following way and it comes back in less than a second. Also created 
index PSDWSI_RCCASE_FLDS only on WSI_SYSTEM_DWNTM_ID column.

SELECT /*+ ordered index(b PSDWSI_RCCASE_FLDS) use_nl(a) */
SUM(CLAIM_AMOUNT)
FROM  PS_WSI_RCCASE_FLDS B, PS_RCFCASE_COMPL A
WHERE A.CASE_ID = B.CASE_ID
and a.business_unit = b.business_unit
AND B.WSI_SYSTM_DWNTM_ID = '91'

    Basically it was doing a FAST FULL SCAN on index
PSDWSI_RCCASE_FLDS which is same as almost full table scan. So I just rebuild the index with only 
one column and it worked fine. Also I use hints.

Thanks,
PRS 


Followup   February 17, 2004 - 6pm Central time zone:

without the hints, what happens -- you changed the schema, the first schema perhaps wasn't "right" 
for using the index.  

I'd like to totally LOSE The hints. 

3 stars questions   February 18, 2004 - 9am Central time zone
Reviewer: PRS from USA
Hi Tom,
   I also do not like to use hints. But what option do I have here. ORACLE does not provide any 
other option here. This are the table designed this way. We have a good machine with 4 CPU sun 
e4500 with 8GB RAM. Why Oracle is doing hash joins and whole thing goes for a toss. This is a 
simple data volume with 7.5 million rows in each table. ORACLE claims that it's CBO is the best. 
But in some case I really doubt. It is a nightmare sometime to tune some of the queries. You are 
left with no option. This is just my perception specially in using the HINTS. I hate using hins. 
But I have no option here. What do you think?

Always appreciate your input. You are the GURU. But sometime this CBO is horrible.

Thanks,
PRS 


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

you changed the index -- what is the plan WITHOUT hints using autotrace with the NEW SCHEMA you 
have.  

forget the old stuff you posted, quite simply "not relevant".  you changed the entire situation. we 
are back at square "uno" here -- forget everything that was said before, you changed it ALL. 

5 stars slow query in stored procedure but very fast as single SQL   February 23, 2004 - 2pm Central time zone
Reviewer: A reader 
Hi

I have a query inside a procedure, the procedure receives the parameter and pass that parameter to 
the query inside as a condition. The thing is the query by itself runs very fast (instantaneously) 
no matter if I use bind variables or literals but inside the procedure it takes 5 minutes!!! I dont 
understand why is this, I thought queries like this inside procedures can be simulated by using 
bind variables such as

var x number
exec :x=7818654

query here
where phone=:x

But NO, it does not produce the same effect, I am forced to execute the proceudre everytime I want 
to test a new HINT!!! How so? Any workaround? Expected behaviour (hope not... if that's the case we 
might as well change rdbms!)

 


Followup   February 23, 2004 - 4pm Central time zone:

can you show us

a) a tkprof from sqlplus (showing fast)
b) a tkprof from plsql (showing slow)

to start with. 

5 stars here is the tkprof   February 24, 2004 - 6am Central time zone
Reviewer: A reader 
query:

in sqlplus
var x varchar2(24)
exec :x := 'AA'

here it´s fast because it uses predicate ph.phone = :x

SELECT
                         be.pa_segment_mkt, bc.SEGMENT, co.customer_type,
                         be.pa_segment_org
                    FROM sysadm.ps_cm_phone ph,
                         sysadm.ps_bo_cm_profl_dtl d,
                         sysadm.ps_bo_cm_profile x1,
                         sysadm.ps_bo_rel a,
                         sysadm.ps_bo_rel_cat_item b,
                         sysadm.ps_bo_rel_cm c,
                         sysadm.ps_pa_bc_ext be,
                         sysadm.ps_bc bc,
                         sysadm.ps_rd_company co
                   WHERE ph.phone = :x
                     AND a.rel_type_id = b.rel_type_id
                     AND a.start_dt <=
                            TO_DATE (TO_CHAR (SYSDATE, :"SYS_B_0"),
                                     :"SYS_B_1"
                                    )
                     AND a.end_dt >
                            TO_DATE (TO_CHAR (SYSDATE, :"SYS_B_2"),
                                     :"SYS_B_3"
                                    )
                     AND b.bo_rel_category_id IN (:"SYS_B_4", :"SYS_B_5")
                     AND a.rel_type_id = c.rel_type_id
                     AND a.bo_id_1 = c.bo_id_1
                     AND a.role_type_id_1 = c.role_type_id_1
                     AND a.bo_id_2 = c.bo_id_2
                     AND a.role_type_id_2 = c.role_type_id_2
                     AND c.bo_id = d.bo_id
                     AND c.profile_cm_seq = d.profile_cm_seq
                     AND x1.end_dt >
                            TO_DATE (TO_CHAR (SYSDATE, :"SYS_B_6"),
                                     :"SYS_B_7"
                                    )
                     AND x1.bo_id = d.bo_id
                     AND x1.cm_type_id = d.cm_type_id
                     AND x1.bo_id = a.bo_id_1
                     AND d.eff_status = :"SYS_B_8"
                     AND d.cm_id = ph.cm_id
                     AND a.bo_id_2 = bc.bo_id
                     AND c.bo_id = be.bo_id
                     AND c.bo_id = co.bo_id(+)

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


in pl/sql

CANT PASTE,it has been running over one hour already and not finishing, the problem is in PL/SQ it 
does not use ph.phone=phone!!!

 


Followup   February 24, 2004 - 8am Central time zone:

I don't see the plan, the plan is what is important -- you need to exit sqlplus before running 
tkprof.

and try adding /*+ first_rows */ to the plsql based one, see what happens there.


does this query really get zero rows??  even in the plsql ? 

4 stars To "A reader"   February 24, 2004 - 9am Central time zone
Reviewer: Kim Berg Hansen from Middelfart, Denmark
Hi, "A reader"

In your last sentence you say "ph.phone=phone"...

Does that mean that you have a PL/SQL variable or argument with the same name as a column in your 
table ?
That could be the case - if so then rename the variable/argument.

 


4 stars Ideal Value of COST   April 13, 2004 - 7am Central time zone
Reviewer: Dhrubo from Kolkata,India
Hi Tom,
      Is there any thing called "Ideal value of COST".Is this COST a function of CPU COST & IO 
COST?Please explain 


Followup   April 13, 2004 - 8am Central time zone:

there is no such concept as the ideal value of cost.

the cost is dependent on cpu costing only when enabled in 9i (or by 'accident' sometimes, that is a 
product "issue") and is on in 10g.  In 9i and before, the cost is predominantly IO based -- in 10g 
and beyond cpu and io based. 

4 stars Reading Explain plan   May 12, 2004 - 7am Central time zone
Reviewer: parag jayant patankar 
Hi Tom,

I have gone thru your explanation of reading explain plan using tree structure. It is very clear. 
That is great. 

Tom will you pl explain us the following explain plan operations in simple language for novice 
person like me

INDEX UNIQUE SCAN
INDEX RANGE SCAN
NESTED LOOPS
NESTED LOOPS OUTER
HASH JOIN
MERGE JOIN
FILTER
VIEW
REMOTE
SORT ORDER BY
SORT GROUP BY
SORT JOIN

Once I understand the concepts, I can read and do further study of this.

thanks & regards 


Followup   May 12, 2004 - 7am Central time zone:

if you have my book "Effective Oracle by Design", check that out -- i do many of them there.

Else, well not else, in addition read the performance guide (on otn), it covers them as well. 

4 stars 10g   May 21, 2004 - 2am Central time zone
Reviewer: SR from Delhi, India
Tom

The advent of 10g and associated hypes brought about tremendous confusion in DBA community. 
Updating oneself has never been a problem, but when claims are made about some radical changes and 
absolute u-turns, the mayhem overshadows the eagerness to learn. Putting it simply, these are some 
of my queries:

1. Should I change the methodologies for TUNING which I have been adapting so far? How long tools 
like tkprof (favorite of yours too) will be having their present relevance?

2.Should I start THINKING differently when I approach a 10g database to tune it PROACTIVELY?

3.Should I start abandoning the classical administrative interfaces like normal SQL prompt and 
start using OEM for almost all operations?


You have to please help us out. 


Followup   May 21, 2004 - 10am Central time zone:

please elaborate -- what are the radical changes and absolute u-turns you speak of?  I see tools 
that help us visualize a system, tools that help tune at the system level -- but as we really know, 
99.999% of performance will come from the proper design and implementation of our applications -- 
no silver bullets there.  If you build an application that doesn't use bind variables -- cursor 
sharing can "help", but it won't fix everything.  session cached cursors can further help, but only 
a little.  At the end of the day, the only thing that will fix it will be to correct the bug in the 
code is -- well -- fixing the bug in the code.

1) tkprof, sql_trace, 10046 level traces are far from gone.

The database will tune:

  for x in ( select * from t )
  loop
     insert into t2 values (t.x,t.y,t.z);
  end loop;

by adding array fetches for us, making the select * from t go as fast as it could -- but it will 
NEVER turn that into:

   insert /*+ append */ into t2 select * from t;

which will *blow away* any procedural code you write.


1) they are not going away anytime soon.  in fact, AWR is just statspack on steriods.  Useful for a 
"system" after the apps have been tuned up.  not so useful for tuning an application (10046 trace 
with tkprof does that)

2) if you haven't be proactively tuning, you've been doing it wrong all along.  TUNE is a 4 letter 
word (cursor word).  DESIGN is a 6 letter word (good word).

3) no way. 

5 stars   May 24, 2004 - 1am Central time zone
Reviewer: SR from Delhi, India
Thanks a million, Tom - That cleared all the clouds ! 


4 stars Thanks, but am I interpreting this right   June 1, 2004 - 6am Central time zone
Reviewer: A reader 
with a explain such as the one you used above

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------
| Id  | Operation                     |  Name       | Rows  | Bytes
-------------------------------------------------------------------
|   0 | SELECT STATEMENT              |             |       |      
|   1 |  NESTED LOOPS                 |             |       |      
|   2 |   NESTED LOOPS                |             |       |      
|   3 |    TABLE ACCESS FULL          | EMP         |       |       
|   4 |    TABLE ACCESS BY INDEX ROWID| DEPT        |       |       
|*  5 |     INDEX UNIQUE SCAN         | DEPT_PK     |       |       
|*  6 |   TABLE ACCESS FULL           | SALGRADE    |       |       
-------------------------------------------------------------------


I think I now understand how to read them in terms of order performed, but I still am unsure 
if I have the following right..
Does the database read the whole of EMP (storing it in memory ?) and then for every
deptno, it looks in retrieves the row from DEPT by scanning the index DEPT_PK and then looking at 
the DEPT table.
Each matched record it stores aside again, and once all records in EMP have been processed, it then 
takes the
result set stored, and compares with SALGRADE table outputting the results to the select. 


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

a query like that will read the first row from EMP, find the deptno, look up the deptno and output 
that record to the next level where it will fullscan salgrade looking for the match and then output 
that to the client application. 

Then, onto the next row in emp, lookup deptno, full scan salgrade

over and over. 

5 stars Oh My Goodness !!   June 2, 2004 - 3am Central time zone
Reviewer: A reader 
At last

after 5 or 6 years using Oracle, I think I finally understand the way it works now ! Thanks so 
much. It makes it clear why nested loops can be so inefficient in certain circumstances. I never 
realised it would full scan SALGRADE for every row !

So, for hash joins, for the following plan

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2891 Card=10 Bytes=790)
   1    0   HASH JOIN (Cost=2891 Card=10 Bytes=790)
   2    1     TABLE ACCESS (FULL) OF 'I2' (Cost=963 Card=100 Bytes=2000)
   3    1     HASH JOIN (Cost=1927 Card=1000 Bytes=59000)
   4    3       TABLE ACCESS (FULL) OF 'I1' (Cost=963 Card=100 Bytes=2000)
   5    3       TABLE ACCESS (FULL) OF 'MAP' (Cost=963 Card=100000 


This is saying, Full scan I1, build a hash table in memory (or TEMP tablespace if cant fit in 
hash_area_size) and then full scan MAP, hash the key and probe the hash map in memory. Output all 
the matches to the next level, where I2 is full scanned, hashed into memory (or TEMP) and the same 
process happens, before all matched  records are output to the client. 

Is that right ?

There is no iteration, rows are only returned once all records have been processed / matched ?

Each of the tables are full scanned only once ?

Many, many thanks.

 


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

correct. 

3 stars Clarification   June 3, 2004 - 8am Central time zone
Reviewer: YenYang 
Im new to tuning and Im yet to go through your book on tuning. Im working on Oracle 9.2. 
Here are the 2 statements I have executed by interchanging the tables in FROM clause. 

After looking at the explain plan, all I could infer (with my little knowledge) is that Case 2 
requires less overhead (Cost=1 Card=1 Bytes=51) than Case 1 which requires (Cost=1 Card=1 
Bytes=64). Bytes=51 and Bytes=64. Is this what I should look at or something else also ? 

Case 1

SELECT mcb.consumer_id, mcb.routing_and_transit_number, mcb.consumer_account_number, 
    mcb.consumer_settlement_ref_number, bm.mem_id, 
    DECODE(mcb.account_type,'DDE',0,'SV',1,null),mcb.account_type 
FROM    cbmig_cons_bank mcb, 
    CBMIG_CONSUMER_CROSS_REF bm 
WHERE    mcb.consumer_id = bm.consumer_id 
AND    mcb.current_status = 'NOT_PROCESSED' 
AND    bm.current_status = 'PROCESSED' 
AND    bm.sponsor_id = 1


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=115)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'CBMIG_CONSUMER_CROSS_REF
          '

   2    1     NESTED LOOPS (Cost=1 Card=1 Bytes=115)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'CBMIG_CONS_BANK' (Co
          st=1 Card=1 Bytes=64)

   4    3         INDEX (RANGE SCAN) OF 'CBMIG_CONS_BANK_IDX1' (NON-UN
          IQUE) (Cost=1 Card=1)

   5    2       INDEX (RANGE SCAN) OF 'CBMIG_CONSUMER_CROSS_REF_IDX5'
          (NON-UNIQUE)

Case 2

SELECT mcb.consumer_id, mcb.routing_and_transit_number, mcb.consumer_account_number,
 mcb.consumer_settlement_ref_number, bm.mem_id,
 DECODE(mcb.account_type,'DDE',0,'SV',1,null),mcb.account_type
FROM CBMIG_CONSUMER_CROSS_REF bm, 
    cbmig_cons_bank mcb
WHERE bm.consumer_id = mcb.consumer_id
AND mcb.current_status = 'NOT_PROCESSED'
AND bm.current_status = 'PROCESSED'
AND bm.sponsor_id = 1


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=115)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'CBMIG_CONS_BANK'
   2    1     NESTED LOOPS (Cost=1 Card=1 Bytes=115)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'CBMIG_CONSUMER_CROSS
          _REF' (Cost=1 Card=1 Bytes=51)

   4    3         INDEX (RANGE SCAN) OF 'CBMIG_CONSUMER_CROSS_REF_IDX1
          ' (NON-UNIQUE) (Cost=1 Card=1)

   5    2       INDEX (RANGE SCAN) OF 'CBMIG_CONS_BANK_IDX1' (NON-UNIQ
          UE)


 


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

they are the same, there is no material difference between these two. 

3 stars Hash Join --Full Scan   June 16, 2004 - 10am Central time zone
Reviewer: READER from US
While doing hash join it always seen that it does a FTS to make the hash key.
Can this key be made based on an Index ?
Will this be efficient ?
Say we have two tables 50 millions and 1 million.
Going thru each and every row to prepare a hash key will be expensive compared to going thru the 
index .
Is it correct ?

Thanks . 


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

tell me how could going via an index 50 million times be MORE efficient than full scanning -- since 
you have to go to each row.

please keep repeating the following until you believe it:

"full scans are not evil"
"indexes are not the goal"


to process 50million rows, a full scan is going to be *brutally* efficient. 

5 stars Question probably not understood   June 16, 2004 - 8pm Central time zone
Reviewer: Logan Palanisamy from Santa Clara, CA USA
Tom,

You probably misunderstood the READER'S question. 

The person is asking why not "Fast Full Scan the Index" to build the hash-key table instead of 
"Full Scanning the Table"?

I too feel "Fast Full Scanning the Index" may be faster than the Full "Scanning the Table" to build 
the Hash-Keys table.

 


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

i disagree.

for you see -- if a fast full scan of the index would work, we do that.  we'll gladly and quickly 
use an index as a table -- given that the index has all of the columns we need to process the 
query.

so, I doubt it was that.

ops$tkyte@ORA9IR2> create table t1 as select * from all_objects where 1=0;
 
Table created.
 
ops$tkyte@ORA9IR2> create table t2 as select * from all_objects where 1=0;
 
Table created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create index t1_idx on t1(object_id,object_name,owner);
 
Index created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec dbms_stats.set_table_stats( user, 'T1', numrows => 10000000, numblks => 
100000 );
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> exec dbms_stats.set_table_stats( user, 'T2', numrows => 10000000, numblks => 
100000 );
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select t1.object_id, t1.owner, t2.object_name
  2    from t1, t2
  3   where t1.object_id = t2.object_id;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=25851 Card=10000000 Bytes=600000000)
   1    0   HASH JOIN (Cost=25851 Card=10000000 Bytes=600000000)
   2    1     INDEX (FAST FULL SCAN) OF 'T1_IDX' (NON-UNIQUE) (Cost=4 Card=10000000 
Bytes=300000000)
   3    1     TABLE ACCESS (FULL) OF 'T2' (Cost=9619 Card=10000000 Bytes=300000000)
 
fast full scan cause index = skinnier table for this query.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select t1.object_id, t1.owner, t1.status, t2.object_name
  2    from t1, t2
  3   where t1.object_id = t2.object_id;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=36638 Card=10000000 Bytes=650000000)
   1    0   HASH JOIN (Cost=36638 Card=10000000 Bytes=650000000)
   2    1     TABLE ACCESS (FULL) OF 'T2' (Cost=9619 Card=10000000 Bytes=300000000)
   3    1     TABLE ACCESS (FULL) OF 'T1' (Cost=9619 Card=10000000 Bytes=350000000)
 
no index because index did not contain all of the information we needed, we would have to go to 
the table after all and that would be heinously slow 
 
 
ops$tkyte@ORA9IR2> set autotrace off
 

1 stars Fast Full scan ...   June 17, 2004 - 7am Central time zone
Reviewer: Reader from US
I would appreciate your views on a fast full scan vs FTS on making hask key on a 50 million row 
table.

Thanks  


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

see above, it just happens. 

3 stars Why this   June 18, 2004 - 12am Central time zone
Reviewer: Goh Seong Hin from Malaysia
Dear Tom,

   If i am not mistaken, the optimizer will use the index if you add in this line (and t1.object_id 
< 1000 in your previous SQL

   select t1.object_id, t1.owner, t1.status, t2.object_name
    from t1, t2
   where t1.object_id = t2.object_id
     and t1.object_id < 1000;

   Can you help to elaborate why the optimizer will choose index range for this case ? 

    Thanks.

Rgds,
SHGoh 


Followup   June 18, 2004 - 10am Central time zone:

sure, indexes can, are, will be used -- absolutely.  I showed that above.

The question typically stems from:

I have

 select t1.object_id, t1.owner, t1.status, t2.object_name
    from t1, t2
   where t1.object_id = t2.object_id

ad object_id is indexed, why isn't the optimizer using the index.


the answer is "cause it would be the worst thing on the planet to actually do" 

5 stars same query, differet executing sys/system different plan, how so?!   July 1, 2004 - 7am Central time zone
Reviewer: A reader 
Hi

I have a query which isnt performed as good as desire, it reads 15 i/o in 8.1.7 and 100 in 
10.1.0.2.0. 
However in 10g if I run the qurey under sys or system it only uses 10 I/O.

The query has owner prefixed:

SELECT   libro.c_nivel, libro.c_asignatura, libro.c_titulo, libro.n_alumnos
    FROM sim.libro_usado libro, sim.definicion_mercado def
   WHERE libro.c_centro = :b7
     AND libro.c_nivel = :b6
     AND libro.c_asignatura = :b5
     AND libro.b_cerrado_periodo = 'N'
     AND libro.b_bloqueado = 'N'
     AND libro.b_estado = 'N'
     AND libro.c_titulo_es_adoptado IN (
                          SELECT titulo.c_titulo
                            FROM sim.titulo
                           WHERE titulo.c_tipo_titulo IN (
                                                          SELECT c_tipo_titulo
                                                            FROM sim.tipo_titulo
                                                           WHERE t_clase = 'T'))
     AND def.c_pais = :b4
     AND def.c_autonoma = :b3
     AND def.c_provincia = :b2
     AND def.c_periodo = :b1
     AND def.c_nivel = libro.c_nivel
     AND def.b_def_mercado = 'S'
     AND def.c_edicion = libro.c_edicion_es_adoptado
     AND def.c_idioma = libro.c_idioma_es_adoptado
     AND def.c_asignatura = libro.c_asignatura
     AND def.b_bloqueado = 'N'
ORDER BY libro.c_nivel, libro.c_asignatura;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=4 Card=1 Bytes=88)
   1    0   TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'LIBRO_USADO' (TABLE) (Cost=1 Card=1 Bytes=46)
   2    1     NESTED LOOPS (Cost=2 Card=1 Bytes=88)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'DEFINICION_MERCADO' (TABLE) (Cost=1 Card=1 
Bytes=42)
   4    3         INDEX (RANGE SCAN) OF 'DEF__MERCADO_UK' (INDEX (UNIQUE)) (Cost=1 Card=1)
   5    2       INDEX (RANGE SCAN) OF 'LUS_LOCALIZA_UK' (INDEX (UNIQUE)) (Cost=1 Card=1)
   6    5         NESTED LOOPS (Cost=2 Card=1 Bytes=18)
   7    6           TABLE ACCESS (BY INDEX ROWID) OF 'TITULO' (TABLE) (Cost=1 Card=1 Bytes=11)
   8    7             INDEX (UNIQUE SCAN) OF 'TITULO_PK' (INDEX (UNIQUE)) (Cost=1 Card=1)
   9    6           TABLE ACCESS (BY INDEX ROWID) OF 'TIPO_TITULO' (TABLE) (Cost=1 Card=1 Bytes=7)
  10    9             INDEX (UNIQUE SCAN) OF 'TTIT_PK' (INDEX (UNIQUE)) (Cost=0 Card=1)

Statistics
----------------------------------------------------------
         16  recursive calls
          0  db block gets
         10  consistent gets
          0  physical reads
          0  redo size
        414  bytes sent via SQL*Net to client
        373  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=7 Card=1 Bytes=113)
   1    0   TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'LIBRO_USADO' (TABLE) (Cost=1 Card=1 Bytes=46)
   2    1     NESTED LOOPS (Cost=5 Card=1 Bytes=113)
   3    2       NESTED LOOPS (Cost=4 Card=1 Bytes=67)
   4    3         NESTED LOOPS (Cost=3 Card=1 Bytes=25)
   5    4           NESTED LOOPS (Cost=2 Card=1 Bytes=10)
   6    5             INDEX (UNIQUE SCAN) OF 'ASIGNATURA_PK' (INDEX (UNIQUE)) (Cost=0 Card=1 
Bytes=5)
   7    5             INDEX (UNIQUE SCAN) OF 'NIVEL_PK' (INDEX (UNIQUE)) (Cost=0 Card=1 Bytes=5)
   8    4           INDEX (UNIQUE SCAN) OF 'PROVINCIA_PK' (INDEX (UNIQUE)) (Cost=0 Card=1 Bytes=15)
   9    3         TABLE ACCESS (BY INDEX ROWID) OF 'DEFINICION_MERCADO' (TABLE) (Cost=1 Card=1 
Bytes=42)
  10    9           INDEX (RANGE SCAN) OF 'DEF_MERCAD_PROVINCIA_FK_I' (INDEX) (Cost=0 Card=1)
  11    2       INDEX (RANGE SCAN) OF 'LUS_LOCALIZA_UK' (INDEX (UNIQUE)) (Cost=1 Card=1)
  12   11         NESTED LOOPS (Cost=2 Card=1 Bytes=18)
  13   12           TABLE ACCESS (BY INDEX ROWID) OF 'TITULO' (TABLE) (Cost=1 Card=1 Bytes=11)
  14   13             INDEX (UNIQUE SCAN) OF 'TITULO_PK' (INDEX (UNIQUE)) (Cost=1 Card=1)
  15   12           TABLE ACCESS (BY INDEX ROWID) OF 'TIPO_TITULO' (TABLE) (Cost=1 Card=1 Bytes=7)
  16   15             INDEX (UNIQUE SCAN) OF 'TTIT_PK' (INDEX (UNIQUE)) (Cost=0 Card=1)

Statistics
----------------------------------------------------------
         80  recursive calls
          0  db block gets
        102  consistent gets
          0  physical reads
          0  redo size
        414  bytes sent via SQL*Net to client
        373  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processede


How so? Why owner needs 102 consistent gets and system only 10? 


Followup   July 1, 2004 - 11am Central time zone:

do not use sys
do not use system

pretend they do not exist

they are not for your use

they are ours

they are not accounts to be used

ignore them

forget they exist


use your own accounts. 

4 stars how do you get the parse,fetch statistics   July 7, 2004 - 10pm Central time zone
Reviewer: ramakrishna from india
Hi Tom , im a beginner to this DBA job, reading all your articles on this site and your books. I 
want to know, in most of the articles, i think the solution which you send is from your laptop,i 
mean the sql demonstrations which you show giving solutions. i have observed, you must have enabled 
some feature where you get the parse,execute and fetch statistics without using tkprof???am i 
right. how do i enable this ???

thanx in advance 


Followup   July 8, 2004 - 8am Central time zone:

i use tkprof all of the time.... 

I use a sun box, linux server, desktop, and a laptop for examples -- they all look the same though 
as it is always 

o autotrace
o explain plan
o tkprof 
o runstats

showing the results -- if you have my book "Effective Oracle By Design" -- i spent a chapter 
covering the tools I use. 

3 stars   July 29, 2004 - 7am Central time zone
Reviewer: A reader from UK
Hi All,
       I have the following query which runs in 150 millisecond when invoked from a single user 
environment but while doing concurrent users test(25 users) the same query takes 3 seconds to 
complete.The table (tab1) on which this query is fired contains 50 million rows.Can anyone please 
help me out in tuning this query.
      
Query
------
SELECT /*+ INDEX (tab1 PK_tab1) */ TR_SUB_PREM_KEY, 
CIR_KEY,CIR_TYPE,locality,LTRIM(RTRIM(SUBSTR(source_postcode,1,DECODE((INSTR(source_postcode,' 
')-1),-1,4,(INSTR(source_postcode,' ')-1))))), 
LTRIM(RTRIM(SUBSTR(source_postcode,DECODE((INSTR(source_postcode,' 
')+1),1,5,(INSTR(source_postcode,' ')+1))))), 
FROM tab1 
WHERE tab1.TR_SUB_PREM_KEY='R00000016682' .

Execution Plan
----------------------------------------------------------
   0
SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=5 Card=1 Bytes=97)
 

   1    0
  TABLE ACCESS (BY INDEX ROWID) OF 'tab1' (Cost=5 Card=1 Bytes=97)
 

   2    1
    INDEX (RANGE SCAN) OF 'PK_tab1' (NON-UNIQUE) (Cost=4 Card=1)
 
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          6  consistent gets
          1  physical reads
          0  redo size
       2199  bytes sent via SQL*Net to client
        655  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed


 


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

so, how many cpus do you have.

 
ops$tkyte@ORA9IR2> select .15 * 25 from dual;
 
    .15*25
----------
      3.75
 

5 stars employee table lookup   July 29, 2004 - 11am Central time zone
Reviewer: Sudhir from Kenton, OH
Tom,

I searched for the 'employee table lookup' and landed here. What I was trying to find was the 
reference to what you say in your book - an application you had developed inhouse to do employee 
lookup and you build a temporary flat table and so forth.

Here is one idea I though about it


@?/rdbms/admin/utlsampl.sql
connect scott/tiger
create table empvert(key varchar2(100),colname varchar2(50), rid rowid);



insert into scott.empvert
(
select empno||'','EMPNO',rowid rid from emp
union all
select ename,'ENAME',rowid rid from emp
union all
select job,'JOB',rowid rid from emp
union all
select MGR||'','MGR',rowid rid from emp
union all
select to_char(hiredate,'YYYYMMDDHH24MISS'),'HIREDATE',rowid rid from emp
union all
select deptno||'','DEPTNO',rowid rid from scott.emp
);
create index si_empvert_1 on empvert(key);
create index si_empvert_2 on empvert(COLNAME);


  1  select * from emp where rowid in (
  2* select rid from empvert where key like '&key' and colname = '&col')
/
Enter value for key: M%
Enter value for col: ENAME
old   2: select rid from empvert where key like '&key' and colname = '&col')
new   2: select rid from empvert where key like 'M%' and colname = 'ENAME')

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
---------- ---------- --------- ---------- --------- ---------- ----------
    DEPTNO
----------
      7654 MARTIN     SALESMAN        7698 28-SEP-81       1250       1400
        30

      7934 MILLER     CLERK           7782 23-JAN-82       1300
        10

  1   select * from emp where rowid in (
  2* select rid from empvert where key like '&key%' )
/
Enter value for key: J
old   2: select rid from empvert where key like '&key%' )
new   2: select rid from empvert where key like 'J%' )

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
---------- ---------- --------- ---------- --------- ---------- ----------
    DEPTNO
----------
      7566 JONES      MANAGER         7839 02-APR-81       2975
20

      7900 JAMES      CLERK           7698 03-DEC-81        950
        30


/
Enter value for key: 78
old   2: select rid from empvert where key like '&key%' )
new   2: select rid from empvert where key like '78%' )

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM
---------- ---------- --------- ---------- --------- ---------- ----------
    DEPTNO
----------
      7566 JONES      MANAGER         7839 02-APR-81       2975
        20

      7698 BLAKE      MANAGER         7839 01-MAY-81       2850
        30

      7782 CLARK      MANAGER         7839 09-JUN-81       2450
        10

      7839 KING       PRESIDENT            17-NOV-81       5000
        10

      7844 TURNER     SALESMAN        7698 08-SEP-81       1500          0
        30

      7876 ADAMS      CLERK           7788 23-MAY-87       1100
        20


6 rows selected.

What do you think?

Thanks

 


Followup   July 29, 2004 - 1pm Central time zone:

don't understand why you would do that when a normal index on ENAME would be much more efficient? 

2 stars My point is   July 29, 2004 - 9pm Central time zone
Reviewer: A reader 
You can search on any column value with sql and with one index. For example key like 'MANAGER', 
will give you all managers. Or key like 'JAMES' will give all rows with that word.
Thats why I named empvert(icle) as table derived from emp.
thanks
 


Followup   July 30, 2004 - 7am Central time zone:

so?  why is this redundant data better then have an index per column on the already stored data.

what have you improved here? 

2 stars for the reason ...   July 30, 2004 - 8pm Central time zone
Reviewer: A reader 
you don't need to know which column the user specified key exists.

thanks 


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

does not compute -- don't know what you mean by "you don't need to knwo which column the user 
specified key exists" means.


I'm not seeing the point here.  if you have:

a) the table emp
b) indexes on the columns you want to search on
c) you generate the query "select * from emp where ename = :x"

you'll use an index.  in fact, if you

where ename = :x or job = :y


where ename = :x and job = :y

we'll be able to not only actually answer those questions easily (try that with your inverted 
table), we'd be able to use the indexes as well.



What it looks like you are trying to reinvent is the multi column datastore index already available 
in Oracle text and I do not see the benefit here in this case. 

2 stars ok, here is one way   August 1, 2004 - 7am Central time zone
Reviewer: A reader 
where ename = :x and job = :y

select * from emp where rowid in (
select rid from empvert where key = :x and
colname = 'ENAME' and rid in
(select rid from empvert where key = :y and colname='JOB' )
)

where ename = :x or job = :y

select * from emp where rowid in (
select rid from empvert where key = :x and
colname = 'ENAME' or
select rid from empvert where key = :y and colname='JOB'
)

I am just trying out something.

thanks 


Followup   August 1, 2004 - 11am Central time zone:

"we'll be able to not only actually answer those questions easily "
                                                           ^^^^^^


believe me -- I know you *can* write the queries (in fact, in Effective Oracle by Design -- i 
demonstrate it -- to show how wrong a model like this is).

the key word here is "easily"



Now think about the case where you have 1,000,000 rows "where key = :y and colname = 'JOB'" and 1 
row "where key = :x and colname = 'ENAME'"

and if you say "ok, i'll flip them", then I'll flip them myself.  Basically to write this easily 
and efficiently -- you need to maintain your own statistics so you know how to build the query and 
so on.


Thanks but I'll stick with "where ename = :x or job = :y"


this solution calls for either:

a) index the columns in the table and just do it as "normal"
b) use a multi-column datastore index from Oracle text




 

3 stars How to do this?   August 20, 2004 - 3pm Central time zone
Reviewer: A reader 
A query runs fine in the UAT environment but tanks in Production. The hardware on both environments 
is the same, the data volume is the same, stats gathered on both using 'dbms_stats' (cascade=>true, 
method_opt=>'for all indexed columns')

On UAT, it runs in 7 seconds, on Prod, it takes 700 seconds. The execution plan is different. On 
both databases, the stats in the dictionary are very close to reality. i.e. select count(*) from 
table is the same as dba_Tables.num_rows and so on.

One interesting thing is that if I add /*+ first_rows */ to the Prod query, the plan becomes 
identical to the UAT query and the execution time also drops down to almost 7 seconds!

How would you go about solving this problem?

Thanks 


Followup   August 21, 2004 - 11am Central time zone:

i'd get the 10053 trace file from both systems and compare them to see what parameters I've set 
differently as the 10053 trace will dump out the init.ora settings used by the optimizer.  a simple 
diff on those files would probably tell me what I changed between the two environments. 

4 stars How to do this?   August 21, 2004 - 10am Central time zone
Reviewer: A reader 
OK I ran explain plan in both databases and did

select distinct operation,owner,object_name from plan_table
where operation in ('TABLE ACCESS','INDEX');

Made sure that stats on all these objects are up-to-date.

The plans are still different between the 2 environments! :(

How to go about solving this? Why does the Prod env need the FIRST_ROWS to get a good plan, but the 
UAT env figures that out by itself? [The data volumes in the tables are identical between the 2 
environments]

Thanks 


Followup   August 21, 2004 - 12pm Central time zone:

read above please -- 10053 trace.  

if you don't know what that is, search for it on this site, or see chapter 6 in my book Effective 
Oracle by Design.

your init.ora's are probably different and you've given the optimizer different settings -- one of 
which is not appropriate. 

4 stars How to do this?   August 21, 2004 - 11pm Central time zone
Reviewer: A reader 
You are right, as usual. 

After a lot of elimination and comparing the 2 environments, the determining factor turned out to 
be the db_file_multiblock_read_count

It is set to 32 on UAT and 64 on Prod.

Changing it has a huge impact on this particular query

Lowering it from 32 to 16 doesnt harm UAT
Increasing it from 32 to 64 makes UAT tank
Increasing it from 32 to 128 makes UAT tank

Lowering it from 64 to 32 doesnt change Prod (still bad)
Lowering it from 64 to 16 makes Prod speed up a lot
Increasing it from 64 to 128 also makes Prod speed up a lot

Questions:

What is the meaning of all this? Why is this parameter having such a dramatic impact on the query? 

Also, why is the effect not the same in both directions (increasing vs. decreasing) on both the 
databases?

What is the best way to set this parameter initially, monitor its effect and tune incrementally?

How much does the underlying hardware have to do with this parameter? [In this case, UAT is a Sun 
E450 with RAID 5 LUNs on a A1000. Prod is a Sun V880 with RAID 0+1 LUNs on a A1000]

How can I set this parameter in a methodical way considering the underlying hardware for each 
database? Are there any disk I/O measurements I can take to help me?

Help! Thanks 


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

that parameter dramatically affects the cost of a full table scan.

bigger values -> full scan cheaper
smaller values -> full scan more expensive

I would leave this (and most all parameters) unchanged, at their default, unless I had good reason 
to change them.


you would compare the 10053 traces to see what is different between the two environments.


you would use system statistics (gathered via dbms_stats) in order to give the optimizer accurate 
information about your systems cpu, single block and multiblock IO capabilities. 

5 stars   August 22, 2004 - 9am Central time zone
Reviewer: A reader 
1. Thanks, but why would the effect of increasing/decreasing it not be the same on both the 
databses? Since the underlying hardware is different, I can understand if the real performance of 
the query is different, but at least the explain plan's should behave the same on both databases in 
response to increasing/decreasing it, right?

"I would leave this (and most all parameters) unchanged, at their default, unless I had good reason 
to change them"

2. Thats what I meant, what would be a "good reason" to change them. OK so you are saying the 
default (8) is a good initial value. 

3. How can I monitor the effect of this value 8 on my system and see if I would benefit from a 
higher number?

4. My RAID system (Sun A1000)has a very high throughput, read prefetching, caching, etc that would 
effectiely be wasted if I set this to the default of 8, wouldnt it?

"you would compare the 10053 traces to see what is different between the two environments"

5. Hm, it is really difficult to compare 10053 traces from 2 databases, most everything looks 
different. Eyeballing it is not easy. Is there a efficient way to compare these (except 'diff 
f1.trc f2.trc')?


"you would use system statistics (gathered via dbms_stats) in order to give the optimizer accurate 
information about your systems cpu, single block and multiblock IO capabilities"

6. OK so would dbms_stats.gather_system_stats make it such that the value of 
db_file_multiblock_read_count is irrelevant? No matter what I set it to, Oracle would "know" my 
CPU/disk speeds so it would automatically (internally) adjust the value of multiblock_read_count?

Thanks 


Followup   August 22, 2004 - 4pm Central time zone:

1) as i said, compare the 10053 traces -- what did you see in there that was different.  that will 
TELL you what was different.  

2) a good reason would be support asking you to for example.... 

3) benchmarking on the actual hardware 

4) no?!?  

5) they should be virtually the same if all is as you say "these databases are the same".  that was 
the point, if they are radically different, these databases cannot even begin to be compared to 
eachother.

6) no, it makes it so the optimizer better understands you IO characteristics, how your CPU's do.  
it would still read 8 blocks in a single IO when performing multi-block IO. 

5 stars db scattered file wait event   August 22, 2004 - 3pm Central time zone
Reviewer: A reader 
I tried the measurements you mention in

http://asktom.oracle.com/pls/ask/f?p=4950:8:6896111968866818991::NO::F4950_P8_DISPLAYID,F4950_P8_CRI
TERIA:4433887271030,
and came up with p3=128 i.e. my OS max IO size also seems to be 1M (my db_block_size is 8K)

So, I should set my db_file_mbrc=128, right? 

I have it currently set to 64. And as I said, performance (of this one query) is good if I set this 
parameter either to 32 or 128! I cant figure out why both lowering it and increasing it improve 
performance!

My concern is that I dont want do "dumb down" my system or give Oracle a lowest common denominator 
view of my system's IO capabilities if I can avoid it. The RAID A1000 has a tremendous IO bandwidth 
(rated at 40MB/sec, UltraSCSI3) that I would like to leverage and not waste.

The gather_system_stats looks like a good feature, but I cant find any docs on how these work with 
the mbrc parameter.

Thanks 


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

no, i would let it default to 8.  


lowering it results in a different plan.

increasing it results in larger IO's


use dbms_stats to gather system stats.

they do not "work with" the mbrc -- they give the optimizer information about the true cost of IO 
on your system period. 

4 stars mbrc   August 22, 2004 - 8pm Central time zone
Reviewer: A reader 
Why do you say that I should let MBRC default to 8?

For a 8k block size that is just 64K read in a single IO request. My OS and disk subsystem is able 
to handle upto 1MB in a single IO request. 

So why should I throttle it down?

On a related note, since this is a ALTER SYSTEM dynamic parameter, when I change it to 8, will all 
my plans get invalidated in the shared pool and be hard-parsed the next time they are executed?

Thanks 


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

because I believe all parameters that don't have to be set, shouldn't be set.

do you want to be doing full scans frequently?
are you doing full scans frequently?
do you want the cost of a full scan to be lower than it is now?


if you change a parameter used by the CBO, it'll cause a child cursor to be created if one already 
exists -- that child cursor will be for "that environment" 

5 stars mbrc   August 23, 2004 - 9am Central time zone
Reviewer: A reader 
"because I believe all parameters that don't have to be set, shouldn't be set"

right, but it is a regular parameter not a hidden "_" parameter, so Oracle does support 
changing it.

Not to sound like a broken record, but I would have thought this would be a perfectly valid 
situation to change it i.e. my hardware can support it, it is not as if I am lying to the 
optimizer.

So under what circumstances would you (or Support) recommend changing it?

Am I full scanning tables frequently? How can I tell? I look at v$sysstat.table scans (long tables) 
and I see a number. How can I tell if this number is good or bad?

"if you change a parameter used by the CBO, it'll cause a child cursor to be created if one already 
exists -- that child cursor will be for "that environment""

Not sure I understand this. I meant ALTER SYSTEM not ALTER SESSION. If I ALTER SYSTEM this 
parameter, would all cursors in shared pool be invalidated and hard-parsed next time?

Thanks 


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

of course we do -- caveat emptor.   

i've already told you when I would change it -- i wouldn't.  


you should know if you intend on scanning tables frequently, it would be part of your design from 
day one?


if your dfmbrc was 8 and you parsed "select * from t" -- it would be there in v$sql.

if you alter the dfmbr from 8 to 16 and parse "select * from t" -- it would create a child cursor 
under the existing query  that was parsed with dfmbrc of 8.

regardless of system or session setting.

the cursors are not invalidated by a simple parameter change, but new child cursors would be "seen"


 

5 stars Different results with different execution plans   August 26, 2004 - 10am Central time zone
Reviewer: A reader 
Hi Tom,

Is it possible that for the same query, the database returns different results by using different 
execution plans (giving it different hints etc.)?

I remember I had one such situation before but I can't remember how the query was written.

Do you know if this is possible?

Thanks. 


Followup   August 26, 2004 - 10am Central time zone:

if it happens it would be what is known as "a bug" 

3 stars 15 Table Join   October 18, 2004 - 6am Central time zone
Reviewer: chetan from INDIA
Dear Sir,

I have a Multi Table view with outer joins. It takes REAL : 1200 to return 158 rows.

please tell me what is the way out to optimize this so that it can return me the data at a faster 
rate.

CREATE OR REPLACE VIEW VIEW_ENQUIRY AS
(
SELECT DISTINCT
EM.ECODE AS ECODE,
EM.eID AS EID,                 
EM.eDate AS EDATE,            
EM.vDate AS VDATE,            
EM.EMode AS EMODE,            
EM.Cust_Code AS CUST_CODE,
EM.Contact_Code AS CONTACT_CODE,
EM.Prod_Code AS PROD_CODE,        
EM.Qty_Required AS QTY,
EM.Qty_Unit AS QTY_UNIT_CODE,            
EM.Qty_Detail AS QTY_DETAIL,        
EM.PayTerms AS PAYTERMS,    
EM.PayMode  AS PAYMODE,     
EM.ShipTo    AS SHIPTO,    
EM.SpecReqired AS SPECS,
EM.SampRequired AS SAMPLE,
EM.Shipment1 AS SHIPMENT1,
EM.Shipment2 AS SHIPMENT2,
EM.Shipment3 AS SHIPMENT3,
EM.Compete_Rate AS COMPETE_RATE,
EM.Compete_Unit AS COMPETE_UNIT_CODE,
EM.RecDate  AS RECORD_DATE,
EM.UpDateDate AS UPDATE_DATE,
EM.RecCreated AS RECCREATEDBY,
EM.RecUpdated AS RECUPDATEDBY,
    PRODUCT_MASTER.PROD_NAME AS PRODUCT_NAME,
    PRODUCT_MASTER.CAS_NO AS PRODUCT_CASNO,
    --
    QTY_UNIT_MASTER.UNIT_NAME AS QTY_UNIT_NAME,
    --ENQ_QTY_UNIT.UNIT_CODE AS QTY_UNIT_CODE,
    --
    COMP_UNIT_MASTER.UNIT_NAME AS COMPETE_UNIT_NAME,
    --ENQ_COMPETE_UNIT.UNIT_CODE AS COMPETE_UNIT_CODE,
    --
    --ENQ_PARTY_MASTER.PARTY_CODE AS PARTY_CODE,
    PARTY_MASTER.PARTY_NAME AS PARTY_NAME,
    --
    CONTACT_MASTER.FNAME||DECODE(NVL(CONTACT_MASTER.SNAME,''),'','',' 
')||CONTACT_MASTER.SNAME||DECODE(NVL(CONTACT_MASTER.LNAME,''),'','',' ')||CONTACT_MASTER.LNAME AS 
CONTACT_PERSON,
    ADRS_MASTER.ADRS_1 AS ADRS_1,
    ADRS_MASTER.ADRS_2 AS ADRS_2,
    ADRS_MASTER.STREET_NAME AS STREET_NAME,
    ADRS_MASTER.PIN AS PIN,
    --
    COMM_MASTER.OFF_TEL_1||DECODE(NVL(COMM_MASTER.OFF_TEL_2,''),'','',' ; ')||COMM_MASTER.OFF_TEL_2 
AS TEL,
    COMM_MASTER.FAX_1||DECODE(NVL(COMM_MASTER.FAX_2,''),'','',' ; ')||COMM_MASTER.FAX_2 AS FAX,
    COMM_MASTER.OFF_EMAIL || DECODE(NVL(COMM_MASTER.PERS_EMAIL,''),'','',' ; 
')||COMM_MASTER.PERS_EMAIL AS  EMAIL,
    --
    CITY_MASTER.CITY_NAME AS CITY_NAME,
    --
    STATE_MASTER.STATE_NAME AS STATE_NAME,
    --
    COUNTRY_MASTER.COUNTRY_NAME AS COUNTRY_NAME,
    --
    USER_MASTER.USER_CODE AS USER_CODE,
    USER_MASTER.USER_NAME AS USER_NAME,
    --
    ES.STATUS AS STATUS,
    ES.ESTATUS AS STATUS_CODE
From
ENQUIRY_MASTER EM,
USER_MASTER USER_MASTER,
ENQUIRYPROCESS EP,
ENQUIRY_STATUS ES,
    PRODUCT_MASTER PRODUCT_MASTER,
    UNIT_MASTER QTY_UNIT_MASTER,
    UNIT_MASTER COMP_UNIT_MASTER,
    PARTY_MASTER PARTY_MASTER,
    CONTACT_PERSON CONTACT_MASTER,
        COMM_MASTER COMM_MASTER,    
        ADDRESS_MASTER    ADRS_MASTER,
            CITY_MASTER CITY_MASTER,
            STATE_MASTER STATE_MASTER,
            COUNTRY_MASTER COUNTRY_MASTER
WHERE
EM.Prod_Code =    PRODUCT_MASTER.PROD_CODE AND
EM.Qty_Unit =   QTY_UNIT_MASTER.UNIT_CODE(+) AND
EM.Compete_Unit =   COMP_UNIT_MASTER.UNIT_CODE(+) AND
EM.Cust_Code =    PARTY_MASTER.PARTY_CODE AND
EM.Contact_Code =   CONTACT_MASTER.CONT_PERS_CODE AND
EP.ECODE = EM.ECODE AND
USER_MASTER.USER_CODE = EP.USERCODE AND
ES.ESTATUS = EP.STATUS AND
    PARTY_MASTER.COMM_CODE =  COMM_MASTER.COMM_CODE(+) AND
    PARTY_MASTER.ADRS_CODE =  ADRS_MASTER.ADRS_CODE(+) AND
        ADRS_MASTER.CITY_CODE =    CITY_MASTER.CITY_CODE(+) AND
        aDRS_MASTER.STATE_CODE =   STATE_MASTER.STATE_CODE(+) AND
        ADRS_MASTER.COUNTRY_CODE = COUNTRY_MASTER.COUNTRY_CODE(+)
)
/


i hope you will be able to solve my problem ...here is the  CBO results as follows ::::::::]

L> set autotrace traceonly
L> set timing on
L> select count(*) from view_enquiry;

eal: 17395

ecution Plan
--------------------------------------------------------
 0      SELECT STATEMENT Optimizer=CHOOSE
 1    0   SORT (AGGREGATE)
 2    1     VIEW OF 'VIEW_ENQUIRY'
 3    2       SORT (UNIQUE)
 4    3         NESTED LOOPS
 5    4           NESTED LOOPS (OUTER)
 6    5             NESTED LOOPS (OUTER)
 7    6               NESTED LOOPS (OUTER)
 8    7                 NESTED LOOPS (OUTER)
 9    8                   NESTED LOOPS (OUTER)
10    9                     NESTED LOOPS (OUTER)
11   10                       NESTED LOOPS (OUTER)
12   11                         NESTED LOOPS
13   12                           NESTED LOOPS
14   13                             NESTED LOOPS
15   14                               NESTED LOOPS
16   15                                 NESTED LOOPS
17   16                                   TABLE ACCESS (FULL) OF 'EN
        QUIRYPROCESS'

18   16                                   TABLE ACCESS (BY INDEX ROW
        ID) OF 'ENQUIRY_STATUS'

19   18                                     INDEX (UNIQUE SCAN) OF '
        SYS_C005354' (UNIQUE)

20   15                                 TABLE ACCESS (BY INDEX ROWID
        ) OF 'USER_MASTER'

21   20                                   INDEX (UNIQUE SCAN) OF 'PK
        _USER_CODE' (UNIQUE)

22   14                               TABLE ACCESS (BY INDEX ROWID)
        OF 'ENQUIRY_MASTER'

23   22                                 INDEX (UNIQUE SCAN) OF 'SYS_
        C005667' (UNIQUE)

24   13                             TABLE ACCESS (BY INDEX ROWID) OF
         'CONTACT_PERSON'

25   24                               INDEX (UNIQUE SCAN) OF 'PK_CON
        TACT_PERSON' (UNIQUE)

26   12                           TABLE ACCESS (BY INDEX ROWID) OF '
        PARTY_MASTER'

27   26                             INDEX (UNIQUE SCAN) OF 'PK_PARTY
        _MASTER' (UNIQUE)

28   11                         TABLE ACCESS (BY INDEX ROWID) OF 'AD
        DRESS_MASTER'

29   28                           INDEX (UNIQUE SCAN) OF 'PK_ADDRESS
        _MASTER' (UNIQUE)

30   10                       TABLE ACCESS (BY INDEX ROWID) OF 'COUN
        TRY_MASTER'

31   30                         INDEX (UNIQUE SCAN) OF 'PK_COUNTRY_M
        ASTER' (UNIQUE)

32    9                     TABLE ACCESS (BY INDEX ROWID) OF 'STATE_
        MASTER'

33   32                       INDEX (UNIQUE SCAN) OF 'PK_STATE_MASTE
        R' (UNIQUE)

34    8                   TABLE ACCESS (BY INDEX ROWID) OF 'CITY_MAS
        TER'

35   34                     INDEX (UNIQUE SCAN) OF 'PK_CITY_MASTER'
        (UNIQUE)

36    7                 TABLE ACCESS (BY INDEX ROWID) OF 'COMM_MASTE
        R'

37   36                   INDEX (UNIQUE SCAN) OF 'SYS_C006283' (UNIQ
        UE)

38    6               TABLE ACCESS (BY INDEX ROWID) OF 'UNIT_MASTER'
39   38                 INDEX (UNIQUE SCAN) OF 'SYS_C005120' (UNIQUE
        )

40    5             TABLE ACCESS (BY INDEX ROWID) OF 'UNIT_MASTER'
41   40               INDEX (UNIQUE SCAN) OF 'SYS_C005120' (UNIQUE)
42    4           TABLE ACCESS (BY INDEX ROWID) OF 'PRODUCT_MASTER'
43   42             INDEX (UNIQUE SCAN) OF 'PK_PRODUCT_MASTER' (UNIQ
        UE)





atistics
--------------------------------------------------------
      900  recursive calls
      143  db block gets
     3643  consistent gets
      727  physical reads
    13980  redo size
      564  bytes sent via SQL*Net to client
      661  bytes received via SQL*Net from client
        4  SQL*Net roundtrips to/from client
        1  sorts (memory)
        1  sorts (disk)
        1  rows processed




-------------------------
I am really tensed with this results need your help badly ...
its oracle 8.0.3 Server
On NT 4 with 512 MB of Ram
 


Followup   October 18, 2004 - 8am Central time zone:

you are using the RBO, not the CBO there.  Nest loops join "heck".


8.0.3 -- on an nt server with 512mb -- surprised it ran that fast.... 


 

3 stars   October 20, 2004 - 5pm Central time zone
Reviewer: A reader 
Tom,
Below is from Oracle Support

The plans generated by the CBO depend upon the sizes of the tables, and potentially on the data 
distributi
ons
as well, if histograms are being used. When using the CBO with a small amount of data to test an 
application
prototype, do not assume that the plan chosen for the full-size database will be the same as that 
chosen for
the prototype. 

Is it true that If I have a well tuned query that's tested in Development with less data, can be 
slow in production environment with more data? Could you please comment on it?
If so, what are the factors that I should consider.. 
Thanks in advance

 


Followup   October 20, 2004 - 8pm Central time zone:

absolutely true.  and the converse, and "neither"

the factors you should consider is that unless you have a test environment that acurately reflects 
your production system (not only in data, but the load you put it under), you will have no idea how 
the system you are inflicting on your end users will actually perform.

and remember, end users are like elephants -- they have exceedingly long memories.

 

5 stars   November 5, 2004 - 1pm Central time zone
Reviewer: A reader 
TOM,

One of the tables here has an id column which is the only pk and some more columns. A query run on 
the date column is performing a full scan. Is there a way to optimize that query? Also, in 
reporting purposes where a date column is not a part of pk and fastness is required, what needs to 
be done? Thanks 


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

create index on t(date_column)?

assuming that date_column is selective enough. 

5 stars   November 6, 2004 - 6pm Central time zone
Reviewer: A reader 
Tom,

Thank you, before creating index I wanted to show you how this is: Please comment

SQL> explain plan for  SELECT count(*) FROM alphatab WHERE to_char(createdate,'mm/dd/yyyy') = 
(SELECT to_char(sysdate-4,'mm/dd/yyyy') FROM DUAL) 
  2  /
 
Explained.
 
SQL> @?/rdbms/admin/utlxpls
 
Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     1 |    7 |   8921 |       |       |
|  SORT AGGREGATE           |          |     1 |    7 |        |       |       |
|   FILTER                  |          |       |      |        |       |       |
|    TABLE ACCESS FULL      | alphatab |    15K|  106K|   8921 |       |       |
|    TABLE ACCESS FULL      |DUAL      |    82 |      |      1 |       |       |
--------------------------------------------------------------------------------
 
8 rows selected.
 
SQL> set timing on
SQL> set autotrace traceonly
SQL> SELECT count(*) FROM alphatab WHERE to_char(createdate,'mm/dd/yyyy') = (SELECT 
to_char(sysdate-4,'mm/dd/yyyy') FROM DUAL)
  2  /
 
Elapsed: 00:01:14.65
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8921 Card=1 Bytes=7)
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       TABLE ACCESS (FULL) OF 'ALPHATAB' (Cost=8921 Card=1
          5572 Bytes=109004)
 
   4    2       TABLE ACCESS (FULL) OF 'DUAL' (Cost=1 Card=82)
 
 
 
 
Statistics
----------------------------------------------------------
          0  recursive calls
         42  db block gets
      58803  consistent gets
      58642  physical reads
          0  redo size
        369  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed
 


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

SELECT count(*) FROM alphatab WHERE 
to_char(createdate,'mm/dd/yyyy') = (SELECT to_char(sysdate-4,'mm/dd/yyyy') FROM 
DUAL) 

should be


select 
  from alphatab
 where createdate >= trunc(sysdate-4,'mm') 
   and createdate <  add_months(trunc(sysdate-4,'mm'),1)

 

5 stars   November 7, 2004 - 4pm Central time zone
Reviewer: A reader 
Tom,

Thank you. There is not much difference in the way the queries execute (except 1  sorts (memory) 
thing). I think the number of consistent gets and reads is large therefore the query is not running 
efficiently. 

The init.ora doesn't have optimizer mode set and I believe it is using RBO(Card is not shown). How 
should it be run by CBO? Should this be run by CBO? This table is analyzed twice daily using 
"analyze table estimate statistics". 

SQL> explain plan for 
select count(*)
  from alphatab
 where createdate >= trunc(sysdate,'mm') 
   and createdate <  add_months(trunc(sysdate,'mm'),1)  2    3    4    5  
  6  /
 
Explained.
 
SQL> @?/rdbms/admin/utlxpls
 
Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     1 |    7 |   8921 |       |       |
|  SORT AGGREGATE           |          |     1 |    7 |        |       |       |
|   TABLE ACCESS FULL       |alphatab  |     3K|   21K|   8921 |       |       |
--------------------------------------------------------------------------------
 
6 rows selected.
 
SQL> set timing on
SQL> set autotrace traceonly
SQL> 
select count(*)
  from alphatab
 where createdate >= trunc(sysdate,'mm') 
   and createdate <  add_months(trunc(sysdate,'mm'),1)SQL>   2    3    4  
  5  /
 
Elapsed: 00:01:10.32
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=8921 Card=1 Bytes=7)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'ALPHATAB' (Cost=8921 Card=320
          2 Bytes=22414)
 
 
 
 
 
Statistics
----------------------------------------------------------
          0  recursive calls
         38  db block gets
      58782  consistent gets
      58237  physical reads
          0  redo size
        369  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed
 
SQL>  


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

umm, you do not appear to have an index -- correct?

that fix for your query allows you to index that column, and the CBO (after analyzing) will use it 
if it is appropriate to do so. 

5 stars   November 7, 2004 - 6pm Central time zone
Reviewer: A reader 
Tom,

With reference to above post, does "analyze table estimate statistics" (all tables)  twice daily 
force an optimizer to be RBO? Would "analyze table compute statistics for table for all indexes for 
all indexed columns" make it cbo? Here, optimizer is not specified in init.ora 


Followup   November 8, 2004 - 9am Central time zone:

no?  if you have stats -- the premise is the CBO would be used.  estimate or compute -- stats are 
stats.


 

5 stars   November 23, 2004 - 11pm Central time zone
Reviewer: A reader 
Tom,

With reference to the above three postings, in an 8.1.7 OLTP table with 15000-20000 rows added 
daily as a continuous process, will a new index (on a date column) slow down inserts? 

 


Followup   November 24, 2004 - 7am Central time zone:

15/20k rows?  that is teeny tiny small. that is about 4 new rows every 3 seconds during an 8 hour 
day - not massive.

is it going to affect the insert?  yes, there will be the index maintenance.

is it going to be huge?  not just this one index, if you have lots of indexes that have no benefit 
(this one would have benefit maybe) they are 'overhead' and should be removed.  if the payback 
exceeds any perceived price, then this index would stay.


ops$tkyte@ORA9IR2> create table t as select * from all_objects where 1=0;
 
Table created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @trace
ops$tkyte@ORA9IR2> alter session set events '10046 trace name context forever, level 12';
 
Session altered.
 
ops$tkyte@ORA9IR2> begin
  2          for x in ( select * from all_objects where rownum < 20000 )
  3          loop
  4                  insert into t T1 values X;
  5          end loop;
  6  end;
  7  /
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> @traceoff
ops$tkyte@ORA9IR2> alter session set events '10046 trace name context off';
 
Session altered.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> truncate table t;
 
Table truncated.
 
ops$tkyte@ORA9IR2> create index t_idx on t(created);
 
Index created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> @trace
ops$tkyte@ORA9IR2> alter session set events '10046 trace name context forever, level 12';
 
Session altered.
 
ops$tkyte@ORA9IR2> begin
  2          for x in ( select * from all_objects where rownum < 20000 )
  3          loop
  4                  insert into t T2 values X;
  5          end loop;
  6  end;
  7  /
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> @traceoff
ops$tkyte@ORA9IR2> alter session set events '10046 trace name context off';
 
Session altered.


INSERT into t T1 values (:b1,:b2,:b3,:b4,:b5,:b6,:b7,:b8,:b9,:b10,:b11,:b12,:b13)
                                                                                                    
                  
                                                                                                    
                  
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          1          0           0
Execute  19999     22.89      22.60          0        302      23071       19999
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    20000     22.89      22.60          0        303      23071       19999
                                                                                                    
                  
INSERT into t T2 values (:b1,:b2,:b3,:b4,:b5,:b6,:b7,:b8,:b9,:b10,:b11,:b12,:b13)
                                                                                                    
                  
                                                                                                    
                  
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  19999     23.52      23.36          2        632      65112       19999
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    20000     23.52      23.36          2        632      65112       19999
 

5 stars   November 24, 2004 - 9am Central time zone
Reviewer: A reader 
Tom,

If the inserts prove to be time-taking after a new index is added, should the index be not created 
and instead go for a materialized view? In OLTP, is materialized view an/the alternative to avoid 
new indexes?  


Followup   November 24, 2004 - 10am Central time zone:

how would a MV help in this case?

everytime you run the query, it hits *different* data since it is based on sysdate. 

5 stars Question about execution plan   December 27, 2004 - 12pm Central time zone
Reviewer: POLINA 
Dear, Tom!
I have 2 tables
km_daily_transact - num of rows 23,209,784
unique_index
 prod_no,
 nwpr_date,
 cstmr_no
km_customers - num of row 37,964 
unique index - cstmr_no

the execution plan is:

SQL> select a.cstmr_no,b.cstmr_desc  from km_daily_transact a,km_customers b
  2  where
  3  a.cstmr_no=b.cstmr_no and 
  4   a.prod_no=1
  5  and a.nwpr_date between '01-nov-2004'  and  '26-dec-2004'  
  6  /

354069 rows selected.


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=408 Card=10589 Bytes
          =423560)

   1    0   HASH JOIN (Cost=408 Card=10589 Bytes=423560)
   2    1     PARTITION RANGE (ITERATOR)
   3    2       INDEX (RANGE SCAN) OF 'KM_DAILY_TRANSACT_PK' (UNIQUE)
          (Cost=206 Card=10589 Bytes=180013)

   4    1     TABLE ACCESS (FULL) OF 'KM_CUSTOMERS' (Cost=194 Card=379
          64 Bytes=873172)


Statistics
----------------------------------------------------------
          0  recursive calls
          7  db block gets
       5285  consistent gets
       1702  physical reads
          0  redo size
   10479182  bytes sent via SQL*Net to client
    1558173  bytes received via SQL*Net from client
      23606  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     354069  rows processed

Why there is a full scan of km_customers and not by index cstmr_no- i expected that for every fetch 
from km_daily_transact there will be join by specific cstmr_no to km_customers?
Thank you
Polina 
 


Followup   December 27, 2004 - 12pm Central time zone:

because full scans are not evil.

hint it to use an index and see how slow it truly can go :)

 

4 stars I just didn't understand well the order of the above explain plan.   December 27, 2004 - 3pm Central time zone
Reviewer: polina 
Tom,thank you for the answer,
Write me please , if i understood the plan properly, if not, correct me please:
First of all: km_customers table is loaded to memory because it has less records.
Second: Oracle scans km_customers in memory and for each 
cstmr_no joins to the big table km_daily_transact.
Have a nice day.
Polina  


Followup   December 27, 2004 - 4pm Central time zone:

given this plan:

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=408 Card=10589 Bytes
   1    0   HASH JOIN (Cost=408 Card=10589 Bytes=423560)
   2    1     PARTITION RANGE (ITERATOR)
   3    2       INDEX (RANGE SCAN) OF 'KM_DAILY_TRANSACT_PK' (UNIQUE)
   4    1     TABLE ACCESS (FULL) OF 'KM_CUSTOMERS' (Cost=194 Card=379


it would have used the index to find the rows in km_daily_transact, created a hash table based on 
the rows returned from that (it is assuming 10,589 rows from km_daily_transact and 37,900 some odd 
records from km_customers -- km_daily_transact is "smaller")

that hash table may or may not be in RAM.  it will then full scan km_customers and row by row probe 
the hash table to find the records to join to.


If you have effective Oracle by Design -- I go over the processing of stuff like this in some 
detail.
 

4 stars Continue question   December 28, 2004 - 2am Central time zone
Reviewer: Polina 
Hello,Tom,
thank you for the answer,

something in above i didn't understand:

  0      SELECT STATEMENT Optimizer=CHOOSE (Cost=408 Card=10589 Bytes
   1    0   HASH JOIN (Cost=408 Card=10589 Bytes=423560)
   2    1     PARTITION RANGE (ITERATOR)
   3    2       INDEX (RANGE SCAN) OF 'KM_DAILY_TRANSACT_PK' (UNIQUE)
   4    1     TABLE ACCESS (FULL) OF 'KM_CUSTOMERS' (Cost=194 Card=379
 What is the meaning of hash table?
When you say that hash table may or may not be in RAM
do you mean that the hash table is not in the cash and for each row of km_customers i must perform 
i/o readings to join to km_daily_transact?
Thank you.

Polina

 


Followup   December 28, 2004 - 10am Central time zone:

if you would like all of the nitty gritty details, like an explaination of the computer science 
term "hash table" and "may or may not be in ram" and how it gets paged in and out and so on -- 
please check out "Effective Oracle by Design" where I spent a good 40 pages on "how steps in plans 
are executed".

A hash table is 
http://www.google.com/search?q=define%3A+hash+table&sourceid=mozilla-search&start=0&start=0&ie=utf-8
&oe=utf-8&client=firefox-a&rls=org.mozilla:en-US:official
a hash table may or may not fit into ram, depends on how big it is, how much ram you got. 

4 stars Which should I believe?   January 6, 2005 - 1pm Central time zone
Reviewer: Daniel from São Paulo, Brazil
Dear Tom,

As far as I know, for Oracle 8i and below, there are two different sql parsers, one for the 
database and another for PL/SQL. If this is true, how can I tune a sql statement running inside a 
procedure or package?

Another thing: when I check explain with a statement using bind variables or constant values I get 
different plans. What's wrong?

SQL>    select
  2      distinct cas_Operation, cas_Case
  3      from orb_Users_Groups, orb_ProcessStages_Groups, orb_Cases
  4      where (ugr_Operation = 'OPE1' and ugr_User = 'SYSTEM')
  5      and   (psg_Operation = 'OPE1' and psg_Group = ugr_Group)
  6      and   (cas_Operation = 'OPE1'
  7        and cas_Process = psg_Process 
  8        and cas_ProcessStage = psg_ProcessStage
  9        and cas_Status = 3);

não há linhas selecionadas


Plano de Execução
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=36 Card=4 Bytes=820)
   1    0   SORT (UNIQUE) (Cost=36 Card=4 Bytes=820)
   2    1     HASH JOIN (Cost=34 Card=4 Bytes=820)
   3    2       NESTED LOOPS (Cost=2 Card=91 Bytes=13286)
   4    3         INDEX (FAST FULL SCAN) OF 'ORB_PROCESSSTAGES_GROUPS_
          PK' (UNIQUE) (Cost=2 Card=713 Bytes=52762)

   5    3         INDEX (UNIQUE SCAN) OF 'ORB_USERS_GROUPS_PK' (UNIQUE
          )

   6    2       TABLE ACCESS (FULL) OF 'ORB_CASES' (Cost=29 Card=434 B
          ytes=25606)

WRONG! Very hi cost!

SQL> var i_Operation char(24)
SQL> var i_User char(24)
SQL>    select
  2      distinct cas_Operation, cas_Case
  3      from orb_Users_Groups, orb_ProcessStages_Groups, orb_Cases
  4      where (ugr_Operation = :i_Operation and ugr_User = :i_User)
  5      and   (psg_Operation = :i_Operation and psg_Group = ugr_Group)
  6      and   (cas_Operation = :i_Operation 
  7        and cas_Process = psg_Process 
  8        and cas_ProcessStage = psg_ProcessStage
  9        and cas_Status = 3);

não há linhas selecionadas


Plano de Execução
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=9 Card=1 Bytes=205)
   1    0   SORT (UNIQUE) (Cost=9 Card=1 Bytes=205)
   2    1     NESTED LOOPS (Cost=7 Card=1 Bytes=205)
   3    2       NESTED LOOPS (Cost=2 Card=1 Bytes=146)
   4    3         INDEX (RANGE SCAN) OF 'ORB_PROCESSSTAGES_GROUPS_PK'
          (UNIQUE) (Cost=2 Card=8 Bytes=592)

   5    3         INDEX (UNIQUE SCAN) OF 'ORB_USERS_GROUPS_PK' (UNIQUE
          )

   6    2       TABLE ACCESS (BY INDEX ROWID) OF 'ORB_CASES' (Cost=5 C
          ard=5 Bytes=295)

   7    6         INDEX (RANGE SCAN) OF 'ORB_CASES_I06' (NON-UNIQUE) (
          Cost=1 Card=5)

FINE!

Thanks,
Daniel
 


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

there is but one optimizer.


it is in the database.


the sql parsing issue is that plsql has to take code like this:


declare
   l_data number;
begin
   ...
   for x in (select a, b, c from t where x = l_data )
   loop



and find the SQL "select a, b, c from t where x = l_data", tokenize and parse it to it can 
understand:

a) this is a query that returns 3 columns
b) whose names are a, b, c
c) with datatypes so and so
d) there is a reference to X and L_DATA as well
e) the table T doesn't have a column L_DATA but it does have a column X
f) l_data is a plsql variable -- therefore, must be a bind variable.
g) so store in the compiled code this query:

    "select a, b, c from t where x = :bv0"

   and when we run it, bind L_data to :bv0

that is what the PLSQL parser does.  The problem was the plsql parser didn't always keep up with 
the latest sql so

  for x in ( select * from (select * from dual order by 1 ) )

would "fail" in 8i plsql -- but that same query would suceed in sqlplus.  

So, in 9i, they use a common parser to tokenize -- steps a..g would be done using the same parser 
as the data used to parse the output of G.




Now, you are asking the question "what about binds" -- the thing is

where x = 5;

vs

where x = :x;


Ok, the optimizer knows "where x=5 returns like 1 row, we have histograms, 1 row -- we know that"

with "where x = :x" the optimizer knows -- well, it knows that :x could be anything -- it does not 
"know" 5, it knows "any value".  It'll take a look at the number of distinct values for x (say 100 
of them) and the number of rows (say 500 rows) and say "ah hah -- about 5 rows on average"


ops$tkyte@ORA9IR2> create table t as select mod(rownum,100) x from all_objects where rownum <= 500;
Table created.
 
ops$tkyte@ORA9IR2> update t set x = 6 where x = 5;
5 rows updated.
 
ops$tkyte@ORA9IR2> update t set x = 5 where x = 6 and rownum = 1;
1 row updated.
 
ops$tkyte@ORA9IR2> exec dbms_stats.gather_table_stats( user, 'T', method_opt=> 'for all columns 
size 254' );
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select * from t where x = 5;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=2)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=1 Bytes=2)
 
 
 
ops$tkyte@ORA9IR2> variable x number
ops$tkyte@ORA9IR2> select * from t where x = :x;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=5 Bytes=10)
   1    0   TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=5 Bytes=10)
 
 
 
ops$tkyte@ORA9IR2>


Just like that.  So bind variables will many times end up with a different plan than constants.



and the optimzer above -- it was not "wrong -- very high cost", it said "you know,given the 
specific inputs you gave me, this is the best we can do"  later, when you took the specifics away 
-- it said "you know, generically, on average this is what you can expect"


 

5 stars I believe   January 7, 2005 - 7am Central time zone
Reviewer: Daniel from São Paulo, Brazil
Great. Thank you! 


3 stars Just a clarification   January 19, 2005 - 3pm Central time zone
Reviewer: A reader from NJ, USA
Tom,

In your explanation above in interpreting the EXPLAIN PLAN using a pseudocode, can we not use the 
following pseudocode for step 4 (while accessing the EMP table)

for salgrade in (select * from salgrade)
loop
   for emp in (select * from emp where sal between salgrade.losal and salgrade.hisal)
   loop
       ...
   end loop
end loop

instead of the if statement inside the second loop (the reason being that the Filter condition in 
step 4 is ideally executed while selecting from the table). Is that correct?
If not pls. advise.
Also it would be helpful if you can provide a pseudocode for MERGE JOIN and HASH JOIN operations as 
those operations are commonly occuring in EXPLAIN PLAN and most books are explaining them in words 
rather than using some sort of pseudocodes. I am from a technical background and providing such 
pseudocodes is much helpful than verbal explanation

Thanks 


Followup   January 20, 2005 - 10am Central time zone:

it is *psuedo code*

a procedural interpretation of something non-procedural.

it is fine as I have it, for purposes of ILLUSTRATION of what is logically taking place.

I do that last paragraph in my book "Effective Oracle by Design" for hash joins, sort merge, 
anti-joins, outer joins, etc. 

5 stars I have a question on range and full scan   January 26, 2005 - 4am Central time zone
Reviewer: Raja from INDIA
for the same query, I saw the following query plans for production and TEST:
In production:
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=3 Card=3 Bytes=7
      14)

   1    0   NESTED LOOPS (Cost=3 Card=3 Bytes=714)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'SERVICE_T' (Cost=2 Car
      d=1 Bytes=78)

   3    2    INDEX (UNIQUE SCAN) OF 'I_SERVICE__ID' (UNIQUE) (Cost=
      2 Card=1)

   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'AU_SERVICE_T' (Cost=2
      Card=3 Bytes=480)

   5    4    INDEX (RANGE SCAN) OF 'I_AU_SERVICE_PARENT__ID' (NON-U
      NIQUE) (Cost=2 Card=3)

=========================================
In TEST:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=1990 Card=3 Byte
      s=948)

   1    0   NESTED LOOPS (Cost=1990 Card=3 Bytes=948)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'SERVICE_T' (Cost=2 Car
      d=1 Bytes=78)

   3    2    INDEX (UNIQUE SCAN) OF 'I_SERVICE__ID' (UNIQUE) (Cost=
      2 Card=1)

   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'AU_SERVICE_T' (Cost=19
      89 Card=3 Bytes=480)

   5    4    INDEX (FULL SCAN) OF 'AU_SERVICE_PARENT' (NON-UNIQUE)
      (Cost=19878 Card=3)


==============

In production it is range scan and in TEST it is FULL scan.

Everythingelse is same in both the environments. along with optimizer mode and set of 
indexes...etc.,

Could you please comment on this...

Thanks a lot

from
Raja
 


Followup   January 26, 2005 - 8am Central time zone:

<quote>
Everythingelse is same in both the environments
</quote>

laughing out loud.....  something is different.

that is about the extent of any comment one could make simply given two plans.


Now, you can use a 10053 optimizer trace to see what optimizer parameters might be different 
(search this site for that number or see chapter 6 of "Effective Oracle by Design" for details).

Look at the statistics, look at the indexes (make sure they all in fact exist), look at the 
datatypes (make sure they are the same) -- you'll find something is *different* (heck, unless you 
backed up production and restored that very database to test -- I can promise you they are 
different.  the bits are stored in different places affecting things like cluster factors and such) 

4 stars Tuning SQL query   February 14, 2005 - 12pm Central time zone
Reviewer: Arya from Reston, VA
Hi Tom,

My java developer has written following query which is erroring out with "unable to extend temp 
space". I'm not able to figure out what's wrong in it. Could you please throw me some light on 
where's the problem.

Original Query  If I execute this it is erroring out with "Unable to extend temp Space"

  1  select * from
  2              (select sd.LEASE_NBR,
  3                 sd.PROJECT_NBR,
  4                 b.RGN_CD ,
  5                 sd.DOC_TYPE,
  6                 sd.VERSION_NBR||'-'|| sd.SUB_VERSION_NBR DOC_NO,
  7                 CASE WHEN sd.PROCESSED_DT is null THEN 'No Processed' ELSE 'Processed' END 
Status,
  8                 sd.SCANNING_REQUEST_DT Request_Date,
  9                 TRUNC(sd.PROCESSED_DT, 'DDD')-TRUNC(sd.SCANNING_REQUEST_DT ,'DDD') 
Process_time,
 10                 sd.NUM_PAGES_ACTUAL||'/'||sd.NUM_PAGES_EXPECTED Actual_Expect,
 11                 elease_util.Initcap2(a.LONG_AGENCY_NAME) Agency_Name,
 12                 elease_util.Initcap2(sd.DOC_CATEGORY) doc_category,
 13                 sd.DOC_ID,
 14                 sd.PROCESSED_DT,
 15                 sd.UPDATE_DT,
 16                 elease_util.Initcap2(sd.DOC_NAME) doc_name,
 17                 sd.num_pages_expected,
 18                 sd.scanning_comments,
 19                 sd.VERSION_NBR,
 20                 sd.SUB_VERSION_NBR,
 21                 sd.agency_cd,
 22                 sd.oa_nbr
 23              from Scanned_docs sd, Buildings b, leases l, agencies a
 24              where l.BUILDING_ID (+)= b.BUILDING_ID and
 25                l.LEASE_NBR = sd.LEASE_NBR and
 26               a.AGENCY_CD (+)= sd.AGENCY_CD )
 27  where LEASE_NBR LIKE '%'
 28  and  UPPER(DOC_CATEGORY) LIKE '%'
 29  and RGN_CD LIKE '%'
 30  and UPDATE_DT >= TO_DATE('2/7/05 ','MM/DD/YYYY')
 31* and NVL(TO_CHAR(processed_dt),'%') like '%'

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=235 Card=1 Bytes=147)
   1    0   NESTED LOOPS (OUTER) (Cost=235 Card=1 Bytes=147)
   2    1     HASH JOIN (Cost=234 Card=1 Bytes=116)
   3    2       TABLE ACCESS (FULL) OF 'SCANNED_DOCS' (Cost=9 Card=5 Bytes=450)
   4    2       FILTER
   5    4         HASH JOIN (OUTER)
   6    5           TABLE ACCESS (FULL) OF 'BUILDINGS' (Cost=150 Card=1456 Bytes=14560)
   7    5           VIEW OF 'LEASES' (Cost=73 Card=13825 Bytes=221200)
   8    7             TABLE ACCESS (FULL) OF 'LEASES' (Cost=73 Card=13825 Bytes=221200)
   9    1     TABLE ACCESS (BY INDEX ROWID) OF 'AGENCIES' (Cost=1 Card=1 Bytes=31)
  10    9       INDEX (UNIQUE SCAN) OF 'AGENCY_PK' (UNIQUE)

Commented last line and executed and it is running in just seconds

  1  select * from
  2              (select sd.LEASE_NBR,
  3                 sd.PROJECT_NBR,
  4                 b.RGN_CD ,
  5                 sd.DOC_TYPE,
  6                 sd.VERSION_NBR||'-'|| sd.SUB_VERSION_NBR DOC_NO,
  7                 CASE WHEN sd.PROCESSED_DT is null THEN 'No Processed' ELSE 'Processed' END 
Status,
  8                 sd.SCANNING_REQUEST_DT Request_Date,
  9                 TRUNC(sd.PROCESSED_DT, 'DDD')-TRUNC(sd.SCANNING_REQUEST_DT ,'DDD') 
Process_time,
 10                 sd.NUM_PAGES_ACTUAL||'/'||sd.NUM_PAGES_EXPECTED Actual_Expect,
 11                 elease_util.Initcap2(a.LONG_AGENCY_NAME) Agency_Name,
 12                 elease_util.Initcap2(sd.DOC_CATEGORY) doc_category,
 13                 sd.DOC_ID,
 14                 sd.PROCESSED_DT,
 15                 sd.UPDATE_DT,
 16                 elease_util.Initcap2(sd.DOC_NAME) doc_name,
 17                 sd.num_pages_expected,
 18                 sd.scanning_comments,
 19                 sd.VERSION_NBR,
 20                 sd.SUB_VERSION_NBR,
 21                 sd.agency_cd,
 22                 sd.oa_nbr
 23              from Scanned_docs sd, Buildings b, leases l, agencies a
 24              where l.BUILDING_ID (+)= b.BUILDING_ID and
 25                l.LEASE_NBR = sd.LEASE_NBR and
 26               a.AGENCY_CD (+)= sd.AGENCY_CD )
 27  where LEASE_NBR LIKE '%'
 28  and  UPPER(DOC_CATEGORY) LIKE '%'
 29  and RGN_CD LIKE '%'
 30  and UPDATE_DT >= TO_DATE('2/7/05 ','MM/DD/YYYY')
 31* --and NVL(TO_CHAR(processed_dt),'%') like '%'
SQL> /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=240 Card=21 Bytes=3087)
   1    0   HASH JOIN (OUTER) (Cost=240 Card=21 Bytes=3087)
   2    1     HASH JOIN (Cost=234 Card=21 Bytes=2436)
   3    2       TABLE ACCESS (FULL) OF 'SCANNED_DOCS' (Cost=9 Card=97 Bytes=8730)
   4    2       FILTER
   5    4         HASH JOIN (OUTER)
   6    5           TABLE ACCESS (FULL) OF 'BUILDINGS' (Cost=150 Card=1456 Bytes=14560)
   7    5           VIEW OF 'LEASES' (Cost=73 Card=13825 Bytes=221200)
   8    7             TABLE ACCESS (FULL) OF 'LEASES' (Cost=73 Card=13825 Bytes=221200)
   9    1     TABLE ACCESS (FULL) OF 'AGENCIES' (Cost=5 Card=983 Bytes=30473)


Commented out last but one line and being executed with in seconds

  1  select * from
  2              (select sd.LEASE_NBR,
  3                 sd.PROJECT_NBR,
  4                 b.RGN_CD ,
  5                 sd.DOC_TYPE,
  6                 sd.VERSION_NBR||'-'|| sd.SUB_VERSION_NBR DOC_NO,
  7                 CASE WHEN sd.PROCESSED_DT is null THEN 'No Processed' ELSE 'Processed' END 
Status,
  8                 sd.SCANNING_REQUEST_DT Request_Date,
  9                 TRUNC(sd.PROCESSED_DT, 'DDD')-TRUNC(sd.SCANNING_REQUEST_DT ,'DDD') 
Process_time,
 10                 sd.NUM_PAGES_ACTUAL||'/'||sd.NUM_PAGES_EXPECTED Actual_Expect,
 11                 elease_util.Initcap2(a.LONG_AGENCY_NAME) Agency_Name,
 12                 elease_util.Initcap2(sd.DOC_CATEGORY) doc_category,
 13                 sd.DOC_ID,
 14                 sd.PROCESSED_DT,
 15                 sd.UPDATE_DT,
 16                 elease_util.Initcap2(sd.DOC_NAME) doc_name,
 17                 sd.num_pages_expected,
 18                 sd.scanning_comments,
 19                 sd.VERSION_NBR,
 20                 sd.SUB_VERSION_NBR,
 21                 sd.agency_cd,
 22                 sd.oa_nbr
 23              from Scanned_docs sd, Buildings b, leases l, agencies a
 24              where l.BUILDING_ID (+)= b.BUILDING_ID and
 25                l.LEASE_NBR = sd.LEASE_NBR and
 26               a.AGENCY_CD (+)= sd.AGENCY_CD )
 27  where LEASE_NBR LIKE '%'
 28  and  UPPER(DOC_CATEGORY) LIKE '%'
 29  and RGN_CD LIKE '%'
 30  --and UPDATE_DT >= TO_DATE('2/7/05 ','MM/DD/YYYY')
 31* and NVL(TO_CHAR(processed_dt),'%') like '%'
SQL> /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=235 Card=1 Bytes=147)
   1    0   NESTED LOOPS (OUTER) (Cost=235 Card=1 Bytes=147)
   2    1     HASH JOIN (Cost=234 Card=1 Bytes=116)
   3    2       TABLE ACCESS (FULL) OF 'SCANNED_DOCS' (Cost=9 Card=5 Bytes=450)
   4    2       FILTER
   5    4         HASH JOIN (OUTER)
   6    5           TABLE ACCESS (FULL) OF 'BUILDINGS' (Cost=150 Card=1456 Bytes=14560)
   7    5           VIEW OF 'LEASES' (Cost=73 Card=13825 Bytes=221200)
   8    7             TABLE ACCESS (FULL) OF 'LEASES' (Cost=73 Card=13825 Bytes=221200)
   9    1     TABLE ACCESS (BY INDEX ROWID) OF 'AGENCIES' (Cost=1 Card=1 Bytes=31)
  10    9       INDEX (UNIQUE SCAN) OF 'AGENCY_PK' (UNIQUE)

I'm not able to understand why this behaviour. I mean if I comment last line or last but one line 
it is running with out any problems and If I run as is erroring out.

I appreciate all your help.

Thanks
arya 


Followup   February 14, 2005 - 2pm Central time zone:

perhaps the error is---

you actually do have insufficient temporary tablespace configured for your system to run the 
queries you want to?  that is an option here.


are the card=values in the autotrace anywhere near what you believe "reality would have to say on 
the topic"? 

4 stars Tuning SQL query   February 14, 2005 - 1pm Central time zone
Reviewer: Arya from Reston, VA
Hi Tom,

In the following query If I comment out any one condition from where clause query runs fine is 
there any limit of having where clause like '%'?

1  select * from
  2              (select sd.LEASE_NBR,
  3                 sd.PROJECT_NBR,
  4                 b.RGN_CD ,
  5                 sd.DOC_TYPE,
  6                 sd.VERSION_NBR||'-'|| sd.SUB_VERSION_NBR DOC_NO,
  7                 CASE WHEN sd.PROCESSED_DT is null THEN 'No Processed' ELSE 'Processed' END 
Status,
  8                 sd.SCANNING_REQUEST_DT Request_Date,
  9                 TRUNC(sd.PROCESSED_DT, 'DDD')-TRUNC(sd.SCANNING_REQUEST_DT ,'DDD') 
Process_time,
 10                 sd.NUM_PAGES_ACTUAL||'/'||sd.NUM_PAGES_EXPECTED Actual_Expect,
 11                 elease_util.Initcap2(a.LONG_AGENCY_NAME) Agency_Name,
 12                 elease_util.Initcap2(sd.DOC_CATEGORY) doc_category,
 13                 sd.DOC_ID,
 14                 sd.PROCESSED_DT,
 15                 sd.UPDATE_DT,
 16                 elease_util.Initcap2(sd.DOC_NAME) doc_name,
 17                 sd.num_pages_expected,
 18                 sd.scanning_comments,
 19                 sd.VERSION_NBR,
 20                 sd.SUB_VERSION_NBR,
 21                 sd.agency_cd,
 22                 sd.oa_nbr
 23              from Scanned_docs sd, Buildings b, leases l, agencies a
 24              where l.BUILDING_ID (+)= b.BUILDING_ID and
 25                l.LEASE_NBR = sd.LEASE_NBR and
 26               a.AGENCY_CD (+)= sd.AGENCY_CD )
 27  where LEASE_NBR LIKE '%'
 28  and  UPPER(DOC_CATEGORY) LIKE '%'
 29  and RGN_CD LIKE '%'
 30  and UPDATE_DT >= TO_DATE('2/7/05 ','MM/DD/YYYY')
 31* and NVL(TO_CHAR(processed_dt),'%') like '%'

If I comment upper(doc_category) like '%' with all other conditions it works fine similarly 
commenting rgn_cd like '%' with all other conditions uncommented again it works fine? So I'm really 
puzzled to understand what's going on?


I request your help in this regard.

Thanks
arya 


5 stars Redundant predicates in the where clause   February 14, 2005 - 3pm Central time zone
Reviewer: Logan Palanisamy from Santa Clara, CA, USA
Arya,

What is the use of predicates like:

LEASE_NBR LIKE '%'
UPPER(DOC_CATEGORY) LIKE '%'
RGN_CD LIKE '%'

in the where clause? Since they select all the rows, you might as well not have them.

like 

select * from emp where emp_name like '%'

is the same as

select * from emp;

 


Followup   February 14, 2005 - 6pm Central time zone:

well, technically, the same as:

select * from emp where emp_name is not null;


 

3 stars Tuning SQL   February 14, 2005 - 6pm Central time zone
Reviewer: Arya from Reston, VA
Palanisami,

This query runs on java application for a search screen i.e. they have coded with all like '%'. I 
don't know whether I have answered your question appropriately.

Thanks
Arya 


Followup   February 14, 2005 - 6pm Central time zone:

(ask them "hey, if they give '%', how about if you leave it off the query"

ask them also -- you guys, you ARE using bind variables right?)

 

4 stars Two digit years   February 14, 2005 - 7pm Central time zone
Reviewer: Gary from Sydney, Aus
I love the

"and UPDATE_DT >= TO_DATE('2/7/05 ','MM/DD/YYYY')"

It's a strange requirement to exclude rows updated over 2000 years ago. 


Followup   February 14, 2005 - 8pm Central time zone:

thank you for point that out, "houston we have a problem" pops into my head suddenly.

laughing out loud -- that probably would be the entire table, unless of course, that was the format 
and string used upon insert -- in which case, they have a looming "year 100 problem" in about 95 
years as they cycle back around. 

4 stars Size of string changes the explain plan   February 15, 2005 - 8am Central time zone
Reviewer: Bipul from London, UK
Hi Tom.

I have a query for which the explain plan changes based on the length of string.

select topn_ord , arx_id , arx_bibl , arx_status , arx_type , man_date_pub as pub_date  
from topn , arx , man 
where  topn_item_id=arx_id 
and man_id=arx_man_id 
and topn_type='TOP_ARTICLE_ACCESSES_ALL_TIME_JOU_10022'
and topn_ord<=20
order by topn_ord;

The explain plan is

Operation    Object Name    Rows    Bytes    Cost    Object Node    In/Out    PStart    PStop

SELECT STATEMENT Optimizer Mode=CHOOSE        14 K         8591                                     
  
  SORT ORDER BY        14 K    11 M    8591                                       
    HASH JOIN        14 K    11 M    6878                                       
      TABLE ACCESS FULL    WEBUSER.BMC_MANUSCRIPT    68 K    1 M    1163                            
           
      HASH JOIN        14 K    10 M    5511                                       
        VIEW    WEBUSER.BMC_TOPN_SUMMARY    14 K    356 K    1258                                   
    
          SORT ORDER BY        14 K    4 M    1258                                       
            VIEW        14 K    4 M    621                                       
              WINDOW BUFFER        14 K    932 K    621                                       
                TABLE ACCESS BY INDEX ROWID    WEBUSER.BMC_TOPN_SUMMARY_ORIGINAL    14 K    932 K   
 621                                       
                  INDEX RANGE SCAN    WEBUSER.BMC_TOPN_TYPE_IDX    14 K         165                 
                      
        TABLE ACCESS FULL    WEBUSER.BMC_ARTICLE_XML    43 K    32 M    3971                        
               


It does FTS on two large tables.

But the plan for same sql with topn_type='TOP_ARTICLE_ACCESSES_ALL_TIME_J' or anything upto 31 
character is much improved.


Operation    Object Name    Rows    Bytes    Cost    Object Node    In/Out    PStart    PStop

SELECT STATEMENT Optimizer Mode=CHOOSE        207           433                                     
  
  NESTED LOOPS        207      164 K    433                                       
    NESTED LOOPS        204      159 K    229                                       
      VIEW    WEBUSER.BMC_TOPN_SUMMARY    204      5 K    25                                       
        SORT ORDER BY        204      59 K    25                                       
          VIEW        204      59 K    12                                       
            WINDOW BUFFER        204      13 K    12                                       
              TABLE ACCESS BY INDEX ROWID    WEBUSER.BMC_TOPN_SUMMARY_ORIGINAL    204      13 K    
12                                       
                INDEX RANGE SCAN    WEBUSER.BMC_TOPN_TYPE_IDX    204           5                    
                   
      TABLE ACCESS BY INDEX ROWID    WEBUSER.BMC_ARTICLE_XML    1      773      1                   
                    
        INDEX UNIQUE SCAN    WEBUSER.BMC_ARX_PK    1                                                
 
    TABLE ACCESS BY INDEX ROWID    WEBUSER.BMC_MANUSCRIPT    1      17      1                       
                
      INDEX UNIQUE SCAN    WEBUSER.BMC_MAN_PK    1                                                 

The data in table topn [its a view on a table] is fairly well distributed and we have histograms 
calculated as well.

Can't figure out why the plan changes? Any suggestion would be very useful.

Thanks
bipul
 


Followup   February 15, 2005 - 3pm Central time zone:

could perhaps be that "where column = longstring" returns a totally different set/amount of rows 
could it not.


histograms are computed only on the first 32 bytes of a string as well.  so, it probably "knows" 
more about the 31 byte string (which is sort of irrelevant, it gets no data does it.....) and 
chooses a totally different plan. 

4 stars Data set returned is similar   February 16, 2005 - 9am Central time zone
Reviewer: bipul from London, UK
Hi Tom.

Thanks for your feedback. The two query with smaller string and longer string returns similar sets.
For  example,

select topn_ord , arx_id , arx_bibl , arx_status , arx_type , man_date_pub as 
pub_date  
from topn , arx , man 
where  topn_item_id=arx_id 
and man_id=arx_man_id 
and topn_type='TOP_ARTICLE_ACCESSES_ALL_TIME'
order by topn_ord;

returns 100 rows 

and 

select topn_ord , arx_id , arx_bibl , arx_status , arx_type , man_date_pub as 
pub_date  
from topn , arx , man 
where  topn_item_id=arx_id 
and man_id=arx_man_id 
and topn_type='TOP_ARTICLE_ACCESSES_ALL_TIME_JOU_10022'
order by topn_ord;

returns 48 rows. Not vastly different.

I think your point about "histograms are computed only on the first 32 bytes of a string as well" 
could be the  reason why it chooses different plan when the length(topn_type) > 31. 

Thanks again!
bipul

 


5 stars get explain plan from system table   February 16, 2005 - 5pm Central time zone
Reviewer: Sean from NJ, USA
I have a script to get all heavy cpu usage sessions by quering v$session, v$process and process id 
I got from prstat command on Solaris.
I am then able to pull the sql statement by joining $session and v$sqltext_with_newlines by address 
column.

Does Oracle store explain plan somewhere in system table other than plan_table, so I can get them 
by my script?

As you know, I don't want to manually login to each schema and analyze sql one by one.

Thanks so much for your help.

 


Followup   February 16, 2005 - 5pm Central time zone:

v$sql_plan is available in software written this century, but not last century (meaning, in 9i and 
above -- yes) 

4 stars How many tables in a query is optimal   March 7, 2005 - 10am Central time zone
Reviewer: BK 
Hi Tom.

Is there any maximum limit on the number of  tables used in a query? Will the optimizer be able to 
generate a good plan if the number of tables in query is more than 6 or 7.

Thanks
BK 


Followup   March 7, 2005 - 3pm Central time zone:

there are no maximums I've hit -- I've gone to 500+

It takes a while to parse of course!

More than 6 or 7 -- sure, hundreds -- bad idea.  Somewhere between 1 and 100 maybe. 

4 stars Tunning SQL query   March 18, 2005 - 10am Central time zone
Reviewer: Thomas Varekat from Mumbai, India
The below query with group by clause takes more than 20 seconds but without group by takes around 2 
seconds. 
Need help to fine tune this query.

Please find the query and explain plan below:

SELECT    PSL.PROCESS_STATUS psl_ps
 ,        count(UPSA.UNIT_ID) upsa_ui
FROM
      PROCESS_SUB_PROCESS_DEFN PSPD
      ,BUSINESS_PROCESS_LIST BPL
      ,PROCESS_STATUS_LIST PSL_PARENT
      ,PROCESS_STATUS_LIST PSL
      ,PROCESS_STATUS_DEFN PSD  
      ,STR_BUS_REF SBR
      ,UNIT U  
      ,STRUCTURE_MF_DATA SMD_ALL      
      ,BUS_REF_LIST BRL
      , UNIT_PROC_STATUS_ASSIGN UPSA
WHERE 1=1
          AND BPL.PROCESS_ID = PSPD.MAJOR_PROCESS_ID
          AND PSD.PROCESS_SUB_PROCESS_ID = PSPD.PROCESS_SUB_PROCESS_ID                  
          AND BPL.PROCESS_NAME = 'RAPID TAG RENEWAL'
          AND PSL.PROCESS_STATUS_ID = PSL_PARENT.PROCESS_STATUS_ID -- no parentrelationship         
 
          AND PSD.PROCESS_STATUS_ID = PSL.PROCESS_STATUS_ID                  
          AND UPSA.IS_CURRENT_SW = 'Y'                    
          AND UPSA.PROCESS_SUB_PROCESS_ID = PSPD.PROCESS_SUB_PROCESS_ID        
          AND UPSA.PROCESS_STATUS_ID  = PSL.PROCESS_STATUS_ID
          AND UPSA.PROCESS_STATUS_ID  = PSD.PROCESS_STATUS_ID                    
          AND UPSA.PROCESS_SUB_PROCESS_ID = PSD.PROCESS_SUB_PROCESS_ID        
          AND SMD_ALL.STRUCTURE_ID      = SBR.STRUCTURE_ID 
          AND BRL.BUS_REF_ID  = SBR.BUS_REF_ID        
          AND UPSA.UNIT_ID = U.UNIT_ID                    
          AND BRL.REFERENCE_ID =U.REFERENCE_ID
          AND PSL_PARENT.PROCESS_STATUS in  ('CURRENT' )                    
          AND BPL.IS_MAJOR_PROCESS_SW = 'Y'                    
          AND (PSPD.EFFECTIVE_START_DATE IS NULL OR PSPD.EFFECTIVE_START_DATE <SYSDATE)
          AND (PSPD.EFFECTIVE_END_DATE  IS NULL OR PSPD.EFFECTIVE_END_DATE  > SYSDATE)
          AND UPSA.EFFECTIVE_END_DATE IS NULL
          AND EXISTS
      ( SELECT 
               'Y'
        FROM  STRUCTURE_MF_DATA SMD
             ,STRUCTURE_CONTACT_LIST SCL
           --  ,INDIVIDUAL I
             ,COUNTRY_GE_CORP CGC
           --  ,CONTACT_TYPE_LIST CTL
        WHERE 
       SMD_ALL.MF_ACCESS_CODE_1  = '005029'
       AND SMD.MF_ACCESS_CODE_1  = '005029'
       AND SMD.GE_CORP_CODE = CGC.CORP_CODE
       AND CGC.COUNTRY_CODE = 'US'
       AND SCL.INDIVIDUAL_ID = 79        
       AND SCL.CONTACT_TYPE_REF = 'TG' --CTL.CONTACT_TYPE_REF
       AND SMD.STRUCTURE_ID = SCL.STRUCTURE_ID       
        AND SMD_ALL.GE_CORP_CODE = SMD.GE_CORP_CODE
        AND SMD_ALL.MF_ACCESS_CODE_1 = SMD.MF_ACCESS_CODE_1
        AND (SMD.MF_ACCESS_CODE_2 = '      ' OR SMD_ALL.MF_ACCESS_CODE_2 = SMD.MF_ACCESS_CODE_2)
        AND (SMD.MF_ACCESS_CODE_3 = '      ' OR SMD_ALL.MF_ACCESS_CODE_3 = SMD.MF_ACCESS_CODE_3)
        AND (SMD.MF_ACCESS_CODE_4 = '      ' OR SMD_ALL.MF_ACCESS_CODE_4 = SMD.MF_ACCESS_CODE_4)
        AND (SMD.MF_ACCESS_CODE_5 = '      ' OR SMD_ALL.MF_ACCESS_CODE_5 = SMD.MF_ACCESS_CODE_5)
        AND (SMD.MF_ACCESS_CODE_6 = '      ' OR SMD_ALL.MF_ACCESS_CODE_6 = SMD.MF_ACCESS_CODE_6)
        AND NOT EXISTS
             (
               SELECT 
                      'CHILD STRUCTURE'
               FROM 
               STRUCTURE_MF_DATA SMD1
               ,STRUCTURE_CONTACT_LIST SCL1
               WHERE 
               SMD1.STRUCTURE_ID = SCL1.STRUCTURE_ID               
               AND  SCL1.INDIVIDUAL_ID = SCL.INDIVIDUAL_ID               
               AND SCL1.CONTACT_TYPE_REF =SCL.CONTACT_TYPE_REF               
               AND SMD1.LEVEL_NUMBER < SMD.LEVEL_NUMBER
               AND SUBSTR((NVL(SMD.MF_ACCESS_CODE_1,'      ') || NVL(SMD.MF_ACCESS_CODE_2,'      ') 
||NVL(SMD.MF_ACCESS_CODE_3,'      ') ||NVL(SMD.MF_ACCESS_CODE_4,'      ') 
||NVL(SMD.MF_ACCESS_CODE_5,'      ') ||NVL(SMD.MF_ACCESS_CODE_6,'     ')),1,(SMD1.LEVEL_NUMBER)*6)
                 = SUBSTR((NVL(SMD1.MF_ACCESS_CODE_1,'      ') || NVL(SMD1.MF_ACCESS_CODE_2,'      
') ||NVL(SMD1.MF_ACCESS_CODE_3,'      ') ||NVL(SMD1.MF_ACCESS_CODE_4,'      ') 
||NVL(SMD1.MF_ACCESS_CODE_5,'      ') ||NVL(SMD1.MF_ACCESS_CODE_6,'      
')),1,(SMD1.LEVEL_NUMBER)*6)
               AND SCL1.STR_CONTACT_EFF_DATE < SYSDATE
               AND (SCL1.STR_CONTACT_END_DATE IS NULL OR SCL1.STR_CONTACT_END_DATE > SYSDATE)
             )
       AND SCL.STR_CONTACT_EFF_DATE < SYSDATE
       AND (SCL.STR_CONTACT_END_DATE IS NULL OR SCL.STR_CONTACT_END_DATE > SYSDATE)
       --AND CTL.CONTACT_TYPE_DESC = 'RAPIDTAG RENEWAL REPORT CONTACT'
        )
        group by PSL.PROCESS_STATUS


Explain plan with group by
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=296 Card=1 Bytes=181
          )

   1    0   SORT (GROUP BY) (Cost=296 Card=1 Bytes=181)
   2    1     FILTER
   3    2       NESTED LOOPS (Cost=285 Card=1 Bytes=181)
   4    3         NESTED LOOPS (Cost=283 Card=1 Bytes=138)
   5    4           NESTED LOOPS (Cost=274 Card=3 Bytes=384)
   6    5             NESTED LOOPS (Cost=262 Card=3 Bytes=354)
   7    6               HASH JOIN (Cost=256 Card=3 Bytes=324)
   8    7                 TABLE ACCESS (FULL) OF 'T4283_PROCESS_SUB_PR
          OCESS_DEFN' (Cost=1 Card=19 Bytes=456)

   9    7                 NESTED LOOPS (Cost=254 Card=427 Bytes=35868)
  10    9                   NESTED LOOPS (Cost=4 Card=1 Bytes=66)
  11   10                     NESTED LOOPS (Cost=3 Card=1 Bytes=60)
  12   11                       NESTED LOOPS (Cost=2 Card=1 Bytes=38)
  13   12                         TABLE ACCESS (BY INDEX ROWID) OF 'T4
          867_PROCESS_STATUS_LIST' (Cost=1 Card=1 Bytes=22)

  14   13                           INDEX (UNIQUE SCAN) OF 'I4867_IX1_
          PROCESS_STATUS_LIST' (UNIQUE)

  15   12                         TABLE ACCESS (BY INDEX ROWID) OF 'T4
          282_BUSINESS_PROCESS_LIST' (Cost=1 Card=1 Bytes=16)

  16   15                           INDEX (UNIQUE SCAN) OF 'I4282_IX1_
          BUSINESS_PROCESS' (UNIQUE)

  17   11                       TABLE ACCESS (BY INDEX ROWID) OF 'T486
          7_PROCESS_STATUS_LIST' (Cost=1 Card=39 Bytes=858)

  18   17                         INDEX (UNIQUE SCAN) OF 'I4867_PK_PRO
          CESS_STATUS_LIST' (UNIQUE)

  19   10                     INDEX (FULL SCAN) OF 'I4868_PK_PROCESS_S
          TATUS_DEFN' (UNIQUE) (Cost=1 Card=26 Bytes=156)

  20    9                   TABLE ACCESS (BY INDEX ROWID) OF 'T4869_UN
          IT_PROC_STATUS_ASSIGN' (Cost=250 Card=355619 Bytes=6401142)

  21   20                     INDEX (RANGE SCAN) OF 'I4869_IX4_UNIT_PR
          OC_STATUS_ASS' (UNIQUE) (Cost=73 Card=355619)

  22    6               TABLE ACCESS (BY INDEX ROWID) OF 'T4201_UNIT'
          (Cost=2 Card=1388953 Bytes=13889530)

  23   22                 INDEX (UNIQUE SCAN) OF 'I4201_PK_UNIT' (UNIQ
          UE) (Cost=1 Card=1388953)

  24    5             TABLE ACCESS (BY INDEX ROWID) OF 'T4059_BUS_REF_
          LIST' (Cost=4 Card=3004512 Bytes=30045120)

  25   24               INDEX (RANGE SCAN) OF 'I4059_IX5_BUS_REF_LIST'
           (NON-UNIQUE) (Cost=3 Card=3004512)

  26    4           INDEX (RANGE SCAN) OF 'I4110_PK_STR_BUS_REF' (UNIQ
          UE) (Cost=3 Card=1388543 Bytes=13885430)

  27    3         TABLE ACCESS (BY INDEX ROWID) OF 'T4118_STRUCTURE_MF
          _DATA' (Cost=2 Card=71080 Bytes=3056440)

  28   27           INDEX (UNIQUE SCAN) OF 'I4118_PK_STRUCTURE_MF_DATA
          ' (UNIQUE) (Cost=1 Card=71080)

  29    2       FILTER
  30   29         FILTER
  31   30           NESTED LOOPS (Cost=8 Card=1 Bytes=73)
  32   31             NESTED LOOPS (Cost=6 Card=1 Bytes=28)
  33   32               INDEX (UNIQUE SCAN) OF 'I4317_PK_COUNTRY_GE_CO
          RP' (UNIQUE)

  34   32               TABLE ACCESS (BY INDEX ROWID) OF 'T4113_STRUCT
          URE_CONTACT_LIST' (Cost=5 Card=1 Bytes=24)

  35   34                 INDEX (RANGE SCAN) OF 'I4113_IX8_STRUCTURE_C
          ONTACT_LI' (NON-UNIQUE) (Cost=2 Card=1)

  36   31             TABLE ACCESS (BY INDEX ROWID) OF 'T4118_STRUCTUR
          E_MF_DATA' (Cost=2 Card=1 Bytes=45)

  37   36               INDEX (UNIQUE SCAN) OF 'I4118_PK_STRUCTURE_MF_
          DATA' (UNIQUE) (Cost=1 Card=1)

  38   29         NESTED LOOPS (Cost=8 Card=1 Bytes=67)
  39   38           TABLE ACCESS (BY INDEX ROWID) OF 'T4113_STRUCTURE_
          CONTACT_LIST' (Cost=6 Card=1 Bytes=24)

  40   39             INDEX (RANGE SCAN) OF 'I4113_IX8_STRUCTURE_CONTA
          CT_LI' (NON-UNIQUE) (Cost=3 Card=1)

  41   38           TABLE ACCESS (BY INDEX ROWID) OF 'T4118_STRUCTURE_
          MF_DATA' (Cost=2 Card=711 Bytes=30573)

  42   41             INDEX (UNIQUE SCAN) OF 'I4118_PK_STRUCTURE_MF_DA
          TA' (UNIQUE) (Cost=1 Card=711)

 


Followup   March 18, 2005 - 10am Central time zone:

are you running this in toad? 

1 stars Tunning Sql statements with Group by   March 21, 2005 - 1am Central time zone
Reviewer: Thomas Varekat from Mumbai, India
Yes i am running the query in code. 
 


1 stars Tuning Sql statements   March 21, 2005 - 1am Central time zone
Reviewer: Thomas Varekat from Mumbai, India
Yes. I am running the sql query in toad. But the explain plan was from sql-plus 


Followup   March 21, 2005 - 10am Central time zone:

fine - toad fetches the first N records and stops.  Toad faked you out.

toad didn't run the query to completion.

use sqlplus and you'll see. 

5 stars Explain plan question   March 22, 2005 - 2am Central time zone
Reviewer: veeresh 
Hi Tom,
  I have two different Queries.

Query 1. Select * from dept
Query 2. Select * from emp where nvl(deptno,0) = Nvl(:Deptno,0)

Query 2 will executed based upon the number of records fethed by Query 1. 
Means For each record of Query 1 , Query2 2 will execute.

How I can Explain plan for both queries in a single statement?

Thanks in Advance

 


Followup   March 22, 2005 - 11am Central time zone:

you cannot, they are two statements, explain plan is a statement thing.

nvl(deptno,0) - ugh. 

1 stars Tuning Sql statements   March 23, 2005 - 3am Central time zone
Reviewer: Thomas Varekat from Mumbai, India
Even in sql-plus the group by is taking lot of time. If i remove the group by clause the result is 
retrieved within 2-3 seconds. 


Followup   March 23, 2005 - 8am Central time zone:

tkprof it.

show us something "real" -- something we can see.

tkprof will show us the work performed. 

5 stars analyze   March 23, 2005 - 9am Central time zone
Reviewer: safrin from South India
In the above answer you have said,

"If you have not analyzed the tables recently -- with 
their current set of data, then the plans generated by the optimizer can be 
quite bad indeed."

so it means, in production tables should be analyzed every 3 months or so , so that optimizer will 
prepare new plan as per the current data. (if CHOOSE ie.. CBO)
Am I right. 



 


Followup   March 23, 2005 - 9am Central time zone:

3 months?  I'm fond of 2.432^1.23 hours

(kidding).

it is a function of how frequently the data changes - you might never need to analyze some tables, 
you might frequently need others to be analyzed, you might use dbms_stats.set table stats for yet 
others to give the optimizer the information it needs. 

4 stars Unique situation   March 24, 2005 - 3am Central time zone
Reviewer: Utpal from India
Tom,

I have a unique situation. I have a query which when fired independently uses the index and works 
properly. But the same query which is a cursor inside a procedure makes a full table scan. I even 
forced the query to use the Index. Even then it is not doing so. The table contains 1500000 records 
and table and index of that table have been analyzed with compute statistics. There is only one 
table in the query - No joins ! - Very strange ! Can you please let me know what could be the 
reason ? 


Followup   March 24, 2005 - 8am Central time zone:

test case?

tkprof results?

anything? 

5 stars CBO vs RBO   March 31, 2005 - 6am Central time zone
Reviewer: Nitesh from Malaysia
we have a situation whereby the SQL cannot be changed as it is being generated by an OLTP s/w 
(Siebel). We notice when the below SQL runs in RBO (using hint) then the results return in 2 secs. 
However, when its in 'CHOOSE' (ie. CBO)mode then it takes about 40mins!! 

The DBA tells me that all table stats are up-to-date. I have copied the SQL, the explain plan for 
'choose' and explain plan for RULE below - any tips on improving performance would be appreciated.

SELECT  
      T31.CONFLICT_ID,
      T31.LAST_UPD,
      T31.CREATED,....
   FROM        SIEBEL.S_CONTACT_FNX T1,
       SIEBEL.S_ACT_EMP T2,
       SIEBEL.S_CONTACT T3,
       SIEBEL.S_PROD_LN T4,
       SIEBEL.S_CONTACT T5,
       SIEBEL.S_EVT_MKTG T6,
       SIEBEL.S_PROD_LN T7,
       SIEBEL.S_FN_APPR T8,
       SIEBEL.S_CONTACT T9,
       SIEBEL.S_REVN T10,
       SIEBEL.S_PARTY T11,
       SIEBEL.S_PROD_INT T12,
       SIEBEL.S_PROJ T13,
       SIEBEL.S_PROD_DEFECT T14,
       SIEBEL.S_PARTY_RPT_REL T15,
       SIEBEL.S_PROJITEM T16,
       SIEBEL.S_EVT_ACT_SS T17,
       SIEBEL.S_POSTN T18,
       SIEBEL.S_PARTY T19,
       SIEBEL.S_CONTACT T20,
       SIEBEL.S_EVT_ACT_FNX T21,
       SIEBEL.S_USER T22,
       SIEBEL.S_CONTACT T23,
       SIEBEL.S_ASSET T24,
       SIEBEL.S_PROD_INT T25,
       SIEBEL.S_EVT_CAL T26,
       SIEBEL.S_ORG_EXT T27,
       SIEBEL.S_OPTY T28,
       SIEBEL.S_POSTN T29,
       SIEBEL.S_CONTACT T30,
       SIEBEL.S_EVT_ACT T31
   WHERE 
      T21.AMS_ACT_ID = T8.ROW_ID (+) AND
      T31.OWNER_PER_ID = T9.PAR_ROW_ID (+) AND
      T31.ASSET_ID = T24.ROW_ID (+) AND
      T31.OPTY_ID = T28.ROW_ID (+) AND
      T31.TARGET_OU_ID = T27.PAR_ROW_ID (+) AND
      T31.SRA_DEFECT_ID = T14.ROW_ID (+) AND
      T31.PROJ_ID = T13.ROW_ID (+) AND
      T31.PROJ_ITEM_ID = T16.ROW_ID (+) AND
      T31.CREATED_BY = T30.PAR_ROW_ID (+) AND
      T9.PR_HELD_POSTN_ID = T18.PAR_ROW_ID (+) AND
      T31.X_PROD_INT_ID = T12.ROW_ID (+) AND
      T31.X_PROD_LN_ID = T7.ROW_ID (+) AND
      T31.LAST_UPD_BY = T23.PAR_ROW_ID (+) AND
      T31.ROW_ID = T21.PAR_ROW_ID (+) AND
      T31.ROW_ID = T17.PAR_ROW_ID (+) AND
      T31.ROW_ID = T26.PAR_ROW_ID (+) AND
      T31.ROW_ID = T6.PAR_ROW_ID (+) AND
      T31.OWNER_PER_ID = T2.EMP_ID AND T31.ROW_ID = T2.ACTIVITY_ID AND
      T2.EMP_ID = T11.ROW_ID AND
      T2.EMP_ID = T20.PAR_ROW_ID (+) AND
      T2.EMP_ID = T22.PAR_ROW_ID (+) AND
      T20.PR_HELD_POSTN_ID = T29.PAR_ROW_ID (+) AND
      T31.TARGET_PER_ID = T19.ROW_ID (+) AND
      T31.TARGET_PER_ID = T5.PAR_ROW_ID (+) AND
      T31.TARGET_PER_ID = T1.PAR_ROW_ID (+) AND
      T28.PR_OPTYPRD_ID = T10.ROW_ID (+) AND
      T10.PROD_ID = T25.ROW_ID (+) AND
      T10.PROD_LN_ID = T4.ROW_ID (+) AND
      T2.EMP_ID = T3.ROW_ID AND
      T3.PR_HELD_POSTN_ID = T15.SUB_PARTY_ID AND
      ((T31.TODO_CD != 'Time Tracking' AND T31.TODO_CD != 'Property Information' AND 
(T31.X_POSTN_TYPE != 'Mobile CRM' OR T31.X_POSTN_TYPE IS NULL)) AND
      (T15.PARTY_ID = '0-5220') AND
      (T31.APPT_REPT_REPL_CD IS NULL) AND
      ((T2.ACT_TEMPLATE_FLG != 'Y' AND T2.ACT_TEMPLATE_FLG != 'P' OR T2.ACT_TEMPLATE_FLG IS NULL) 
AND (T31.OPTY_ID IS NULL OR T28.SECURE_FLG != 'Y' OR T28.SECURE_FLG IS NULL OR T31.OPTY_ID IN (
            SELECT SQ1_T1.OPTY_ID
               FROM  SIEBEL.S_OPTY_POSTN SQ1_T1, SIEBEL.S_CONTACT SQ1_T2, SIEBEL.S_PARTY SQ1_T3, 
SIEBEL.S_POSTN SQ1_T4
               
               WHERE ( SQ1_T4.PR_EMP_ID = SQ1_T2.PAR_ROW_ID AND SQ1_T3.ROW_ID = SQ1_T4.PAR_ROW_ID 
AND SQ1_T1.POSITION_ID = SQ1_T3.ROW_ID) 
                   AND (SQ1_T2.ROW_ID = '0-1')))) AND
      (T31.PRIV_FLG = 'N' OR T31.PRIV_FLG IS NULL OR T31.OWNER_PER_ID = '0-1')) AND
      ((T2.ACT_EVT_STAT_CD = 'Not Started' OR T2.ACT_EVT_STAT_CD = 'In Progress') AND 
T2.ACT_TODO_PLNEND_DT <= TO_DATE('03/29/2005 23:59:59','MM/DD/YYYY HH24:MI:SS'))
   ORDER BY
      T31.CREATED DESC
_____________
Operation    Object Name    Rows    Bytes    Cost

SELECT STATEMENT Optimizer Mode=CHOOSE        1         2085
  SORT ORDER BY        1    1 K    2085
    FILTER                    
      NESTED LOOPS OUTER        1    1 K    2083
        NESTED LOOPS OUTER        1    1 K    2082
          NESTED LOOPS OUTER        1    1 K    2081
            NESTED LOOPS OUTER        1    1 K    2080
              NESTED LOOPS OUTER        1    1 K    2079
                NESTED LOOPS OUTER        1    1 K    2078
                  NESTED LOOPS OUTER        1    1 K    2077
                    NESTED LOOPS        1    1 K    2076
                      NESTED LOOPS        15    20 K    2075
                        NESTED LOOPS OUTER        15    20 K    2074
                          NESTED LOOPS        15    20 K    2073
                            NESTED LOOPS OUTER        15    19 K    2072
                              NESTED LOOPS OUTER        15    19 K    2071
                                NESTED LOOPS OUTER        15    18 K    2070
                                  NESTED LOOPS OUTER        15    18 K    2069
                                    NESTED LOOPS OUTER        15    17 K    2068
                                      NESTED LOOPS OUTER        15    17 K    2067
                                        NESTED LOOPS OUTER        15    15 K    2066
                                          NESTED LOOPS OUTER        15    15 K    2065
                                            NESTED LOOPS OUTER        15    14 K    2064
                                              NESTED LOOPS OUTER        15    14 K    2063
                                                NESTED LOOPS OUTER        15    13 K    2062
                                                  NESTED LOOPS OUTER        15    13 K    2061
                                                    NESTED LOOPS OUTER        15    12 K    2060
                                                      NESTED LOOPS OUTER        15    11 K    2059
                                                        NESTED LOOPS OUTER        15    9 K    2058
                                                          NESTED LOOPS OUTER        15    8 K    
2057
                                                            NESTED LOOPS OUTER        15    7 K    
2056
                                                              NESTED LOOPS        15    6 K    2055
                                                                NESTED LOOPS OUTER        460    26 
K    2046
                                                                  TABLE ACCESS FULL    S_ACT_EMP    
460    17 K    2041
                                                                  TABLE ACCESS BY INDEX ROWID    
S_USER    1    18    2
                                                                    INDEX UNIQUE SCAN    S_USER_U2  
  1          
                                                                TABLE ACCESS BY INDEX ROWID    
S_EVT_ACT    1    411    2
                                                                  INDEX UNIQUE SCAN    S_EVT_ACT_P1 
   1         1
                                                              TABLE ACCESS BY INDEX ROWID    
S_PROJITEM    1    45    2
                                                                INDEX UNIQUE SCAN    S_PROJITEM_P1  
  1          
                                                            TABLE ACCESS BY INDEX ROWID    
S_PROD_DEFECT    1    43    2
                                                              INDEX UNIQUE SCAN    S_PROD_DEFECT_P1 
   1          
                                                          TABLE ACCESS BY INDEX ROWID    S_PROJ    
1    107    2
                                                            INDEX UNIQUE SCAN    S_PROJ_P1    1     
     
                                                        TABLE ACCESS BY INDEX ROWID    S_EVT_ACT_SS 
   1    137    2
                                                          INDEX RANGE SCAN    S_EVT_ACT_SS_U1    1  
       1
                                                      TABLE ACCESS BY INDEX ROWID    S_PROD_LN    1 
   27    2
                                                        INDEX UNIQUE SCAN    S_PROD_LN_P1    1      
    
                                                    TABLE ACCESS BY INDEX ROWID    S_EVT_CAL    1   
 60    2
                                                      INDEX RANGE SCAN    S_EVT_CAL_U1    1         
1
                                                  TABLE ACCESS BY INDEX ROWID    S_EVT_MKTG    1    
56    2
                                                    INDEX RANGE SCAN    S_EVT_MKTG_U1    1         
1
                                                TABLE ACCESS BY INDEX ROWID    S_ORG_EXT    1    24 
   2
                                                  INDEX UNIQUE SCAN    S_ORG_EXT_U3    1          
                                              TABLE ACCESS BY INDEX ROWID    S_PROD_INT    1    16  
  2
                                                INDEX UNIQUE SCAN    S_PROD_INT_P1    1          
                                            TABLE ACCESS BY INDEX ROWID    S_EVT_ACT_FNX    1    68 
   2
                                              INDEX RANGE SCAN    S_EVT_ACT_FNX_U1    1         2
                                          TABLE ACCESS BY INDEX ROWID    S_FN_APPR    1    12    2
                                            INDEX UNIQUE SCAN    S_FN_APPR_P1    1          
                                        TABLE ACCESS BY INDEX ROWID    S_OPTY    1    118    2
                                          INDEX UNIQUE SCAN    S_OPTY_P1    1         1
                                      TABLE ACCESS BY INDEX ROWID    S_REVN    1    21    2
                                        INDEX UNIQUE SCAN    S_REVN_P1    1         1
                                    TABLE ACCESS BY INDEX ROWID    S_PROD_LN    1    27    2
                                      INDEX UNIQUE SCAN    S_PROD_LN_P1    1          
                                  TABLE ACCESS BY INDEX ROWID    S_PROD_INT    1    16    2
                                    INDEX UNIQUE SCAN    S_PROD_INT_P1    1          
                                TABLE ACCESS BY INDEX ROWID    S_CONTACT_FNX    1    100    2
                                  INDEX RANGE SCAN    S_CONTACT_FNX_I04    1         2
                              INDEX UNIQUE SCAN    S_PARTY_P1    1    10    1
                            INDEX UNIQUE SCAN    S_PARTY_P1    1    10    1
                          TABLE ACCESS BY INDEX ROWID    S_ASSET    1    28    2
                            INDEX UNIQUE SCAN    S_ASSET_P1    1         1
                        TABLE ACCESS BY INDEX ROWID    S_CONTACT    1    24    2
                          INDEX UNIQUE SCAN    S_CONTACT_P1    1         1
                      INDEX RANGE SCAN    S_PARTY_RPT_REL_U1    1    17    1
                    TABLE ACCESS BY INDEX ROWID    S_CONTACT    1    29    2
                      INDEX UNIQUE SCAN    S_CONTACT_U2    1         1
                  TABLE ACCESS BY INDEX ROWID    S_CONTACT    1    29    2
                    INDEX UNIQUE SCAN    S_CONTACT_U2    1         1
                TABLE ACCESS BY INDEX ROWID    S_CONTACT    1    44    2
                  INDEX UNIQUE SCAN    S_CONTACT_U2    1         1
              TABLE ACCESS BY INDEX ROWID    S_POSTN    1    17    2
                INDEX UNIQUE SCAN    S_POSTN_U2    1          
            TABLE ACCESS BY INDEX ROWID    S_CONTACT    1    44    2
              INDEX UNIQUE SCAN    S_CONTACT_U2    1         1
          TABLE ACCESS BY INDEX ROWID    S_POSTN    1    17    2
            INDEX UNIQUE SCAN    S_POSTN_U2    1          
        TABLE ACCESS BY INDEX ROWID    S_CONTACT    1    54    2
          INDEX UNIQUE SCAN    S_CONTACT_U2    1         1
      NESTED LOOPS        1    68    5
        NESTED LOOPS        2    116    4
          NESTED LOOPS        1    37    3
            TABLE ACCESS BY INDEX ROWID    S_CONTACT    1    18    2
              INDEX UNIQUE SCAN    S_CONTACT_P1    3 M         2
            INDEX RANGE SCAN    S_OPTY_POSTN_U1    1    19    2
          TABLE ACCESS BY INDEX ROWID    S_POSTN    2    42    2
            INDEX RANGE SCAN    S_POSTN_V1    2         1
        INDEX UNIQUE SCAN    S_PARTY_P1    1    10    1
________________
Operation    Object Name    Rows    Bytes    Cost    Object Node    In/Out    PStart    PStop

SELECT STATEMENT Optimizer Mode=HINT: RULE                                                         
  SORT ORDER BY                                                         
    FILTER                                                         
      NESTED LOOPS OUTER                                                         
        NESTED LOOPS OUTER                                                         
          NESTED LOOPS OUTER                                                         
            NESTED LOOPS OUTER                                                         
              NESTED LOOPS OUTER                                                         
                NESTED LOOPS OUTER                                                         
                  NESTED LOOPS OUTER                                                         
                    NESTED LOOPS OUTER                                                         
                      NESTED LOOPS OUTER                                                         
                        NESTED LOOPS OUTER                                                         
                          NESTED LOOPS OUTER                                                        
 
                            NESTED LOOPS OUTER                                                      
   
                              NESTED LOOPS OUTER                                                    
     
                                NESTED LOOPS                                                        
 
                                  NESTED LOOPS OUTER                                                
         
                                    NESTED LOOPS OUTER                                              
           
                                      NESTED LOOPS OUTER                                            
             
                                        NESTED LOOPS OUTER                                          
               
                                          NESTED LOOPS OUTER                                        
                 
                                            NESTED LOOPS OUTER                                      
                   
                                              NESTED LOOPS OUTER                                    
                     
                                                NESTED LOOPS OUTER                                  
                       
                                                  NESTED LOOPS OUTER                                
                         
                                                    NESTED LOOPS OUTER                              
                           
                                                      NESTED LOOPS OUTER                            
                             
                                                        NESTED LOOPS OUTER                          
                               
                                                          NESTED LOOPS OUTER                        
                                 
                                                            NESTED LOOPS                            
                             
                                                              NESTED LOOPS                          
                               
                                                                NESTED LOOPS                        
                                 
                                                                  TABLE ACCESS BY INDEX ROWID    
S_PARTY_RPT_REL                                                     
                                                                    INDEX RANGE SCAN    
S_PARTY_RPTREL_F50                                                     
                                                                  TABLE ACCESS BY INDEX ROWID    
S_CONTACT                                                     
                                                                    INDEX RANGE SCAN    
S_CONTACT_V5                                                     
                                                                TABLE ACCESS BY INDEX ROWID    
S_ACT_EMP                                                     
                                                                  INDEX RANGE SCAN    S_ACT_EMP_M4  
                                                   
                                                              TABLE ACCESS BY INDEX ROWID    
S_EVT_ACT                                                     
                                                                INDEX UNIQUE SCAN    S_EVT_ACT_P1   
                                                  
                                                            TABLE ACCESS BY INDEX ROWID    
S_CONTACT                                                     
                                                              INDEX UNIQUE SCAN    S_CONTACT_U2     
                                                
                                                          TABLE ACCESS BY INDEX ROWID    S_OPTY     
                                                
                                                            INDEX UNIQUE SCAN    S_OPTY_P1          
                                           
                                                        TABLE ACCESS BY INDEX ROWID    S_ORG_EXT    
                                                 
                                                          INDEX UNIQUE SCAN    S_ORG_EXT_U3         
                                            
                                                      TABLE ACCESS BY INDEX ROWID    S_ASSET        
                                             
                                                        INDEX UNIQUE SCAN    S_ASSET_P1             
                                        
                                                    TABLE ACCESS BY INDEX ROWID    S_CONTACT        
                                             
                                                      INDEX UNIQUE SCAN    S_CONTACT_U2             
                                        
                                                  TABLE ACCESS BY INDEX ROWID    S_USER             
                                        
                                                    INDEX UNIQUE SCAN    S_USER_U2                  
                                   
                                                TABLE ACCESS BY INDEX ROWID    S_CONTACT            
                                         
                                                  INDEX UNIQUE SCAN    S_CONTACT_U2                 
                                    
                                              TABLE ACCESS BY INDEX ROWID    S_POSTN                
                                     
                                                INDEX UNIQUE SCAN    S_POSTN_U2                     
                                
                                            INDEX UNIQUE SCAN    S_PARTY_P1                         
                            
                                          TABLE ACCESS BY INDEX ROWID    S_PROJITEM                 
                                    
                                            INDEX UNIQUE SCAN    S_PROJITEM_P1                      
                               
                                        TABLE ACCESS BY INDEX ROWID    S_PROD_DEFECT                
                                     
                                          INDEX UNIQUE SCAN    S_PROD_DEFECT_P1                     
                                
                                      TABLE ACCESS BY INDEX ROWID    S_PROJ                         
                            
                                        INDEX UNIQUE SCAN    S_PROJ_P1                              
                       
                                    TABLE ACCESS BY INDEX ROWID    S_PROD_INT                       
                              
                                      INDEX UNIQUE SCAN    S_PROD_INT_P1                            
                         
                                  INDEX UNIQUE SCAN    S_PARTY_P1                                   
                  
                                TABLE ACCESS BY INDEX ROWID    S_REVN                               
                      
                                  INDEX UNIQUE SCAN    S_REVN_P1                                    
                 
                              TABLE ACCESS BY INDEX ROWID    S_PROD_INT                             
                        
                                INDEX UNIQUE SCAN    S_PROD_INT_P1                                  
                   
                            TABLE ACCESS BY INDEX ROWID    S_CONTACT                                
                     
                              INDEX UNIQUE SCAN    S_CONTACT_U2                                     
                
                          TABLE ACCESS BY INDEX ROWID    S_POSTN                                    
                 
                            INDEX UNIQUE SCAN    S_POSTN_U2                                         
            
                        TABLE ACCESS BY INDEX ROWID    S_PROD_LN                                    
                 
                          INDEX UNIQUE SCAN    S_PROD_LN_P1                                         
            
                      TABLE ACCESS BY INDEX ROWID    S_CONTACT                                      
               
                        INDEX UNIQUE SCAN    S_CONTACT_U2                                           
          
                    TABLE ACCESS BY INDEX ROWID    S_PROD_LN                                        
             
                      INDEX UNIQUE SCAN    S_PROD_LN_P1                                             
        
                  TABLE ACCESS BY INDEX ROWID    S_EVT_CAL                                          
           
                    INDEX RANGE SCAN    S_EVT_CAL_U1                                                
     
                TABLE ACCESS BY INDEX ROWID    S_EVT_ACT_FNX                                        
             
                  INDEX RANGE SCAN    S_EVT_ACT_FNX_U1                                              
       
              TABLE ACCESS BY INDEX ROWID    S_FN_APPR                                              
       
                INDEX UNIQUE SCAN    S_FN_APPR_P1                                                   
  
            TABLE ACCESS BY INDEX ROWID    S_EVT_ACT_SS                                             
        
              INDEX RANGE SCAN    S_EVT_ACT_SS_U1                                                   
  
          TABLE ACCESS BY INDEX ROWID    S_EVT_MKTG                                                 
    
            INDEX RANGE SCAN    S_EVT_MKTG_U1                                                     
        TABLE ACCESS BY INDEX ROWID    S_CONTACT_FNX                                                
     
          INDEX RANGE SCAN    S_CONTACT_FNX_I05                                                     

      NESTED LOOPS                                                         
        NESTED LOOPS                                                         
          NESTED LOOPS                                                         
            TABLE ACCESS BY INDEX ROWID    S_CONTACT                                                
     
              INDEX UNIQUE SCAN    S_CONTACT_P1                                                     

            TABLE ACCESS BY INDEX ROWID    S_POSTN                                                  
   
              INDEX RANGE SCAN    S_POSTN_V1                                                     
          INDEX UNIQUE SCAN    S_PARTY_P1                                                     
        INDEX RANGE SCAN    S_OPTY_POSTN_U1 


Followup   March 31, 2005 - 8am Central time zone:

sorry, i cannot take every multi page query in a followup/review and say "why" -- i do these 
sections really fast.

My approach that you can follow (not asking you to do this and post the number) would be:

a) obtain an autotrace traceonly explain output of the query, noting the CARD= values

b) obtain a tkprof of same, the Row Source plan in there will have the actuals

c) see where they diverge significantly -- see where the CBO thought (autotrace) that it would get 
say 1,000 rows but the tkprof shows it got 100,000 rows.

d) ask "why would the cbo have thought that", generally indicates missing/invalid/stale stats. 

5 stars full table scan because of subquery   April 10, 2005 - 2pm Central time zone
Reviewer: Sean Li from NJ, USA
Hi Tom,

Why does this query do full table scan on table t2 when there is a subquery?  How can I avoid full 
table scan in this case?  Of course, four tables have different data in PROD.  We have to use RBO.  
Oracle 9204

Thanks so much for your help.
----------------------------------------------------
create table t1 as select * from sys.dba_objects;
create index t1_object_id_idx on t1(object_id);

create table t2 as select * from sys.dba_objects;
create index t2_object_id_idx on t2(object_id);

create table t3 as select * from sys.dba_objects;
create index t3_object_id_idx on t3(object_id);

create table t4 as select * from sys.dba_objects;
create index t4_object_id_idx on t4(object_id);

create or replace view v1(object_id) as 
    select t1.object_id
    from t1, t2
    where t1.object_id=t2.object_id
    union
    select t3.object_id
    from t3, t2
    where t3.object_id=t2.object_id;


-- It uses indexes if there is no subquery.
SQL>explain plan for select * from v1
 where object_id=1;

SQL> @d:/oracle/ora92/rdbms/admin/utlxpls.sql

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

--------------------------------------------------------------------------
| Id  | Operation            |  Name             | Rows  | Bytes | Cost  |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |                   |       |       |       |
|   1 |  VIEW                | V1                |       |       |       |
|   2 |   SORT UNIQUE        |                   |       |       |       |
|   3 |    UNION-ALL         |                   |       |       |       |
|   4 |     NESTED LOOPS     |                   |       |       |       |
|*  5 |      INDEX RANGE SCAN| T1_OBJECT_ID_IDX  |       |       |       |
|*  6 |      INDEX RANGE SCAN| T2_OBJEC_ID_IDX   |       |       |       |
|   7 |     NESTED LOOPS     |                   |       |       |       |
|*  8 |      INDEX RANGE SCAN| T3_OBJECT_ID_IDX  |       |       |       |
|*  9 |      INDEX RANGE SCAN| T2_OBJEC_ID_IDX   |       |       |       |
--------------------------------------------------------------------------

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

   5 - access("T1"."OBJECT_ID"=1)
   6 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
   8 - access("T3"."OBJECT_ID"=1)
   9 - access("T3"."OBJECT_ID"="T2"."OBJECT_ID")

Note: rule based optimization

25 rows selected.

SQL> truncate table plan_table;

Table truncated.

-- full table scan on t2 when there is subquery.
SQL> explain plan for select * from v1
  2  where object_id in (select object_id from t4
  3                      where object_id=1);

Explained.

SQL> @d:/oracle/ora92/rdbms/admin/utlxpls.sql

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

-----------------------------------------------------------------------------
| Id  | Operation               |  Name             | Rows  | Bytes | Cost  |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |                   |       |       |       |
|   1 |  MERGE JOIN             |                   |       |       |       |
|   2 |   SORT JOIN             |                   |       |       |       |
|   3 |    VIEW                 | V1                |       |       |       |
|   4 |     SORT UNIQUE         |                   |       |       |       |
|   5 |      UNION-ALL          |                   |       |       |       |
|   6 |       NESTED LOOPS      |                   |       |       |       |
|   7 |        TABLE ACCESS FULL| T2                |       |       |       |
|*  8 |        INDEX RANGE SCAN | T1_OBJECT_ID_IDX  |       |       |       |
|   9 |       NESTED LOOPS      |                   |       |       |       |
|  10 |        TABLE ACCESS FULL| T2                |       |       |       |
|* 11 |        INDEX RANGE SCAN | T3_OBJECT_ID_IDX  |       |       |       |
|* 12 |   SORT JOIN             |                   |       |       |       |
|  13 |    VIEW                 | VW_NSO_1          |       |       |       |
|  14 |     SORT UNIQUE         |                   |       |       |       |
|* 15 |      INDEX RANGE SCAN   | T4_OBJECT_ID_IDX  |       |       |       |
-----------------------------------------------------------------------------

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

   8 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
  11 - access("T3"."OBJECT_ID"="T2"."OBJECT_ID")
  12 - access("V1"."OBJECT_ID"="VW_NSO_1"."$nso_col_1")
       filter("V1"."OBJECT_ID"="VW_NSO_1"."$nso_col_1")
  15 - access("T4"."OBJECT_ID"=1)

Note: rule based optimization

32 rows selected.

SQL>  


5 stars my query processing 31 records at a time only   April 29, 2005 - 4pm Central time zone
Reviewer: prasad from India
In the package,one Query - each time processing 30 - 32 records only why ?? 

any performance issue ??
   


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

sorry, does not make sense, don't know what you are trying to say 

4 stars Explain Plan On Update --Locks the table.   May 9, 2005 - 12pm Central time zone
Reviewer: Neeraj Nagpal from Costa Mesa, CA
Tom,

Is there a reason why explain plan on an UPDATE statement,
should lock the row in the table being explained ??


CREATE TABLE TWO ( ONE NUMBER, TWO CHAR(1 BYTE) )
/
Insert into TWO  values (1,'2')
/
SQL> commit;

Commit complete.

SQL> 
SQL> 
SQL> 
SQL> set autot trace expl
SQL> update two set one = 2
SQL> /

1 row updated.

Execution Plan
----------------------------------------------------------
0 null UPDATE STATEMENT Optimizer=CHOOSE                            null
1    0   UPDATE OF 'TWO'                                            null
2    1     TABLE ACCESS (FULL) OF 'TWO'                             null



Now Checking the locks 
------------------
    select  distinct SESSION_ID     , a.os_user_name as "Os user",b.object_name "Object Name",
    decode(a.locked_mode,
    ,'ROW SHARE MODE',
    ,'ROW EXCLUSIVE MODE',
    ,'SHARE MODE',
    ,'SHARE ROW EXCLUSIVE MODE',
    ,'EXCLUSIVE','UNKNOWN')as "Lock Mode"
     from
    v$locked_object a, all_objects b where
  a.object_id = b.object_id
SQL> 
SQL> 
SQL> /

SESSION_ID Os user                        Object Name                    Lock Mode
---------- ------------------------------ ------------------------------ 
---------------------------
         8 nnagpal                        PLAN_TABLE                     ROW EXCLUSIVE MODE
         8 nnagpal                        TWO  <---- (Locked Row)        ROW EXCLUSIVE MODE




Thanks Always For your help,
Neeraj 
 


Followup   May 9, 2005 - 2pm Central time zone:

SQL> update two set one = 2
SQL> /

1 row updated.


because autotrace traceonly explain with a MODIFICATION runs the statement.

a select is special because sqlplus knows that "even if I don't bother running this, they will get 
what they want without it running"

autotrace traceonly explain *does not prevent the statement from running*

it just doesn't run ones it knows "it doesn't need to" 

5 stars Thanks Very Much   May 9, 2005 - 8pm Central time zone
Reviewer: Neeraj Nagpal 
TOM, Thanks so much for your help! 
 


4 stars Explain Plan Interpretation   May 10, 2005 - 1am Central time zone
Reviewer: Dennis from Mumbai,India
Hi Tom,

Most of the queries in one of our databases are having execution plan similar to the one below.

Plan Table
----------------------------------------------------------------------------------------------------

| OPERATION                                                  |  NAME                         |  
ROWS | BYTES|  COST  |
----------------------------------------------------------------------------------------------------

| SELECT STATEMENT                                           |                               |     
1 |  112 |    301 |
|  TABLE ACCESS BY INDEX ROWID                               |AMC                            |     
1 |  112 |    301 |
|   INDEX RANGE SCAN                                         |AMC_PK                         |   
418 |      |      8 |
--------------------------------------------------------------------------------

What I interpret is the index was scanned for 418 rows to fetch 1 row from the table.
What I would like to know is  
1.What causes the index to be so unselective when it is a primary key. 
The application concerned has lots of deletes and inserts on this table does this have anything to 
do with this.
2.What does the rows coulmn signify is it the actual rows that will be returned after the execution 
or is it the  no of rows scanned to fetch 1 row.

thanks,
Dennis
 


Followup   May 10, 2005 - 8am Central time zone:

that means it THINKS it will get 418 rows from the index, to find a single row in the table.  It is 
the "plan"

tkprof of a sql_trace will show you what actually happens.


1) you don't give me anything to work with, how can I tell?????  no creates, no stats gathering 
method.....  you'll need to give me something to go on.

2) it is the number of rows flowing out of that step and into the next step.  

 

4 stars   May 16, 2005 - 4am Central time zone
Reviewer: dennis from India
Thanks Tom  


5 stars autotrace plan is dfferent comapred with tkprof   May 23, 2005 - 9am Central time zone
Reviewer: A reader 
Hi

We have one query running in development and production, one is very fast and the other slow. Two 
environments are very similar, almost same amount of data.
When we compared execution plan using autotrace they both share the same plan but when we sql 
traced the sessions and tkprof to get the execution plan then we see they are different.

Under what circumstances can autotrace behave this way? 


Followup   May 23, 2005 - 2pm Central time zone:

autotrace does not do bind variable peeking, real life will (in 9i and above).

autotrace will always assume ALL binds are strings, no matter what you define them as.  You would 
have to use to_number(:bv) and to_date(:bv) to avoid implicit conversions 


Best if you can put the literal values in (the ones the query was parsed with the first time) when 
autotracing...   

5 stars re-write SQL   May 24, 2005 - 9am Central time zone
Reviewer: Laurie Murray from Golden, CO USA
I need to tune this SQL.  Have worked a lot with indexing, hints and archiving off old data.  None 
of my usual tricks are working.  It looks like the SQL itself may need to be re-written.  Can you 
give me a couple of suggestions for re-writing it?  Thank you.
SELECT d.descr
, e.first_name || ' ' || e.last_name
, c.descr
, b.descr
, SUM(a.tl_quantity )
FROM ps_lp_curr_proj_vw b
, ps_lp_tl_task_bu c
, ps_dept_tbl d
, ps_personal_data e
, ps_tl_rptd_elptime a
WHERE b.taskgroup (+) = a.taskgroup
AND b.project_id (+) = a.project_id
AND c.taskgroup = a.taskgroup
AND c.task = a.task
AND c.effdt = (
SELECT MAX(c1.effdt)
FROM ps_lp_tl_task_bu c1
WHERE c1.taskgroup = c.taskgroup
AND c1.task = c.task
AND c1.effdt <= a.dur)
AND d.deptid = a.deptid
AND d.effdt = (
SELECT MAX(d1.effdt)
FROM ps_dept_tbl d1
WHERE d1.deptid = d.deptid
AND d1.effdt <= a.dur)
AND e.emplid = a.emplid
AND a.taskgroup = 'EUFIN'
AND a.dur >= '01/01/2005'
AND a.dur <= '07/05/2005'
GROUP BY d.descr , e.first_name || ' ' || e.last_name , c.descr , b.descr
ORDER BY d.descr , e.first_name || ' ' || e.last_name , c.descr , b.descr
/
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1536 Card=1 Bytes=16
          4)

   1    0   SORT (GROUP BY) (Cost=1536 Card=1 Bytes=164)
   2    1     FILTER
   3    2       NESTED LOOPS (Cost=1533 Card=1 Bytes=164)
   4    3         NESTED LOOPS (OUTER) (Cost=1532 Card=1 Bytes=143)
   5    4           NESTED LOOPS (Cost=1479 Card=1 Bytes=106)
   6    5             HASH JOIN (Cost=1473 Card=3 Bytes=207)
   7    6               INDEX (FAST FULL SCAN) OF 'PS0LP_TL_TASK_BU' (
          NON-UNIQUE) (Cost=2 Card=19 Bytes=684)

   8    6               INDEX (FAST FULL SCAN) OF 'LMLMLMMAY232005' (N
          ON-UNIQUE) (Cost=1470 Card=4533 Bytes=149589)

   9    5             INDEX (FAST FULL SCAN) OF 'LMLMLMLM' (NON-UNIQUE
          ) (Cost=2 Card=1394 Bytes=51578)

  10    4           VIEW OF 'PS_LP_CURR_PROJ_VW' (Cost=53 Card=81 Byte
          s=2997)

  11   10             FILTER
  12   11               INDEX (FAST FULL SCAN) OF 'PS0LP_TL_PROJECT' (
          UNIQUE) (Cost=53 Card=81 Bytes=3564)

  13   11               SORT (AGGREGATE)
  14   13                 INDEX (RANGE SCAN) OF 'FELIX' (NON-UNIQUE) (
          Cost=2 Card=1 Bytes=22)

  15    3         TABLE ACCESS (BY INDEX ROWID) OF 'PS_PERSONAL_DATA'
          (Cost=1 Card=12492 Bytes=262332)

  16   15           INDEX (UNIQUE SCAN) OF 'PS_PERSONAL_DATA' (UNIQUE)
  17    2       SORT (AGGREGATE)
  18   17         FIRST ROW (Cost=2 Card=1 Bytes=19)
  19   18           INDEX (RANGE SCAN (MIN/MAX)) OF 'PS_LP_TL_TASK_BU'
           (UNIQUE) (Cost=2 Card=1)

  20    2       SORT (AGGREGATE)
  21   20         INDEX (RANGE SCAN) OF 'LMLMLMLM' (NON-UNIQUE) (Cost=
          2 Card=1 Bytes=14)



Statistics
----------------------------------------------------------
          0  recursive calls
      24458  db block gets
  298062340  consistent gets
         28  physical reads
          0  redo size
      12222  bytes sent via SQL*Net to client
       1540  bytes received via SQL*Net from client
         12  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
        153  rows processed

ps_lp_curr_proj_vw b is a simple view
ps_lp_tl_task_bu c  is a table with 1500 rows
ps_dept_tbl d is a table with 1394 rows
ps_personal_data e is a table with 12,492 rows
ps_tl_rptd_elptime a is a table with 954,250 rows

Statistics are current on all tables.

ps_lp_curr_proj_vw view definition:
SELECT a.taskgroup 
, a.project_id 
, a.effdt 
, a.eff_status 
, a.DESCR 
, a.descrshort 
, a.user_field_3 
, a.user_field_1 
, a.user_field_2 
, a.product 
, a.lp_project_cat 
, a.comments 
FROM ps_lp_tl_project a 
WHERE a.effdt = ( 
SELECT MAX(a1.effdt) 
FROM ps_lp_tl_project a1 
WHERE a1.taskgroup = a.taskgroup 
AND a1.project_id = a.project_id 
AND a1.effdt <= SYSDATE 
AND a1.eff_status = 'A')

ps_lp_tl_project is a table with 44,419 rows.

LMLMLMMAY232005 is an index on ps_tl_rptd_elptime(DUR,
PROJECT_ID, TASK, EMPLID, DEPTID, TASKGROUP, TL_QUANTITY)

LMLMLMLM is an index on ps_dept_tbl( EFFDT,DEPTID,DESCR)

FELIX is an index on ps_lp_tl_project ( PROJECT_ID,
 EFFDT, TASKGROUP, EFF_STATUS, DESCR) 


Followup   May 24, 2005 - 12pm Central time zone:

what are the primary keys -- I mean, can we defer some of the joins until after the group by and 
perhaps use scalar subqueries?


eg: can we just sum by taskgroup, taks, a.dur -- then pick up the strings? 

5 stars Here are the PK's.   May 24, 2005 - 3pm Central time zone
Reviewer: Laurie Murray from Golden, CO USA
Hi, Tom:  I'm looking into the re-write based on your comments.  In the meantime, here's the answer 
to your question.
thanks,
Laurie

Primary Keys:
PS_PERSONAL_DATA EMPLID 1

PS_DEPT_TBL SETID 1
PS_DEPT_TBL DEPTID 2
PS_DEPT_TBL EFFDT 3

PS_LP_TL_TASK_BU TASKGROUP 1
PS_LP_TL_TASK_BU TASK 2
PS_LP_TL_TASK_BU EFFDT 3

PS_TL_RPTD_ELPTIME EMPLID 1
PS_TL_RPTD_ELPTIME EMPL_RCD 2
PS_TL_RPTD_ELPTIME DUR 3
PS_TL_RPTD_ELPTIME SEQNUM 4

PS_LP_TL_PROJECT TASKGROUP 1
PS_LP_TL_PROJECT PROJECT_ID 2
PS_LP_TL_PROJECT EFFDT 3

 


Followup   May 24, 2005 - 4pm Central time zone:

so it looks like getting the strings from the other tables by taskgroup/task/ MAX(effdt) could be 
put off, so you could probably aggregate by taskgroup/task/dur and then join. 

5 stars explain plan for and TKPROF   July 28, 2005 - 7am Central time zone
Reviewer: PINGU 
I have this query

SELECT :b1, :b2, DECODE (:b3, 'C', (-:b4), 'D', :b4, :b6),
       DECODE (:b3, 'C', (-:b8), 'D', :b8, :b10), :b11,
       DECODE (:b3, 'R', :b8, NULL), :b14, :b15, :b16, :b17,
       DECODE (:b3, 'R', :b4, NULL),
       DECODE (:b3, 'C', (-:b21), 'D', :b21, :b23),
       DECODE (:b3, 'C', (-:b25), 'D', :b25, :b27),
       DECODE (:b3, 'R', :b25, NULL), :b30, :b31, :b32, :b33, :b34, :b35,
       :b36, :b37, DECODE (:b3, 'C', NULL, cdl.dr_code_combination_id),
       DECODE (:b3, 'C', cdl.cr_code_combination_id, NULL),
       DECODE (:b40, 'N', 'H', 'Z'), :b41, :b42, SYSDATE, :b43, :b44, :b45,
       :b46, SYSDATE, 'N', DECODE (:b3, 'R', DECODE (:b48, 'N', 'I', :b3),
                                   :b3), :b51, DECODE (:b3, 'R', :b21, NULL),
       :b54, :b55, :b56, 'N', DECODE (:b3, 'R', 'N', NULL), :b58:b59, :b60,
       :b61, DECODE (:b3, 'R', DECODE (:b48, 'N', :b64, NULL), NULL), :b65,
       :b66, :b67, :b68, :b69, :b70, :b71, :b72
  FROM pa_expenditure_items itm,
       pa_expenditure_items itm1,
       pa_cost_distribution_lines cdl
 WHERE itm.expenditure_item_id = :b2
   AND itm1.expenditure_item_id =  itm.adjusted_expenditure_item_id
   AND DECODE(itm1.cost_distributed_flag, 'S', 
       DECODE(itm1.cost_dist_rejection_code, NULL, 'Y', 'N'), 'N', 'N', 'Y') = 'Y'
   AND cdl.expenditure_item_id = itm.adjusted_expenditure_item_id
   AND cdl.reversed_flag IS NULL
   AND cdl.line_num_reversed IS NULL
   AND cdl.line_type = :b3
   AND cdl.line_num = (SELECT MAX(cdl1.line_num)
                         FROM pa_cost_distribution_lines cdl1
                        WHERE cdl1.expenditure_item_id = cdl.expenditure_item_id
                          AND cdl1.line_type = cdl.line_type
                          AND cdl1.reversed_flag IS NULL
                          AND cdl1.line_num_reversed IS NULL )
   AND :b65:b76 IS NOT NULL
   AND :b66:b78 IS NOT NULL
   AND :b67:b80 IS NOT NULL
   AND :b68:b82 IS NOT NULL
   AND :b69:b84 IS NOT NULL
   AND :b70:b86 IS NOT NULL
   AND :b71:b88 IS NOT NULL
   AND :b72:b90 IS NOT NULL
   AND :b91 IS NULL
   AND itm.adjusted_expenditure_item_id IS NOT NULL
   AND NOT EXISTS 
       ( 
        SELECT NULL
        FROM pa_cost_distribution_lines cdl3
        WHERE cdl3.expenditure_item_id = :b2
        AND DECODE (cdl3.line_type, 'C', cdl3.cr_code_combination_id, cdl3.dr_code_combination_id) 
= :b93
        AND cdl3.billable_flag = :b42
        AND cdl3.acct_raw_cost = DECODE (cdl3.line_type, 'R', :b6, 'D', :b4, 'C', (-:b4))
        AND NVL (cdl3.ind_compiled_set_id, (-99)) = NVL (:b51, (-99))
        AND DECODE (cdl3.line_type, 'R', NVL (cdl3.acct_burdened_cost, 0), 1) = DECODE (:b3, 'R', 
NVL (:b4, 0), 1 )
        AND cdl3.line_type = DECODE (:b3, 'R', DECODE (:b48, 'N', 'I', :b3), :b3) -- causa el 
CONCAT
        AND cdl3.denom_raw_cost = DECODE (cdl3.line_type, 'R', :b10, 'D', :b8, 'C', (-:b8))
        AND DECODE (cdl3.line_type, 'R', NVL (cdl3.denom_burdened_cost, 0), 1) = DECODE (:b3, 'R', 
NVL (:b8, 0), 1)
        AND cdl3.amount = DECODE(cdl3.line_type, 'R', :b23, 'D', :b21, 'C', (-:b21) )
        AND DECODE (cdl3.line_type, 'R', NVL (cdl3.burdened_cost, 0), 1) = DECODE (:b3,'R', NVL 
(:b21, 0), 1)
        AND cdl3.project_raw_cost = DECODE (cdl3.line_type, 'R', :b27, 'D', :b25, 'C', (-:b25))
        AND DECODE (cdl3.line_type, 'R', NVL (cdl3.project_burdened_cost, 0), 1) = DECODE (:b3, 
'R', NVL (:b25, 0), 1 )
        AND cdl3.line_num_reversed IS NULL
        AND cdl3.reversed_flag IS NULL
       )


When traced with sql_trace tkprof shows a full scan on pa_cost_distribution_lines is performed, 
however if I do explain plan for the query it shows index range scan instead of full scan.

You mentioned that this only happens with autotrace how come it happens with explain plan as well? 
You said we should put to_number(:binds) however I see this in the plan_table

21 - access("PA_COST_DISTRIBUTION_LINES_ALL"."EXPENDITURE_ITEM_ID"=TO_NUMBER(:Z))

So explain plan actually does implicit to_numeber for us why it shows different plan as TKPROF? 


Followup   July 28, 2005 - 9am Central time zone:

search this site for

"bind variable peeking" 

3 stars Does CBO get it horribly wrong?   July 29, 2005 - 3pm Central time zone
Reviewer: Marco Coletti from Italy
I submitted a TAR to Metalink on the following problem, which is under review. It seems confirmed 
that CBO guesses wrong in this situation.

Still I can't believe that perhaps nobody noticed such a horrible behaviour.

I observed in 9.2.0.6 that sometimes the INDEX RANGE SCAN or INDEX UNIQUE SCAN execution plan step 
has no cost (zero cost) leading the CBO to choose NESTED LOOP over HASH JOIN.

Example:

create table A as select * from ALL_OBJECTS;
create table B as select * from ALL_OBJECTS;
alter table B add constraint PKB primary key (OBJECT_ID);
execute dbms_stats.gather_table_stats(OwnName => user, TabName => 'A', Cascade => true);
execute dbms_stats.gather_table_stats(OwnName => user, TabName => 'B', Cascade => true);

explain plan for select count(1) from A, B where A.OBJECT_ID = B.OBJECT_ID;

select * from plan;

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |     1 |    10 |    15 |
|   1 |  SORT AGGREGATE      |             |     1 |    10 |       |
|   2 |   NESTED LOOPS       |             | 23835 |   232K|    15 |
|   3 |    TABLE ACCESS FULL | A           | 23835 |   116K|    15 |
|*  4 |    INDEX UNIQUE SCAN | PKB         |     1 |     5 |       |
--------------------------------------------------------------------

You can see there is no cost associated to step id 4.
The CBO then thinks that doing and index unique scan 23835 times costs nothing.
Step 2 should cost about 24000, that is:
  15 + 23835*c
where c is the cost of an index unique scan (which is at least 1).

When hinted for hash join, the CBO estimates a cost of 22:

explain plan for select /*+ use_hash(A B) */ count(1) from A, B where A.OBJECT_ID = B.OBJECT_ID;

----------------------------------------------------------------------
| Id  | Operation              |  Name       | Rows  | Bytes | Cost  |
----------------------------------------------------------------------
|   0 | SELECT STATEMENT       |             |     1 |    10 |    22 |
|   1 |  SORT AGGREGATE        |             |     1 |    10 |       |
|*  2 |   HASH JOIN            |             | 23835 |   232K|    22 |
|   3 |    TABLE ACCESS FULL   | A           | 23835 |   116K|    15 |
|   4 |    INDEX FAST FULL SCAN| PKB         | 23836 |   116K|     3 |
----------------------------------------------------------------------

which is correct.
Of course 15 is better than 22, then the CBO goes incorrectly for nested loop.

But hash join is better, as demonstrated here:

set autotrace on

select /*+ use_hash(A B) */ count(1) from A, B where A.OBJECT_ID = B.OBJECT_ID;

----------------------------
          0  recursive calls
          0  db block gets
        385  consistent gets
          0  physical reads
...


select count(1) from A, B where A.OBJECT_ID = B.OBJECT_ID;

----------------------------
          0  recursive calls
          0  db block gets
      24168  consistent gets
          0  physical reads
...




The CBO makes a better guess when the primary key constraint on B is dropped and replaced with a 
simple index:


alter table B drop constraint PKB;

create index IXB on B (OBJECT_ID);

execute dbms_stats.gather_table_stats(OwnName => user, TabName => 'B', Cascade => true);

explain plan for select count(1) from A, B where A.OBJECT_ID = B.OBJECT_ID;

----------------------------------------------------------------------
| Id  | Operation              |  Name       | Rows  | Bytes | Cost  |
----------------------------------------------------------------------
|   0 | SELECT STATEMENT       |             |     1 |    10 |    23 |
|   1 |  SORT AGGREGATE        |             |     1 |    10 |       |
|*  2 |   HASH JOIN            |             | 23835 |   232K|    23 |
|   3 |    TABLE ACCESS FULL   | A           | 23835 |   116K|    15 |
|   4 |    INDEX FAST FULL SCAN| IXB         | 23836 |   116K|     4 |
----------------------------------------------------------------------

explain plan for select /*+ use_nl(A B) */ count(1) from A, B where A.OBJECT_ID = B.OBJECT_ID;

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |     1 |    10 | 23850 |
|   1 |  SORT AGGREGATE      |             |     1 |    10 |       |
|   2 |   NESTED LOOPS       |             | 23835 |   232K| 23850 |
|   3 |    TABLE ACCESS FULL | A           | 23835 |   116K|    15 |
|*  4 |    INDEX RANGE SCAN  | IXB         |     1 |     5 |     1 |
--------------------------------------------------------------------



As a final note:

> show parameters optimizer

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------
optimizer_dynamic_sampling           integer     1
optimizer_features_enable            string      9.2.0
optimizer_index_caching              integer     0
optimizer_index_cost_adj             integer     100
optimizer_max_permutations           integer     2000
optimizer_mode                       string      CHOOSE

> select * from V$VERSION;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.6.0 - 64bit Production
PL/SQL Release 9.2.0.6.0 - Production
CORE    9.2.0.6.0       Production
TNS for Solaris: Version 9.2.0.6.0 - Production
NLSRTL Version 9.2.0.6.0 - Production
 


Followup   July 29, 2005 - 5pm Central time zone:

all I can say is 10g got it right from the get go.  

Could be that it is not very widespread as the query, if made to touch the table, does the right 
thing.  Not having to hit B in this case makes for an 'unusual' query. 

5 stars   July 30, 2005 - 5am Central time zone
Reviewer: Alberto Dell'Era from Milan, Italy
Another very simple workaround to Marco's problem is to create the PK as DEFERRABLE:

alter table B add constraint PKB primary key (OBJECT_ID) deferrable;

----------------------------------------------------------------------
| Id  | Operation              |  Name       | Rows  | Bytes | Cost  |
----------------------------------------------------------------------
|   0 | SELECT STATEMENT       |             |       |       |    68 |
|   1 |  SORT AGGREGATE        |             |     1 |    10 |       |
|*  2 |   HASH JOIN            |             | 35940 |   350K|    68 |
|   3 |    TABLE ACCESS FULL   | A           | 35940 |   175K|    51 |
|   4 |    INDEX FAST FULL SCAN| PKB         | 35941 |   175K|     9 |
---------------------------------------------------------------------- 


3 stars CBO still wrong   August 2, 2005 - 5pm Central time zone
Reviewer: Marco Coletti from Italy
Tom:

The Oracle Metalink analist told me that he observed the same wrong behaviour (i.e. zero cost on 
the last step) in 9.2.0.6 and 10.0.1.4 with the following query:
select count(*) from emp,dept where emp.deptno=dept.deptno
the only relevant difference being that explain plan shows cost "0" for 10.0.1.4 against empty cost 
for 9.2.0.6.


I don't agree that the query is "unusual" since we have coded many statements like the following:

  delete (
    select 0
    from A, B
    where A.ID = B.ID
    );

Here the goal is to delete all "old" rows from A, where "old" rows are by definition those listed 
in B, and of course there is a primary key B(ID).
It must be done online, that is concurrently with a process that does insert/update on A.


Touching table B makes the CBO account for the cost of "TABLE ACCESS BY INDEX ROWID", but the cost 
of "INDEX UNIQUE SCAN" is still zero:

explain plan for select /*+ use_nl(A B) */ B.OBJECT_TYPE from A, B where A.OBJECT_ID = B.OBJECT_ID;

----------------------------------------------------------------------------
| Id  | Operation                    |  Name       | Rows  | Bytes | Cost  |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |             | 23837 |   442K| 23852 |
|   1 |  NESTED LOOPS                |             | 23837 |   442K| 23852 |
|   2 |   TABLE ACCESS FULL          | A           | 23837 |   116K|    15 |
|   3 |   TABLE ACCESS BY INDEX ROWID| B           |     1 |    14 |     1 |
|*  4 |    INDEX UNIQUE SCAN         | PKB         |     1 |       |       |
----------------------------------------------------------------------------

The correct total cost for above should be about
15 + 23837*(1+1) = 47689


----

Alberto Dell'Era suggested to create constraint PKB as deferrable, and indeed the CBO gets it right 
-->

alter table B drop constraint PKB;
alter table B add constraint PKB primary key (OBJECT_ID) deferrable;
execute dbms_stats.gather_table_stats(OwnName => user, TabName => 'B', Cascade => true);

explain plan for select /*+ use_nl(A B) */ 1 from A, B where A.OBJECT_ID = B.OBJECT_ID;

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             | 23837 |   232K| 23852 |
|   1 |  NESTED LOOPS        |             | 23837 |   232K| 23852 |
|   2 |   TABLE ACCESS FULL  | A           | 23837 |   116K|    15 |
|*  3 |   INDEX RANGE SCAN   | PKB         |     1 |     5 |     1 |
--------------------------------------------------------------------

The "deferrable" state makes the CBO access the index by RANGE instead of UNIQUE because:

select UNIQUENESS from USER_INDEXES where INDEX_NAME = 'PKB';

UNIQUENES
---------
NONUNIQUE


At this point, it would seem that the problem is: cost for "INDEX UNIQUE SCAN" calculated as zero.
Not entirely correct, as we can see -->

Dropping the deferrable constraint leaves behind a nonunique index that is reused when the 
constraint is created again in non deferrable state.

alter table B drop constraint PKB;
alter table B add constraint PKB primary key (OBJECT_ID);

select UNIQUENESS from USER_INDEXES where INDEX_NAME = 'PKB';

UNIQUENES
---------
NONUNIQUE

execute dbms_stats.gather_table_stats(OwnName => user, TabName => 'B', Cascade => true);

The nonunique index must be accessed by RANGE, but the CBO still assumes zero for the cost:

explain plan for select /*+ use_nl(A B) */ 1 from A, B where A.OBJECT_ID = B.OBJECT_ID;

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             | 23837 |   232K|    15 |
|   1 |  NESTED LOOPS        |             | 23837 |   232K|    15 |
|   2 |   TABLE ACCESS FULL  | A           | 23837 |   116K|    15 |
|*  3 |   INDEX RANGE SCAN   | PKB         |     1 |     5 |       |
--------------------------------------------------------------------


What about no constraint and unique index? -->

alter table B drop constraint PKB;
drop index PKB;
create unique index UXB on B (OBJECT_ID);
execute dbms_stats.gather_table_stats(OwnName => user, TabName => 'B', Cascade => true);
explain plan for select /*+ use_nl(A B) */ 1 from A, B where A.OBJECT_ID = B.OBJECT_ID;

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             | 23837 |   232K|    15 |
|   1 |  NESTED LOOPS        |             | 23837 |   232K|    15 |
|   2 |   TABLE ACCESS FULL  | A           | 23837 |   116K|    15 |
|*  3 |   INDEX UNIQUE SCAN  | UXB         |     1 |     5 |       |
--------------------------------------------------------------------
WRONG!


What about a unique constraint and nonunique index? -->

drop index UXB;
create index IXB on B (OBJECT_ID);
alter table B add constraint UB unique (OBJECT_ID);
execute dbms_stats.gather_table_stats(OwnName => user, TabName => 'B', Cascade => true);
explain plan for select /*+ use_nl(A B) */ 1 from A, B where A.OBJECT_ID = B.OBJECT_ID;


--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             | 23837 |   232K|    15 |
|   1 |  NESTED LOOPS        |             | 23837 |   232K|    15 |
|   2 |   TABLE ACCESS FULL  | A           | 23837 |   116K|    15 |
|*  3 |   INDEX RANGE SCAN   | IXB         |     1 |     5 |       |
--------------------------------------------------------------------
WRONG!


What if the unique constraint is deferrable? -->

alter table B drop constraint UB;
alter table B add constraint UB unique (OBJECT_ID) deferrable;
explain plan for select /*+ use_nl(A B) */ 1 from A, B where A.OBJECT_ID = B.OBJECT_ID;

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             | 23837 |   232K| 23852 |
|   1 |  NESTED LOOPS        |             | 23837 |   232K| 23852 |
|   2 |   TABLE ACCESS FULL  | A           | 23837 |   116K|    15 |
|*  3 |   INDEX RANGE SCAN   | IXB         |     1 |     5 |     1 |
--------------------------------------------------------------------
RIGHT!


The problem seems related to "non deferrable uniqueness".
To sum up:
CBO assumes zero cost for accessing an index (by UNIQUE SCAN or RANGE SCAN) when uniqueness on the 
same column is enforced (by non-deferrable pk/unique constraint or unique index).
 


3 stars Help required   August 24, 2005 - 8am Central time zone
Reviewer: Anil Pant 
First of all sorry as I could not format the explain plan output properly.
Help me in finding out why CBBILL_SUMMARY table is not using index. 

Im referring to this line TABLE ACCESS FULL    CB3DBA    CBBILL_SUMMARY    385068    15184955    
106294685
in the explain plan.

I've verified the indexes and all the statistics are updated.


UPDATE CBMEMBER A 
SET (MEM_NEXT_BILL_DATE, MEM_LAST_BILL_DATE,   
    MEM_INITIALBILL_DATE) = 
    (SELECT TRUNC(BRMBLRL_RUN_DATE), 
        ADD_MONTHS(TRUNC(BRMBLRL_RUN_DATE), -1), 
        ADD_MONTHS(TRUNC(BRMBLRL_RUN_DATE), -1)
     FROM CBBRAND_MBILL_RULE X 
     WHERE A.BR_ID = X.BR_ID),
     MEM_MODIFIED_DATE = SYSDATE,
     MEM_MODIFIER_ID   = 99904
WHERE MEMSTA_ID = 1 AND
      MEM_ID IN
    (
         SELECT MEM_ID 
         FROM CBMEMBER MEM,   
              CBBRAND_MBILL_RULE MBR 
         WHERE MBR.BILLING_CYCLE = 2 AND 
               MBR.MULTIJOB IS NOT NULL AND 
              --MEM.MEMSTA_ID = 1 AND 
               MEM.BR_ID = MBR.BR_ID AND
          EXISTS
          (
          SELECT 1 
                FROM CBBILL_SUMMARY BLS 
                WHERE BLS.MEM_ID = MEM.MEM_ID AND
                      BLTYP_ID = 3 AND BLS.BLSSTA_ID != 4
          )
    )


UPDATE STATEMENT, GOAL = CHOOSE            411255    835212    33408480
 UPDATE    CB3DBA    CBMEMBER            
  HASH JOIN            411255    835212    33408480
   VIEW    SYS    VW_NSO_1    391489    835212    10857756
    SORT UNIQUE            391489    835212    20045088
     HASH JOIN            387411    835212    20045088
      NESTED LOOPS            1639    274653    4669101
       TABLE ACCESS FULL    CB3DBA    CBBRAND_MBILL_RULE    1    26    234
       INDEX RANGE SCAN    CB3DBA    MEM_BR_UK    63    10564    84512
      TABLE ACCESS FULL    CB3DBA    CBBILL_SUMMARY    385068    15184955    106294685
   TABLE ACCESS FULL    CB3DBA    CBMEMBER    23130    1018328    27494856
  TABLE ACCESS BY INDEX ROWID    CB3DBA    CBBRAND_MBILL_RULE    1    1    5
   INDEX UNIQUE SCAN    CB3DBA    BR_MBILL_RULE_UNQ        79     


Followup   August 24, 2005 - 2pm Central time zone:

indexes do not mean "fast=true", perhaps the index is not the way to go.

How do you know the query portition is "slow" here, perhaps it is the update itself.

trace it, see what is taking long.

 

4 stars   August 26, 2005 - 12pm Central time zone
Reviewer: A reader 
Thanks - Marco Coletti  for letting us know about this. 


4 stars CBO and number of disks   September 28, 2005 - 8am Central time zone
Reviewer: Sven 
Hi Tom,

Will the CBO when calculating the cost for the query take into consideration (make the assumption) 
about how many disks are available to execute a query plan?

Thanks,

Sven
 


Followup   September 28, 2005 - 10am Central time zone:

no, not really - it takes into consideration the single block and multiblock IO historical times if 
available - but the number of "disks" (a disk isn't a disk anymore), no. 

5 stars What do you think about this ? Your opinion   October 4, 2005 - 4pm Central time zone
Reviewer: A reader 
Tom,

We have a lot stuff liket his...does it look good to you?
any work around??

 SELECT count(victoria)
     INTO v_victor
    FROM visitation
   WHERE trunc(l_in_date) BETWEEN NVL(l_s_date,l_e_date) 
     AND NVL(l_e_date,l_s_date)
     AND t_date IS NOT NULL; 


Followup   October 4, 2005 - 8pm Central time zone:

I've no idea what are columns and what are variables.  If l_ things are variables.....  I'd say

well, regardless - I'd ask "WHY ARE YOU COUNTING" - what is the point behind counting -- 
999999999999 times out of 1000000000000 you don't need to do it (if you are counting to see if 
there is something to process- why not JUST TRY TO PROCESS IT and then discover "nothing there" 
rather then hitting everything to be processed?? 

5 stars yes l's are variables...   October 5, 2005 - 9am Central time zone
Reviewer: A reader 


Followup   October 5, 2005 - 11am Central time zone:

so, why are you counting in the first place ?!?!?


 SELECT count(victoria)
     INTO v_victor
    FROM visitation
   WHERE trunc(l_in_date) BETWEEN NVL(l_s_date,l_e_date) 
     AND NVL(l_e_date,l_s_date)
     AND t_date IS NOT NULL; 


if trunc(l_in_date) BETWEEN NVL(l_s_date,l_e_date) 
     AND NVL(l_e_date,l_s_date)
then
    SELECT count(victoria)
     INTO v_victor
    FROM visitation
   WHERE t_date IS NOT NULL; 
else
   v_victor := 0;
end if


is a bad way to do it...  good way is:


<this space intentionally left blank>


counting, almost always a waste of resources. 

5 stars taking your suggesting!!!   October 5, 2005 - 11am Central time zone
Reviewer: A reader 
It will be left 


5 stars Parse   October 19, 2005 - 7pm Central time zone
Reviewer: Aru from nz
Hi Tom,

I have a certain query that has the following information in OEM about 

SELECT DISTINCT t1.currenttimespentminutes, t1.totaltimespentminutes
    FROM timespent t1, service_request t2, parent_child_links t2mm
    WHERE t1.dbid = t2mm.child_dbid (+)
      AND 16782564 = t2mm.child_fielddef_id (+)
      AND t2mm.parent_dbid = t2.dbid (+)
      AND t1.dbid <> 0
      AND t2.id = 'PROD00095465';
      
      
 9  SELECT STATEMENT 
 8  SORT [UNIQUE] 
 7  FILTER 
 6  HASH JOIN [OUTER] 
 4  NESTED LOOPS 
 2  CQ_PROD.SERVICE_REQUEST TABLE ACCESS [BY INDEX ROWID] 
 1  CQ_PROD.SERVICE_REQUEST_UK INDEX [UNIQUE SCAN] 
 3  CQ_PROD.TIMESPENT TABLE ACCESS [FULL] 
 5  CQ_PROD.PARENT_CHILD_LINKS TABLE ACCESS [FULL] 


Disk reads per execution                        2666
Buffer Gets per execution            4042
executions                    122
buffer gets per row                4076
sorts                        125
rows processed                     124
parse Calls per execution            1.00
parse calls                     122
loads                        1
Cpu time                     17909000
Elapsed time                    27450000


What I am struggling with is the information 'parse Calls per execution' 
and 'parse calls' being 1.00 and 126 respectively. Does this mean that 
the query was "hard" parsed 122 times? Also the 1.00 parse calls per 
execution is indicative that it was parsed each time it was executed.
Is that hard parse or cold parse? 
PLease please clarify Tom,
Regards,
Aru. 


Followup   October 19, 2005 - 7pm Central time zone:

that means the query was parsed 122 times.

probably 1 hard parse and 121 softparses.

It basically means you always parse it, execute it once and never again in your program.


 

3 stars Puzzled timings of similar query...   October 21, 2005 - 7am Central time zone
Reviewer: Shailesh Saraff from India
Hello Tom,

We have tried one exercise and would like to share with you. Similar query has given two different 
elapsed time. Please check WHERE clause (small changes in expression strings etc.), difference in 
elapsed time is huge. 

What is NESTED LOOPS (SEMI) and NESTED LOOPS (ANTI)? Can you please us explain why timings are so 
different. (Buffer was flushed before each query execution). First query 117 seconds and Second 
Query takes 31.90 seconds. 

Thanks & Regards, 

Shailesh 

SELECT Distinct TblFall.FallId, TblPatient.PatientenNr, TblPatient.Name, TblPatient.Vorname
  FROM TblPatient, TblFall, TblBewegung 
 WHERE (TblFall.Fallart IN ('ambulant')) 
   AND TblFall.FallStartDatum >= TO_DATE('01.04.2005','DD.MM.YYYY') 
   AND TblFall.FallStartDatum <= TO_DATE('05.10.2005','DD.MM.YYYY') 
   AND (TblBewegung.PflegerischeOE IN ('AN')) 
   AND TblPatient.PatientenId = TblFall.PatientenId 
   And TblPatient.Stornierer IS NULL 
   AND TblFall.Stornierer IS NULL 
   And TblFall.FallId >= 0 
   AND TblFall.Einrichtung = '1' 
   AND TblFall.FallId = TblBewegung.FallId 
   AND TblBewegung.Stornierer IS NULL 
   AND TblPatient.PatientenId = TblFall.PatientenId 
   And TblFall.Stornierer IS NULL 
   And TblFall.FallId >= 0 
   AND TblFall.Einrichtung = '1' 
   AND EXISTS (SELECT TblSchein.FallId 
                 FROM TblSchein 
                WHERE TblFall.FallId = TblSchein.FallId 
                  AND (TblSchein.ScheinTyp IN ('NtfallSchein')) 
                  AND TblSchein.GueltigVon >= TO_DATE('01.04.2005','DD.MM.YYYY') 
                  AND TblSchein.GueltigBis <= TO_DATE('05.10.2005','DD.MM.YYYY') 
                  AND TblSchein.Stornierungszeit IS NULL) 
   AND NOT EXISTS (SELECT TblLMLeistAbrechng.BewegungsId 
                     FROM TblLMLeistAbrechng 
                    WHERE TblBewegung.BewegungsId = TblLMLeistAbrechng.BewegungsId 
                      AND TblLMLeistAbrechng.Stornierer Is NULL 
                      AND NVL(TblLMLeistAbrechng.AbrechenbarITB, 'N') = 'J')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.21       0.21          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      1.14     116.85       3414      80901          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      1.35     117.06       3414      80901          0           0

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   MODE: ALL_ROWS
      0   SORT (UNIQUE)
      0    NESTED LOOPS (SEMI)
      0     NESTED LOOPS
      0      NESTED LOOPS (ANTI)
      0       NESTED LOOPS
  19664        TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF 
                   'TBLFALL' (TABLE)
  30977         INDEX   MODE: ANALYZED (RANGE SCAN) OF 'XIE7TBLFALL' 
                    (INDEX)
      0        TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF 
                   'TBLBEWEGUNG' (TABLE)
  38492         INDEX   MODE: ANALYZED (RANGE SCAN) OF 
                    'XIE1TBLBEWEGUNG' (INDEX)
      0       TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF 
                  'TBLLMLEISTABRECHNG' (TABLE)
      0        INDEX   MODE: ANALYZED (RANGE SCAN) OF 
                   'XIE1TBLLMLEISTABRE' (INDEX)
      0      TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF 
                 'TBLPATIENT' (TABLE)
      0       INDEX   MODE: ANALYZED (UNIQUE SCAN) OF 'XPKPATIENT2' 
                  (INDEX (UNIQUE))
      0     TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF 
                'TBLSCHEIN' (TABLE)
      0      INDEX   MODE: ANALYZED (RANGE SCAN) OF 'XIE1TBLSCHEIN' 
                 (INDEX)

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

SELECT Distinct TblFall.FallId, TblPatient.PatientenNr, TblPatient.Name, TblPatient.Vorname
  FROM TblPatient, TblFall, TblBewegung 
 WHERE TblFall.Fallart IN ('ambulant') 
   AND TO_DATE('01.04.2005', 'DD.MM.YYYY') <= TblFall.FallStartDatum 
   AND TO_DATE('05.10.2005', 'DD.MM.YYYY') >= TblFall.FallStartDatum 
   AND TblBewegung.PflegerischeOE IN ('AN') 
   AND TblFall.PatientenId = TblPatient.PatientenId 
   AND TblPatient.Stornierer IS NULL 
   AND TblFall.Stornierer IS NULL 
   AND 0 <= TblFall.FallId 
   AND '1' = TblFall.Einrichtung 
   AND TblBewegung.FallId = TblFall.FallId 
   AND TblBewegung.Stornierer IS NULL 
   AND NOT EXISTS (SELECT TblLMLeistAbrechng.BewegungsId 
                     FROM TblLMLeistAbrechng 
                    WHERE TblLMLeistAbrechng.BewegungsId = TblBewegung.BewegungsId 
                      AND TblLMLeistAbrechng.Stornierer Is NULL 
                      AND 'J' = NVL(TblLMLeistAbrechng.AbrechenbarITB, 'N')) 
   AND TblBewegung.FallId >= 0 
   AND EXISTS (SELECT TblSchein.FallId 
                 FROM TblSchein 
                WHERE TblSchein.FallId = TblBewegung.FallId 
                  AND TblSchein.ScheinTyp IN ('NtfallSchein') 
                  AND TO_DATE('01.04.2005', 'DD.MM.YYYY') <= TblSchein.GueltigVon 
                  AND TO_DATE('05.10.2005', 'DD.MM.YYYY') >= TblSchein.GueltigBis 
                  AND TblSchein.Stornierungszeit IS NULL)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.84      31.89       3415      80901          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.85      31.90       3415      80901          0           0

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   MODE: ALL_ROWS
      0   SORT (UNIQUE)
      0    NESTED LOOPS (ANTI)
      0     NESTED LOOPS
      0      NESTED LOOPS (SEMI)
      0       NESTED LOOPS
  19664        TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF 
                   'TBLFALL' (TABLE)
  30977         INDEX   MODE: ANALYZED (RANGE SCAN) OF 'XIE7TBLFALL' 
                    (INDEX)
      0        TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF 
                   'TBLBEWEGUNG' (TABLE)
  38492         INDEX   MODE: ANALYZED (RANGE SCAN) OF 
                    'XIE1TBLBEWEGUNG' (INDEX)
      0       TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF 
                  'TBLSCHEIN' (TABLE)
      0        INDEX   MODE: ANALYZED (RANGE SCAN) OF 'XIE1TBLSCHEIN' 
                   (INDEX)
      0      TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF 
                 'TBLPATIENT' (TABLE)
      0       INDEX   MODE: ANALYZED (UNIQUE SCAN) OF 'XPKPATIENT2' 
                  (INDEX (UNIQUE))
      0     TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF 
                'TBLLMLEISTABRECHNG' (TABLE)
      0      INDEX   MODE: ANALYZED (RANGE SCAN) OF 
                 'XIE1TBLLMLEISTABRE' (INDEX)

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


Followup   October 21, 2005 - 8am Central time zone:

I'd be curious as to see the wait events for this - they did the same work it looks like - but one 
of them just had to wait longer (eg: it could be a fluke)



that is however, a lot of work to find zero rows don't you think. 

5 stars why toad shows correct plan and sqlplus doesnt   October 25, 2005 - 5pm Central time zone
Reviewer: A reader 
Hi

I am using Oracle 8.1.7.4 on HP-UX.

I am tunign a query with no bind variables when I do set autotrace on I see a plan totally 
different than that plan I see in TOAD

The plan in TOAD is correct but autotrace shows the wrong one (I tkprofed).

How so? Where does TOAD get the plan from? Why SQLPLUS gives wrong plan? 


Followup   October 26, 2005 - 11am Central time zone:

sqlplus uses explain plan.

do this, run the tkprof with explain=user/pass so we get

a) the actual plan
b) the explain plan plan

and show it to us.

you would have to ask the makers of toad where they get the plan from. 

4 stars explain plan   November 10, 2005 - 6am Central time zone
Reviewer: Parag J Patankar from India
Hi Tom,

In Oracle 9i, I am running a SQL query which is taking 2 hours to run. I just want to see execution 
plan but it is when I am doing "traceonly explain" it also taking very long time ( ultimately I 
cancelled this ). Is it normal ? how can I see only execution plan fast without waiting for so much 
of time.

Secondly my exeuction plan breaks down in two lines I want to appear in one line ( line width 
should be 132 chars ) how can I do that ?

regards & thanks
pjp 


Followup   November 11, 2005 - 11am Central time zone:

if the query is running, the plan would already be in v$sql_PLAN, you can get it from there.


You can also:

explain plan for select .....;
select * from table(dbms_xplan.display);



if the explain plan call takes long - it is a problem with parsing the sql statement itself. 

4 stars Explain plan output   November 12, 2005 - 1am Central time zone
Reviewer: Parag J Patankar from India
Hi Tom,

Thanks for your answer. Will you also answer my question of how to set up Explain plan output for 
linesize 100.

regards & thanks
pjp 


Followup   November 12, 2005 - 10am Central time zone:

set linesize.


ops$tkyte@ORA9IR2> set linesize 20
ops$tkyte@ORA9IR2> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------

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

| Id  | Operation
         |  Name
   | Rows  | Bytes |
 Cost  |

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

|   0 | SELECT STATE
MENT     |
   |       |       |
       |

|   1 |  TABLE ACCES
S FULL   | DUAL
   |       |       |
       |

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


Note: rule based opt
imization


9 rows selected.

ops$tkyte@ORA9IR2> set linesize 100
ops$tkyte@ORA9IR2> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------


--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |       |       |       |
|   1 |  TABLE ACCESS FULL   | DUAL        |       |       |       |
--------------------------------------------------------------------

Note: rule based optimization

9 rows selected.
 

5 stars this is cool stuff, may I have more please...   November 14, 2005 - 4pm Central time zone
Reviewer: Kevin Meade from Connecticut USA
Nice thread

To go one step further with this, while tuning sql I have often wanted to associate a sql statement 
in the cursor cache with the piece of code it comes from.  Particularly, I am currently trying to 
match sql from the cursor cache with the specific packaged procedure/function from which it 
eminates.

I know v$open_cursor shows open sql in the cursor cache.  I know v$session shjows the user and sid. 
 I know v$process shows OS info and program name.  But I can't find any likage back to the source 
code.

Is there a way to map sql in the cursor cache to the packaged procedure/function that owns it?

I know your time is valuable and limited so I have tried to find an answer elsewhere.  I have 
searched the net, Oracle Metalink, the other suggested resources your sites offers, and here, to no 
avail.

Thanks, Kevin 


Followup   November 15, 2005 - 7am Central time zone:

nope, it is NOT a one to one function.

1 or 1,000 programs may issue:

select * from emp;




What I like to see is the liberal use of dbms_application_info!

the action and module you can set (like HTMLDB does) is associated with the SQL in v$SQL 

4 stars Tuning revisited in context of a unique index   November 14, 2005 - 5pm Central time zone
Reviewer: Devopam Mittra from Santa Clara,CA USA
Hi Tom,
I have a typical situation :
Table Tab1 has columns A,B,C and D.
Uniqueness of a record is defined as a combination of A AND(B OR C) i.e. 
either of values B or C (or both).
Simply defined a Unique index on A,B,C and it works fine, as per logic.
Business logic wise, there can't exist a record with both B and C NULL at the same time.
But the data loading process is becoming slower by the day as the table size is growing.

Would it be beneficial if I define a pseudo column E (B||'-'||C) and define a primary key on A and 
E instead of Unique index being maintained.

The data querying modules run fine and pick up the correct path as well. So not a problem there.
regards
Devopam 


Followup   November 15, 2005 - 7am Central time zone:

You have an atypical situation (almost every time someone starts with "I have a typical situation" 
- it turns out to be something atypical!)


the size of the table should not affect load times - an insert into a 50 billion row table should 
take about as long as into an empty table.


A primary key will have an index associated with it - unique or non-unique - but there WILL be an 
index.


tell us why you believe the "size" is affecting the load? 

5 stars If there is no one to one correspondance then I don't undertand this   November 15, 2005 - 5pm Central time zone
Reviewer: Kevin Meade from Connecticut, USA
Maybe I am interpreting things wrong but this example tells me there is indeed a one to one 
correspondence of open cursors to programs executing sql...

create or replace procedure p1 is
   v1 number;
begin
dbms_application_info.set_client_info('start');
   select 1 into v1 from dual;
   select 1 into v1 from dual;
dbms_lock.sleep(30);
dbms_application_info.set_client_info('done');
end;
/

execute p1

-- jump over to second sqlplus session to watch

KEVIN@>select * from v$open_cursor where sid in (select sid from v$session where client_info is not 
null);


SADDR           SID USER_NAME                      ADDRESS  HASH_VALUE
-------- ---------- ------------------------------ -------- ----------
SADDR           SID USER_NAME                      ADDRESS
-------- ---------- ------------------------------ --------
HASH_VALUE
----------
SQL_TEXT
------------------------------------------------------------
432EBEAC         17 KEVIN                          4CDBFCD0
3029709661
BEGIN p1; END;

432EBEAC         17 KEVIN                          4CDB5AA0
3876449241
SELECT 1 FROM DUAL

432EBEAC         17 KEVIN                          4CDB5AA0
3876449241
SELECT 1 FROM DUAL


3 rows selected.

KEVIN@>desc v$open_cursor
 Name                          Null?    Type
 ----------------------------- -------- -----------------
 SADDR                                  RAW(4)
 SID                                    NUMBER
 USER_NAME                              VARCHAR2(30)
 ADDRESS                                RAW(4)
 HASH_VALUE                             NUMBER
 SQL_TEXT                               VARCHAR2(60)


Oracle has clearly cached one statement with one plan but, I see two entries in v$open_cursor 
because this process opens the same statement twice.  This seems to me to be a one to one 
correspondance between the code and the v$open_cursor table.  Since in executing each sql 
statement, Oracle had to get it from where ever it lives, I was hoping it had this information 
buried somewhere (maybe in a not yet exposed v$... or available via some utility package I don't 
know yet, or gettable with some x$magic) and you could tell me how to dig it out.  I suppose if 
oracle just doesn't keep this info around during execution then I am out of luck.

As for lots of DBMS_APPLICATION_INFO, I agree, but the reality is as a contractor, I move to places 
that don't do it and so what am I to do?  Tell them, "go back to all your pl/sql code, put in these 
statements then spend time retesting it all, and then I can get you some cool documentation"...  I 
can hear their response already... "MIster, you were supposed to be an expert at this.  If you 
can't get what we want without requiring us to go through hoops well, guess we will have to find 
someone else".  Dealing with after-the-fact dirty systems is just the way it is.  Which is why I 
rely so heavily on Oracle to make me look good.

Thanks, I'm done.  I'll try to get it some other way.

Kevin 


Followup   November 16, 2005 - 8am Central time zone:

you cannot use plsql to test this particular thing because plsql is the *best* environment for 
doing sql and very smartly caches things.

write a program using another language and you'll find that if you open/parse/close a cursor here 
and there, you'll have - well, zero open cursors.... but you'll have the same "cursor" pointed to 
in v$sql


in v$sql, there is only one (even from plsql).  so in v$sql, it is not possible to say "this sql is 
executed from this line of code in this program"


There is not any facility in general to do what you want - for in general it is not doable (same 
sql, many programs) (we have no clue what line of code you are in in your java/vb/c/etc program)

 

4 stars Re:Tuning revisited in context of a unique index   November 15, 2005 - 7pm Central time zone
Reviewer: Devopam Mittra from Santa Clara,CA USA
Hi Tom,
Thanks for the enlightment :-) about typicality of a problem.

If we have high volume of data in a table where a unique/pk index is preserving the business logic 
for uniqueness of a record, my understanding is that it should take higher processing 
time/resources to evaluate if a new record can be inserted or updated.
High volume : 40 mil rec - 200 mil rec. 
Index spans across 5 columns of datatype VARCHAR,DATE and NUMBER.

Please advise further.
regards
Devopam 


Followup   November 16, 2005 - 8am Central time zone:

right, it will take time to verify that the data is unique (obviously) however, it won't take 
measurably more time with 5,000,000,000,000,000 rows in the table than 5.

You said:

But the data loading process is becoming slower by the day as the table size is 
growing.


I don't see the connection between this unique constraint and increasing load times. 

3 stars explain plan output in one line   November 16, 2005 - 1am Central time zone
Reviewer: Parag J Patankar from India
Hi Tom,

Sorry I might not be clear while explaining the question. I want explain output in one line when I 
setup "autotrace". For e.g.

if I do "autotrace" I am getting following output

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=9 Card=1 Bytes=5)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'X01DT31' (UNIQUE) (Cost=9 Car
          d=1 Bytes=5)

I want ouput like following

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=9 Card=1 Bytes=5)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'X01DT31' (UNIQUE) (Cost=9 Card=1 Bytes=5)

I set up linesize 9999 but no effect on output.

regards & thanks
pjp 


Followup   November 16, 2005 - 9am Central time zone:

from effective Oracle by design:


<quote>
You do have some amount of control over the formatting of this report. The defaults (found in 
$ORACLE_HOME/sqlplus/admin/glogin.sql) are as follows:

COLUMN id_plus_exp FORMAT 990 HEADING i
COLUMN parent_id_plus_exp FORMAT 990 HEADING p
COLUMN plan_plus_exp FORMAT a60
COLUMN object_node_plus_exp FORMAT a8
COLUMN other_tag_plus_exp FORMAT a29
COLUMN other_plus_exp FORMAT a44

The ID_PLUS_EXP and PARENT_ID_PLUS_EXP columns are the first two numbers you see in the EXPLAIN 
PLAN output above. The PLAN_PLUS_EXP column is perhaps the most important one. It is the textual 
description of the plan step itself; for example, TABLE ACCESS (FULL) OF ‘DEPT’ (Cost=2 Card=4 
Bytes=72). I find the default of 60 characters wide to be too small for most uses, so I set it to 
100 in my login.sql file.

The last three settings control the output information displayed for parallel query plans. The 
easiest way to see which columns they affect is to run a parallel query with SET AUTOTRACE 
TRACEONLY EXPLAIN and turn them off, one by one. YouÂ’ll clearly see what disappears from the 
report, so youÂ’ll know what they control.  

4 stars Re:Tuning revisited in context of a unique index   November 17, 2005 - 3am Central time zone
Reviewer: Devopam Mittra from Santa Clara,CA USA
Hi Tom,
Is the assumption fair with no performance hit against a table of 5 recs vs that big a number ?
Am not doubting the capabilities of Oracle but it should be a reasonable assumption that there will 
be a performance change.

Okay about my prob, there are usually 1 mil rec inserts per day in this table.
Of late the procedure is taking more time. (not a drastic change though)

So I was evaluating if keeping an index on a non-null column would help me instead of keeping a 
unique index with NULL recs ~few mil recs.

Please let me know if the line of approach is correct.
regards
Devopam

 


Followup   November 17, 2005 - 8am Central time zone:

why is it a reasonable assumption?  

What is the thoughts behind this assumption?


have you traced your application to see what is taking "long"? 

4 stars Re:Tuning revisited in context of a unique index   November 18, 2005 - 2am Central time zone
Reviewer: Devopam Mittra from Santa Clara,CA USA
Hi Tom,
Please find my responses as under.
why is it a reasonable assumption?  
Searching a 5 record table for uniqueness should not need an index in the first place itself.
A FTS will be way faster.


What is the thoughts behind this assumption?
With the table size the index size also grows. So now the query will be sampling more no of records 
to determine the uniqueness. 

have you traced your application to see what is taking "long"? 
There is an I/O contention issue during the data loading time (it is a scheduled activity) and that 
could be a potential problem

Please let me know if the assumptions made above make sense. 

I was trying to explore the option of a PK vs a Unique index (not a constraint) as a faster means 
of determining the uniqueness.

Requesting your opinion on the subject matter. Are these two types of indices going to perform the 
same way , then I guess I should explore the other aspects of the ETL process for timing 
improvement.

regards
Devopam 


Followup   November 18, 2005 - 10am Central time zone:

but oracle needs and will only enforce a unique constraint with an index.  so the theory of the fts 
does not fly.

And - for a 5 row table, it would be more efficient to use an index, not a fts.  In fact, for any 
sized table it would be more efficient to use the index, not a fts!


The time to find a unique key in an index is directly related to the height of the index.  You will 
find that you can have millions and millions and millions of rows and it'll still only take 3 or 4 
logical IO's to determine "unique or not"


a primary key is enforced via an index, don't try to "outsmart" anything here by using - well - AN 
INDEX, that is already happening.


IF your goal is to tune performance
THEN
   your first step must be to ascertain where your time is being spent
   and concentrate on tuning that.  Not on tuning hypothetical situations :)
END IF

 

4 stars explain plan interpretation   November 22, 2005 - 9am Central time zone
Reviewer: dhamodharan.l from India
hi tom the exlanation u gane is very useful for me please tell my how do solve using this explain 
plan                                             
--------------------------------------------------------------------------------
-                                                                               
                                                                                
| Id  | Operation                      |  Name          | Rows  | Bytes | Cost  
|                                                                               
                                                                                
--------------------------------------------------------------------------------
-                                                                               
                                                                                
|   0 | SELECT STATEMENT               |                |       |       |       

PLAN_TABLE_OUTPUT                                                               
--------------------------------------------------------------------------------
|                                                                               
                                                                                
|   1 |  NESTED LOOPS                  |                |       |       |       
|                                                                               
                                                                                
|   2 |   NESTED LOOPS                 |                |       |       |       
|                                                                               
                                                                                
|   3 |    NESTED LOOPS                |                |       |       |       
|                                                                               
                                                                                

PLAN_TABLE_OUTPUT                                                               
--------------------------------------------------------------------------------
|   4 |     TABLE ACCESS FULL          | PATREVISIT     |       |       |       
|                                                                               
                                                                                
|   5 |     TABLE ACCESS BY INDEX ROWID| CONSULTANT     |       |       |       
|                                                                               
                                                                                
|   6 |      INDEX UNIQUE SCAN         | PK_CONSULTANT  |       |       |       
|                                                                               
                                                                                
|   7 |    TABLE ACCESS FULL           | PATMLC         |       |       |       
|                                                                               

PLAN_TABLE_OUTPUT                                                               
--------------------------------------------------------------------------------
                                                                                
|   8 |   TABLE ACCESS FULL            | CONSULTANT     |       |       |       
|                                                                               
                                                                                
--------------------------------------------------------------------------------
-                                                                               
                                                                                
                                                                                
Note: rule based optimization, 'PLAN_TABLE' is old version                      

16 rows selected. 


Followup   November 22, 2005 - 10am Central time zone:

ctl-f for

Reading an Explain Plan

I've described how to read a plan.
 

5 stars Index Access for a table   November 25, 2005 - 9am Central time zone
Reviewer: Vivek Sharma from Bombay, India
Hi Tom,

I am one of your regular reader and appreciate your efforts. I have learned a lot from this website 
(educational). :)

I have one general query on the Columns used by an Index Access. My query is :

Suppose a query
----------------

select a.x, a.y, b.x, b.z, c.x
from a, b, c
where a.xy = b.xy
and   a.pq = c.pq
and   a.mn = 'some value'
and   b.op = some_value
and   c.dt = some_date;

I have a Composite Index on a(xy, pq) and my autotrace shows that A is my driving table and a 
nested loop of A and B are then joined again via nested loop of C.

My Query is that though the Index on A is on (xy, pq) but since column PQ is joined to a different 
table (C) and this step is just a Nested loop between A and B, will the query make use of both 
columns of an Index or just XY.

I think that since column PQ is joined to table C, the Index will only use Column XY as a joining 
condition between A and B and hence, PQ will be of no use.

Am I correct ? Please let me know.

Thanks and Regards
Vivek

 


Followup   November 25, 2005 - 10am Central time zone:

I cannot see the query using the index on a(xy,pq)  if A is in fact the driving table.  

I would see a full scan of A or a index on A(mn) being used.

 

5 stars Sorry Tom....this is in continuation of my previous post   November 25, 2005 - 11am Central time zone
Reviewer: Vivek Sharma from Bombay, India
Sorry Tom,

This is in continuation to my previous post.

There is no index on A(m,n)....though I understand that the Optimizer should do a FTS on A, but I 
have seen the CBO using an index on A(xy, pq)..may be because of the setting of 
optimizer_index_cost_adj and optimizer_index_caching. In this case, it was CBO but I have also seen 
this behaviour in RBO. So was just curious to know that whether it will only use XY column of A for 
the Scan.

Awaiting your response.

Regards
Vivek
 


Followup   November 25, 2005 - 1pm Central time zone:

not if A was the DRIVING table it wouldn't, you would need to set up a test case to show otherwise. 
 

If b/c were joined AND THEN that is joined to A, I could see the index but not otherwise. 

4 stars reader   November 29, 2005 - 3pm Central time zone
Reviewer: A reader 
Hi Tom

may i know how you have converted 


PLAN_TABLE_OUTPUT
-----------------------------------------------------------------
| Id  | Operation                    |Name     |Rows|Bytes|Cost |
-----------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |    |     |     |
|   1 |  NESTED LOOPS                |         |    |     |     |
|   2 |   NESTED LOOPS               |         |    |     |     |
|   3 |    TABLE ACCESS FULL         | SALGRADE|    |     |     |
|*  4 |    TABLE ACCESS FULL         | EMP     |    |     |     |
|   5 |   TABLE ACCESS BY INDEX ROWID| DEPT    |    |     |     |
|*  6 |    INDEX UNIQUE SCAN         | DEPT_PK |    |     |     |
-----------------------------------------------------------------

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

   4 - filter("EMP"."SAL"<="SALGRADE"."HISAL" AND 
              "EMP"."SAL">="SALGRADE"."LOSAL")
   6 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")

Note: rule based optimization

21 rows selected.





into 



For salgrade in (select * from salgrade) 
Loop
   For emp in ( select * from emp )
   Loop
      If ( emp.sal between salgrade.losal and salgrade.hisal )
      Then
          Select * into dept_rec
            From dept
           Where dept.deptno = emp.deptno;

          OUTPUT RECORD with fields from salgrade,emp,dept
      End if;
   End loop;
End loop; 





and then into this 


                  1
                               / \
                              2   5
                             / \   \
                            3   4   6

i have read How the CBO Chooses an Access Path in oracle performance tuning guide but i could not 
somehow understand that fully. 


Followup   November 30, 2005 - 11am Central time zone:

I did????  I mean that was the crux of the posting you cut this from - it goes through this step by 
step. 

1 stars reader   November 30, 2005 - 11am Central time zone
Reviewer: A reader 
Hi 

in the below graph you said that we move from left to right.first you wrote step 3(For salgrade in 
(select * from salgrade) ) then step 4(For emp in ( select * from emp )) and then step 2(nested 
loop) after then cant we do step 6 insted of step 5 as you have done in the pseudocode...will you 
be able to explain this whole pseudocode again with the help of this graph. i would be thankful to 
you ....

                1
                               / \
                              2   5
                             / \   \
                            3   4   6 


1 stars reader   December 10, 2005 - 1am Central time zone
Reviewer: A reader 
Hi Tom

Please could you answere this(I am novice in this matter,i have also read access paths but i 
somehow could not understand this):
In the below graph you said that we move from left to right.first you wrote 
step 3
(For salgrade in (select * from salgrade) ) then step 4(For emp in ( select * 
from emp )) 
and then 
step 2
(nested loop), then after that cant we do step 6 insted of 
step 5 as you have done in the pseudocode...WILL YOU BE ABLE TO EXPLAIN this 
whole pseudocode again with the help of this graph. I would be thankful to you ....

                                1
                               / \
                              2   5
                             / \   \
                            3   4   6 


 


Followup   December 10, 2005 - 5am Central time zone:

I cannot think of any other ways to say it other than the way I said it above where i went step by 
step by step by step through this.  sorry.  I thought I said it as simply as I can.  maybe someone 
else can try - but I cannot see any easier way to say it. 

4 stars tkprof script   December 26, 2005 - 2pm Central time zone
Reviewer: Reader from US
I scanned all (3) books but could not find the script which you use to read the tkprof during your 
presentations .

Where can I find it ?

Thanks 
 


Followup   December 26, 2005 - 2pm Central time zone:

I don't know what you mean?

tkprof is a command line tool provided with the database, it generates a report.  it is in 
$ORACLE_HOME/bin 

2 stars TKPROF   December 26, 2005 - 11pm Central time zone
Reviewer: reader from US
Well ,during your demos you execute a sql then you run a script which prints the tkprof output .(I 
assume you grab the pid the pass this to the tkprof ) 

I will try to explain in detail if I am not clear this time .

I was told that it is in the book but I could not find it .

Thanks 
 


Followup   December 27, 2005 - 9am Central time zone:

oh, all I'm doing is:

SQL> disconnect
SQL> !tkprof `ls -t $ORACLE_HOME/admin/$ORACLE_SID/udump/*ora_*.trc | head -1` ./tk.prf
SQL> edit tk.prf
SQL> connect /


in my scripts (or calling a small script tk, that does the same)

I just find the last trace file generated, and tkprof it.  works good on my single user demo 
system.


You can also using my getspid script (which is in the books)

ops$tkyte@ORA10GR2> l
  1  select a.spid dedicated_server,
  2        b.process clientpid
  3    from v$process a, v$session b
  4   where a.addr = b.paddr
  5*    and b.sid = (select sid from v$mystat where rownum=1)
ops$tkyte@ORA10GR2> /

DEDICATED_SE CLIENTPID
------------ ------------
2533         2520

ops$tkyte@ORA10GR2> !ls -ltr $ORACLE_HOME/admin/$ORACLE_SID/udump/*2533.trc
-rw-rw----  1 ora10gr2 ora10gr2 18690 Dec 27 10:29 
/home/ora10gr2/admin/ora10gr2/udump/ora10gr2_ora_2533.trc


it would be more appropriate on a multi-user system where many people might be generating traces.


 

5 stars THANK YOU !   December 27, 2005 - 7pm Central time zone
Reviewer: reader from US


4 stars your user   January 11, 2006 - 7am Central time zone
Reviewer: Reader from US
In the above script is your user identified externally .

Thanks ,

 


Followup   January 12, 2006 - 10am Central time zone:

yes?  is that a problem? 

3 stars Explain Plan   January 12, 2006 - 10am Central time zone
Reviewer: a from India
500000 rows created.

Elapsed: 00:08:01.12

Execution Plan
----------------------------------------------------------
   0      INSERT STATEMENT Optimizer=CHOOSE (Cost=1681 Card=500000 Byt
          es=103500000)

   1    0   LOAD AS SELECT
   2    1     HASH JOIN (Cost=1681 Card=500000 Bytes=103500000)
   3    2       TABLE ACCESS (FULL) OF 'LND_DIABETES_DERIVED' (Cost=2
          Card=500000 Bytes=10500000)

   4    2       TABLE ACCESS (FULL) OF 'LND_DIABETES_TRANS' (Cost=263
          Card=500000 Bytes=93000000)





Statistics
----------------------------------------------------------
       5000  recursive calls
     264003  db block gets
      26628  consistent gets
      16683  physical reads
  179047124  redo size
        791  bytes sent via SQL*Net to client
       5801  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
     500000  rows processed

Could you please let us know why 500000 records take 8 minutes? 


Followup   January 12, 2006 - 11am Central time zone:

no, not from this.  No more than you could.

I could think of many reasons, none of which might apply, or all of which might apply, or some of 
which might apply.

for example, you generated some 175mb of redo.  Likely you have many indexes on this table (guess 
based on redo generated)


you did 16,683 physical IO's, if each took 0.01 seconds, that would be almost 3 minutes - maybe IO 
is even slower on your system.

maybe the system was loaded and you had a hard time getting CPU time.

maybe your redo logs are way small and you suffered from lots of "checkpoint not complete, cannot 
allocate new log" messages.

maybe......


suggestion: trace with wait event recording enabled, use tkprof, see what you are waiting on. 

4 stars How to retrieve long running sql queries ran by a particular user at a past time period   January 13, 2006 - 2pm Central time zone
Reviewer: A reader from NJ, US
Tom,

How can I retrieve the query ( with complete sql_text) ran by a particular user during a past time 
window, say last night 8-9pm? 

Thanks, 


Followup   January 15, 2006 - 3pm Central time zone:

were you auditing what this user was doing last night between 8-9pm, if not, you won't be able to 
retrieve the query the user was running.  

If you were - it'll be in the fine grained auditing trail log. 

5 stars Detecting query plan changes for selected queries   January 16, 2006 - 8pm Central time zone
Reviewer: Menon 
Hi Tom
Recently, we ran into a problem wherein after changing a database parameter, one of the queries 
changed plan to use full table scan with pretty bad results. We have a tar open with Oracle on this 
issue (though they simply told us to use hints..) 

Anyways, when we ran the same query in test, we were able to see that the plan change was due to 
the db parameter change.
This led to a discussion on whether it makes sense to have a framework to detect changes in query 
plans in test system (which has the same configuration as the production system.) The idea is that 
whenever DBAs change a parameter, we have a way of identifying for queries that are in the critical 
path, if the execution path has changed or not. Such a system would have been usful at least in the 
above case where the plan change was not for the better. (Note that I am well aware that plans 
change (and change for the good - usually based on changing stats) and FTS in general is not bad 
and so on.)
Assume that we have identified a set of important queries (embedded in PL/SQL). We can even mark 
this queries with comments to identify them (instead of using SQL Hash created by Oracle which is 
overly sensitive to spaces and so forth for our purposes.

Does it make sense to create such a framework - is it worth it? On a cursory glance, I don't think 
it involves a lot of work (esp. if it is generic only for an app) but I am just wondering if it is 
a good idea in general or not. This would be a *very small* subset of the intentions of the hotsos 
"laredo" product 
https://portal.hotsos.com/products/laredo
Btw, if there are other such existing products (esp. free ones), let me know. 


Followup   January 16, 2006 - 9pm Central time zone:

the product I know of that does this is Laredo from hotsos.com

Yes, it does make sense. (and with the hash in v$sql_plan, it can be relatively easy to identify 
changed plans) 

5 stars thanx...   January 17, 2006 - 1am Central time zone
Reviewer: Menon 
"the product I know of that does this is Laredo from hotsos.com"

Yup - that is the one I also mentioned above...

"Yes, it does make sense. (and with the hash in v$sql_plan, it can be relatively easy to identify 
changed plans)"..

That is one idea though the problem is that anytime you edit the query,the hash changes... I was 
thinking more like marking the selected queries to identify them...

Thanx for your input! 


Followup   January 17, 2006 - 8am Central time zone:

if you edit the query - nothing much is going to be able to match them.

my point was "if you hold all things constant EXCEPT for something that affects the optimizer 
(gather stats, change parameters, perform a patch upgrade....) - the hash can be very useful for 
identifying changed plans" 

5 stars hmm..   January 17, 2006 - 1pm Central time zone
Reviewer: Menon 
"if you edit the query - nothing much is going to be able to match them."

But if you put a known string in the comment as a hint
(e.g. 
change select * from emp to select /*+ PLAN_CHANGE_MONITORED(Q1) */ * from emp, then you could 
track them regardless of hash value? Q1 - is the identifier for this particular query. The next 
query would be Q2 and so on. This would also document the queries whose plans are being monitored. 
You would still use v$sql_plan to detect plan changes. You would store the old plans in another 
table with query identifier being the string for each query (instead of the hash value). 


5 stars User user-function instead????   January 20, 2006 - 10am Central time zone
Reviewer: A reader 
Hi Tom, 
  I've got a sql as follows as part of a report query. 

select trx.ctry_orig_dest, c.ctry_name,  
   sum(decode(trx.ccy, 'EUR', trx.trxvalue, 
           decode(fx.invrate,
                    'N', trx.trxvalue * fx.fxrate, 
                     trx.trxvalue / fx.fxrate)) ) incoming_payments,
   trx.ccy
from acc_trxc trx, acc_mast acc, fxrates_arch fx,iso_countries c
where trx.trxtype = 'CASH RECD'
and trx.accode = acc.accode
and acc.country_code = 'DEU'
and trx.ctry_orig_dest = c.ctry_code
and trx.ctry_orig_dest <> 'DEU'
and fx.trdate between trunc(to_date(:end_date)) and trunc(to_date(:end_date)) + 1
and trx.ccy = fx.prm_ccy
and fx.sec_ccy = 'EUR'
group by trx.ctry_orig_dest, c.ctry_name, ccy
having sum(decode(trx.ccy, 'EUR', trx.trxvalue, 
                   decode(fx.invrate, 'N', trx.trxvalue * fx.fxrate, 
                   trx.trxvalue / fx.fxrate)) ) >= 12500 



  There are 10 such individual SELECTS, all joined together using a UNION ALL.  
   My query is regarding the sum(decode...) in the statement above.  As you can see, we are using 
it in both the select-ed list as well as for checking the group total is > 12500.  This summation 
is being done in all individual SELECTs. Is there any way I can just get the actual sum just once 
and then select-check it, so that the operation is not done twice? Any suggestions you can give me 
will be greatly appreciated.  By the way, the client is still on version 817 (!).  Thanks 


Followup   January 20, 2006 - 10am Central time zone:

are you sure the operation is done twice.  if so, "how" 

5 stars Reg. last query   January 20, 2006 - 11am Central time zone
Reviewer: A reader from UK
HI Tom,
  Thanks for the quick response. 
  Oops, I just assumed the sum(decode..) stuff would be done twice.  Isn't this the case?  How 
would I go about checking this?  
Thanks 


Followup   January 20, 2006 - 12pm Central time zone:

the optimizer will take whatever shortcuts it feels like taking for us. it may do it twice, it may 
not do it twice.  we cannot really control that (sql is somewhat non-procedural that way).  inline 
view won't do it.

but one would presume it would only do it once - since it is in fact the same thing. 

5 stars Thank you   January 23, 2006 - 3am Central time zone
Reviewer: A reader from UK


5 stars elapsed time   January 27, 2006 - 5am Central time zone
Reviewer: Mehmood from PAKISTAN
Dear Tom:

I am analyzing the trace output of the same query in two databases, All the init parameters are 
almost same. The execution plan is same, but one query is taking almost 25 seconds of elapsed time, 
and on the other database same query is taking 0.22 seconds of elapsed time. Can you please put 
some light on this. I am pasting the output from both the queries.

=======================================
Database 1:
********************************************************************************

UPDATE VEHICLE_M SET TRIPS=:b1 - 1  
WHERE
 VEH_NO = :b2


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute     12      0.00      25.04          0          0         48          12
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       13      0.00      25.05          0          0         48          12

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

Rows     Row Source Operation
-------  ---------------------------------------------------
     12  UPDATE VEHICLE_M 
      0   TABLE ACCESS FULL VEHICLE_M 


Rows     Execution Plan
-------  ---------------------------------------------------
      0  UPDATE STATEMENT   GOAL: CHOOSE
     12   UPDATE OF 'VEHICLE_M'
      0    TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'VEHICLE_M' [:Q2125000]

              SELECT /*+ Q2125000 NO_EXPAND ROWID(A1) */ A1.ROWID,A1."VEH_NO",
              A1."TRIPS" FROM "VEHICLE_M" PX_GRANULE(0, BLOCK_RANGE, 
                DYNAMIC)  A1 WHERE A1."VEH_NO"=:B1

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

=======================================
Database 2:
=======================================

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

UPDATE VEHICLE_M SET TRIPS=:b1 - 1  
WHERE
 VEH_NO = :b2


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute     12      0.00       0.21          0          0         48          12
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       13      0.00       0.22          0          0         48          12

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

Rows     Row Source Operation
-------  ---------------------------------------------------
     12  UPDATE VEHICLE_M 
      0   TABLE ACCESS FULL VEHICLE_M 


Rows     Execution Plan
-------  ---------------------------------------------------
      0  UPDATE STATEMENT   GOAL: CHOOSE
     12   UPDATE OF 'VEHICLE_M'
      0    TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'VEHICLE_M' [:Q884000]

              SELECT /*+ Q884000 NO_EXPAND ROWID(A1) */ A1.ROWID,A1."VEH_NO",
              A1."TRIPS" FROM "VEHICLE_M" PX_GRANULE(0, BLOCK_RANGE, 
                DYNAMIC)  A1 WHERE A1."VEH_NO"=:B1

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

Thanks for your time.
 


Followup   January 27, 2006 - 8am Central time zone:

why are you using parallel query to update 12 rows?

what is the size of the table - and is the load on these systems "the same" (and are the systems 
"the same" 

5 stars elapsed time   January 28, 2006 - 2am Central time zone
Reviewer: Mehmood from Pakistan
Dear Tom:

We have parameter parallel_max_servers=5 in both the databases, so parallel queries are being used.

And the size of the table is 5MB in both the tables.

The Database 1, where the query is taking 25 seconds, is dual processor machine, with 1GB RAM, with 
minimum load.

And the Database 2, where the query is taking 0.22 seconds, is the single processor machine, with 
512MB RAM.

Thanks for the time. 


Followup   January 28, 2006 - 1pm Central time zone:

why are you using parallel at all for such small stuff. 

4 stars elapsed time   January 29, 2006 - 3am Central time zone
Reviewer: Mehmood from Pakistan
So is it the parallel query which is taking 25 seconds to process 12 rows. 

Should I set the max_parallel_server=0??

And could you please suggest, where we should use the parallel query, and is this depends upon the 
number of processors in the machine. And also does parallel query will have any impact,if we have 
single processor machine.

Thanks for the help. 


Followup   January 29, 2006 - 8am Central time zone:

it could well be - if you have something tiny to do - as you do - just setting up for parallel 
query (getting the processes going, getting ready to just "start" and then doing it and 
coordinating it takes many times longer than "just doing it yourself"

why are you doing parallel DML - you have to ASK specfically to do that in your application, stop 
asking for it.

<quote src=Expert Oracle: Database Architecture>
When to Use Parallel Execution

Parallel execution can be fantastic. It can allow you to take a process that executes over many 
hours or days and complete it in minutes. Breaking down a huge problem into small components may, 
in some cases, dramatically reduce the processing time. However, one underlying concept that it 
will be useful to keep in mind while considering parallel execution is summarized by this very 
short quote from Practical Oracle8i: Building Efficient Databases (Addison-Wesley, 2001) by 
Jonathan Lewis:

PARALLEL QUERY option is essentially nonscalable.

Parallel execution is essentially a nonscalable solution. It was designed to allow an individual 
user or a particular SQL statement to consume all resources of a database. If you have a feature 
that allows an individual to make use of everything that is available, and then allow two 
individuals to use that feature, youÂ’ll have obvious contention issues. As the number of concurrent 
users on your system begins to overwhelm the number of resources you have (memory, CPU, and I/O), 
the ability to deploy parallel operations becomes questionable. If you have a four-CPU machine, for 
example, and on average you have 32 users executing queries simultaneously, then the odds are that 
you do not want to parallelize their operations. If you allowed each user to perform just a 
“parallel 2” query, then you would now have 64 concurrent operations taking place on a machine with 
just four CPUs. If the machine were not overwhelmed before parallel execution, it almost certainly 
would be now.

In short, parallel execution can also be a terrible idea. In many cases, the application of 
parallel processing will only lead to increased resource consumption, as parallel execution 
attempts to use all available resources. In a system where resources must be shared by many 
concurrent transactions, such as an OLTP system, you would likely observe increased response times 
due to this. It avoids certain execution techniques that it can use efficiently in a serial 
execution plan and adopts execution paths such as full scans in the hope that by performing many 
pieces of the larger, bulk operation in parallel, it would be better than the serial plan. Parallel 
execution, when applied inappropriately, may be the cause of your performance problem, not the 
solution for it.
So, before applying parallel execution, you need the following two things to be true:

    *    You must have a very large task, such as the full scan of 50GB of data.
    *    You must have sufficient available resources. Before parallel full scanning 50GB of data, 
you would want to make sure that there is sufficient free CPU (to accommodate the parallel 
processes) as well as sufficient I/O. The 50GB should be spread over more than one physical disk to 
allow for many concurrent read requests to happen simultaneously, there should be sufficient I/O 
channels from the disk to the computer to retrieve the data from disk in parallel, and so on.

If you have a small task, as generally typified by the queries carried out in an OLTP system, or 
you have insufficient available resources, again as is typical in an OLTP system where CPU and I/O 
resources are often already used to their maximum, then parallel execution is not something youÂ’ll 
want to consider. So you can better understand this concept, I present the following analogy.

...
</quote> 

5 stars problem resolved   January 30, 2006 - 2am Central time zone
Reviewer: Mehmood from Pakistan
Dear Tom:

Thanks for the help. I have setup max_parallel_servers=0, and now it is taking less then 0.22 
seconds to get the 12 rows, previoulsy it was taking 25 seconds.

I must say, Tom you are very helping for the World's Oracle family. you are very valuable asset of 
all the Oracle community.
 


5 stars problem resolved   January 30, 2006 - 2am Central time zone
Reviewer: Mehmood from Pakistan
Dear Tom:

Thanks for the help. I have setup max_parallel_servers=0, and now it is taking less then 0.22 
seconds to get the 12 rows, previoulsy it was taking 25 seconds.

I must say, Tom you are very helping for the World's Oracle family. you are very valuable asset of 
all the Oracle community.
 


5 stars plan differerence when executing standalone versus PL/SQL   January 31, 2006 - 10am Central time zone
Reviewer: Menon 
Hi Tom
consider the following procedure:
create or replace procedure p
as
  l_count number;
begin
select  count(*)
into l_count
from emp;
end;
/
If I execute this query in PL/SQL it will use an execution plan.

Now assuming the same input/same CBO environment, if I execute this SQL standalone, can it ever 
have a different execution plan? I don't think so but wanted to confirm... 


Followup   January 31, 2006 - 3pm Central time zone:

that simple query - probably not, in a CBO environment in plsql or standalone it would likely 
either

a) index fast full scan any index that includes a non-null column
b) full scan the emp table if such an index does not exist


(ignoring fine grained access control (fgac), ignoring who owns the procedure, and other likely 
esoteric things) 

5 stars clarification of the above question   January 31, 2006 - 1pm Central time zone
Reviewer: Menon 
The above procedure is just an example to elaborate the question: "If I execute a query in PL/SQL 
and execute it as a SQL, can they generate different plans - assuming all the other variables (such 
as stats/CBO parameters etc.) are same? 


Followup   January 31, 2006 - 3pm Central time zone:

yes, especially dependent on version.  older plsql releases did not respect the session setting for 
optimizer mode.

binding can be a little bit different (could lead to differents with bind variable peeking in some 
strange cases)

Answer is "yes"
But not usually. 

5 stars thanx!   January 31, 2006 - 4pm Central time zone
Reviewer: Menon 
"yes, especially dependent on version.  older plsql releases did not respect the 
session setting for optimizer mode.

binding can be a little bit different (could lead to differents with bind 
variable peeking in some strange cases)

Answer is "yes"
But not usually. "

I should have mentioned - the Oracle version is 9.2.0.3 in my case. So do you know if the PL/SQL 
not respecting optimizer in rare situations in this version as well or has the problem been 
addressed?

And do you have the "strange cases" you mention above listed somewhere (in asktom or metalink, for 
example)?

Thanx! 


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

in 9ir2, plsql obeys the optimizer settting - you can set up a quick test to verify that it is 
happening for you. 

5 stars Full table scans on the dual table   January 31, 2006 - 5pm Central time zone
Reviewer: ravinder matte from austin,tx
Tom, 

I have seen 99% of the FULL tablescans are on the DUAL table in my database when I query the 
v$sql_plan. 

When i see the statspack report, top sql statement is SELECT NULL FROM DUAL FOR UPDATE NOWAIT;

I dont understand what is causing this?

Database :9206V
AIX 5.2

Thanks
Matte 


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

sqlplus is likely doing that.  do you use sqlplus alot?  There was a time when sqlplus would issue 
that every time you connected (to see if the database was read/write), they was corrected in 
current releases.

If that is your top sql though, must be an almost idle database otherwise?   

5 stars thanx Tom!   February 1, 2006 - 5am Central time zone
Reviewer: Menon 
"in 9ir2, plsql obeys the optimizer settting - you can set up a quick test to 
verify that it is happening for you. 
"
thanx! Yeah, well, in this case it is a counter example that can kill you. One of the cases where 
you have to know if there is any known bug since simply proving it for one case does not mean that 
you cant have another counter case.. 


4 stars Have a little question about CBO behaviour   February 6, 2006 - 10am Central time zone
Reviewer: Dmytro from Kyiv, Ukraine
Where is near 2 mln. records in the table and query has constraint like this:

select
...
where
...
and dt_create <= :date

CBO always supposes cardinality 15 and so uses index, but in real queries :date constraint is 
almost unselective (few hundreds of thousands or even almost all records satisfy it). If I'm using 
constant CBO makes right decision and query is executed in two min, but if I'm using a variable 
(what is needed), it takes near an hour to run.
 What can be done about this? Maybe few ideas, that can help in such a case. Or only 
/*+CARDINALITY*/ will help? But what if somebody will set the :date, that selects only few records? 


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

need test case to work with - or at least a slightly longer description of the data.  How is the 
data such that the cbo things "15", 15 is a rather odd number.


for example:

ops$tkyte@ORA10GR2> create table t as select * from all_objects;

Table created.

ops$tkyte@ORA10GR2> exec dbms_stats.gather_table_stats( user, 'T' );

PL/SQL procedure successfully completed.

ops$tkyte@ORA10GR2> create index t_idx on t(created);

Index created.

ops$tkyte@ORA10GR2> variable d varchar2(25)
ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> set autotrace traceonly explain
ops$tkyte@ORA10GR2> select * from t where created <= to_date(:d,'dd-mon-yyyy');

Execution Plan
----------------------------------------------------------
Plan hash value: 470836197

-------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |  2496 |   226K|    10   (0)| 00:
|   1 |  TABLE ACCESS BY INDEX ROWID| T     |  2496 |   226K|    10   (0)| 00
|*  2 |   INDEX RANGE SCAN          | T_IDX |   449 |       |     3   (0)| 00
-------------------------------------------------------------------------------

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

   2 - access("CREATED"<=TO_DATE(:D,'dd-mon-yyyy'))


The 2,496 number is the result of:

ops$tkyte@ORA10GR2> select num_rows from user_tables where table_name = 'T';

  NUM_ROWS
----------
     49925

ops$tkyte@ORA10GR2> select 2496/49925 from dual;

2496/49925
----------
.049994992


the optimizer used a 5% guess, so if 15 were the guess - I could think your statistics would have 
the table having 300 rows - but you say 2 million.

 

4 stars My stupid mistake....   February 8, 2006 - 6am Central time zone
Reviewer: Dmytro from Kyiv, Ukraine
Was looking on the cost column instead of cardinality.
Today tested on the other schema, so there is 2,400,000 records in the table and plan of query:

SELECT *
  FROM details
 WHERE dt_created < :dt

is like this:

SELECT STATEMENT, GOAL = CHOOSE            Cost=2831    Cardinality=120082    Bytes=10206970
 TABLE ACCESS BY INDEX ROWID    Object owner=AIRT_RT    Object name=ANIMALS    Cost=2831    
Cardinality=120082    Bytes=10206970
  INDEX RANGE SCAN    Object owner=AIRT_RT    Object name=AN_DT_CREATE_I    Cost=69    
Cardinality=21615    

So, CBO makes few percent guess and is using an index. The same thing is happening, if I include 
this "...AND dt_created < :dt" in more complicated queries with lot of joins and other stuff.

 These queries are reports and they usually include data for a long period of time, so use of this 
index is death-like for their perfomance. I can't drop this index, because it's helpful in other 
queries and sometimes even reports are taken for one month or something like this...

 Now I'm using /*+NO_INDEX (det det_dt_create_i)*/ and it really helps, but I'm not too fond of 
this kind of solution. Can something be better, than this? Some dynamic estimation of cardinality? 
This possibly means hard-parsing every time, but what if query is run only once a day, for example 
(hm... but it can be few dozens of such a queries)?..

Hope, I have clarified the situation a little...

Appreciate your help. 


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

what are you binding in there - a string, or a DATE? 

4 stars One more mistake... :)   February 8, 2006 - 6am Central time zone
Reviewer: A reader 
Two queries absolutely alike except the table names and too many opened windows... :)

so, query like this:
SELECT * FROM animals WHERE dt_created <:dt

and this hint I'm using
/*+NO_INDEX (an an_dt_create_i)*/ 


4 stars About binding string or date   February 8, 2006 - 9am Central time zone
Reviewer: Dmytro from Kyiv, Ukraine
 I'm using PL/SQL Developer :)), so to get this plan, I just wrote query as is with ":dt" and 
receive plan.
 Real query is used to open ref_cursor, in PL/SQL function and in place of ':dt' stays call to 
another function, that gets date and returns date (with time or without depending on the second 
parameter).

In simple words - date.
 


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

ok, with the function - it won't bind variable peek (if it did, it would get the right plan)

How about instead of 

where date_column = f(x)

you run


   plsql_variable_of_type_date := f(x);

   ...
     where date_column = plsql_variable_of_type_date
   ....

? 

5 stars Remote objects stats   February 13, 2006 - 7am Central time zone
Reviewer: Reader from Russia
Tom, i have question and unfortunately can't find answer in the docs.

If my query envolves remote tables (assume we use CBO) - does CBO look at remote objects stats or 
not?

It seems for me what is does so.

CREATE DATABASE LINK self CONNECT TO user identified by "password" using 'connect';

SQL> create table t1 as select * from all_objects;

Table created.

SQL> exec dbms_stats.gather_table_stats(user, 't1');

PL/SQL procedure successfully completed.

SQL> set autotrace traceonly explain
SQL> select * from t1@self, dual;

Execution Plan
----------------------------------------------------------
Plan hash value: 3027949496

-------------------------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     | Inst   |IN-OUT|
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      | 49181 |  4562K|   432  (66)| 00:00:06 |        |      |
|   1 |  NESTED LOOPS      |      | 49181 |  4562K|   432  (66)| 00:00:06 |        |      |
|   2 |   TABLE ACCESS FULL| DUAL |     1 |     2 |     2   (0)| 00:00:01 |        |      |
|   3 |   REMOTE           | T1   | 49181 |  4466K|   150   (1)| 00:00:02 |   SELF | R->S |
-------------------------------------------------------------------------------------------

Remote SQL Information (identified by operation id):
----------------------------------------------------

   3 - SELECT "OWNER","OBJECT_NAME","SUBOBJECT_NAME","OBJECT_ID","DATA_OBJECT_ID","
       OBJECT_TYPE","CREATED","LAST_DDL_TIME","TIMESTAMP","STATUS","TEMPORARY","GENERATED"
       ,"SECONDARY" FROM "T1" "T1" (accessing 'SELF' )

It picked cardinality just right. Please clarify:

1. By asking remote site for stats we indeed increase parse time (by orders and orders of 
magnitude)?
2. We increase latch hold time by orders of magnitude as well?
 


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

... If my query envolves remote tables (assume we use CBO) - does CBO look at remote 
objects stats or not? ...

yes it does. 

2 stars Explain Plan indicates full table scan   February 17, 2006 - 8am Central time zone
Reviewer: Bhaskar from India
Hi Tom,
Explain plan indicates a fulltablescan for a BIG table[optiontable] in the below query. Forcing 
optimizer to use a index on this table makes things worse.

SELECT DISTINCT OPTIONTABLE.MOSB_OPTN_CD, 
DESCTABLE.CONFIG_SHORT_DESC,ORDERTABLE.MODEL_YR_CD,ORDERTABLE.MODEL_YR_SUFX_CD 
FROM 
VEH_CONFIG_DESC DESCTABLE  
,VEH_ORDER ORDERTABLE
,VEH_ORDER_OPTN OPTIONTABLE
WHERE 
DESCTABLE.COUNTRY_CD = 'US' 
AND DESCTABLE.LANG_CD = 'US' 
AND DESCTABLE.CONFIG_TYPE = 'O' 
AND DESCTABLE.MODEL_YR_CD = ORDERTABLE.MODEL_YR_CD
AND DESCTABLE.MODEL_YR_SUFX_CD = ORDERTABLE.MODEL_YR_SUFX_CD  
AND ORDERTABLE.ORDER_ID = OPTIONTABLE.ORDER_ID
AND OPTIONTABLE.MOSB_OPTN_CD = DESCTABLE.MOSB_CONFIG_CD 
AND OPTIONTABLE.OVC_OPTN_CD = DESCTABLE.OVC_CONFIG_CD 
AND NVL (ORDERTABLE.RECORD_STATUS_TYPE,'TRANSFER') <> 'TRANSFER' 
AND (ORDERTABLE.DLR_CD IN ('USA304', 'USA319', 'USA322','USA330','USA305') 
OR ORDERTABLE.RESRVD_DLR_CD IN ('USA304', 'USA319', 'USA322','USA330','USA305') 
OR ORDERTABLE.DELVRY_DEST_CD IN ('USA304', 'USA319', 'USA322','USA330','USA305') 
OR ORDERTABLE.CHARGE_TO_CD IN ('USA304', 'USA319', 'USA322','USA330','USA305')) 
AND DESCTABLE.CONFIG_TYPE = 'O' 
ORDER BY OPTIONTABLE.MOSB_OPTN_CD;
-----------------------------------------------------
Explain Plan Output is as below.

<PRE>
-----------------------------------------------------------------------------------

| Id  | Operation                      |  Name            | Rows  | Bytes | Cost |

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

|   0 | SELECT STATEMENT               |                  |     1 |    97 |   227 |
|   1 |  SORT UNIQUE                   |                  |     1 |    97 |   226 |
|   2 |   NESTED LOOPS                 |                  |     1 |    97 |   224 |
|   3 |    HASH JOIN                   |                  |    66 |  4026 |    92 |
|   4 |     TABLE ACCESS BY INDEX ROWID| VEH_CONFIG_DESC  |    64 |  3200 |    14 |
|   5 |      INDEX RANGE SCAN          | SYS_C001860      |     5 |       |    11 |
|   6 |     TABLE ACCESS FULL          | VEH_ORDER_OPTN   |   144K|  1550K|    76 |
|   7 |    TABLE ACCESS BY INDEX ROWID | VEH_ORDER        |     1 |    36 |    2 |
|   8 |     INDEX UNIQUE SCAN          | SYS_C001569      |    13 |       |     1 |
-----------------------------------------------------------------------------------
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=227 Card=1 Bytes=97)
   1    0   SORT (UNIQUE) (Cost=226 Card=1 Bytes=97)
   2    1     NESTED LOOPS (Cost=224 Card=1 Bytes=97)
   3    2       HASH JOIN (Cost=92 Card=66 Bytes=4026)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'VEH_CONFIG_DESC' (Cost=14 Card=64 Bytes=3200)
   5    4           INDEX (RANGE SCAN) OF 'SYS_C001860' (UNIQUE) (Cost =11 Card=5)
   6    3         TABLE ACCESS (FULL) OF 'VEH_ORDER_OPTN' (Cost=76 Card=144366 Bytes=1588026)
   7    2       TABLE ACCESS (BY INDEX ROWID) OF 'VEH_ORDER' (Cost=2 Card=1 Bytes=36)
   8    7         INDEX (UNIQUE SCAN) OF 'SYS_C001569' (UNIQUE) (Cost=1 Card=13)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
    1675841  consistent gets
      30470  physical reads
          0  redo size
       6704  bytes sent via SQL*Net to client
        743  bytes received via SQL*Net from client
         10  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
        128  rows processed
</PRE>
-----------------------------------------------

can you explain why optimizer is going for FTS and why a index scan is much costlier.Table details 
are as below.
VEH_CONFIG_DESC [SMALLEST]-5000 rows
VEH_ORDER [MEDIUM]- 600,000
VEH_ORDER_OPTN [BIG] - 1100,000

This query was run in 9.2 and I have computed statistics for all indexed columns.At presne it takes 
30 seconds to run.Can we write the query in a more efficient way ?

thanks in advance,
bhaskar 


Followup   February 17, 2006 - 2pm Central time zone:

if going for the index makes it worse, why would you do that?


are we a warehouse or a transactional system here.  I can suggest bitmaps but only if the database 
is read only/read mostly. 

5 stars Tuning HASH JOIN   February 17, 2006 - 3pm Central time zone
Reviewer: Prerak Mehta from New York, USA
Hi Tom,

I have a billing monthend query that takes 20 hours to finish.

SELECT /*+  ORDERED */ sum(pin_round(decode(impact_type, 4, 0.0, 64, 0.0, 
       portion * db_gross_amt), adjust, rounding)) db_gross, sum(
       pin_round(decode(impact_type, 4, 0.0, 64, 0.0, portion * cr_gross_amt), 
       adjust, rounding)) cr_gross, sum(pin_round(decode(impact_type, 4, 0.0, 
       64, 0.0, portion * db_disc_amt), adjust, rounding)) db_disc, sum(
       pin_round(decode(impact_type, 4, 0.0, 64, 0.0, portion * cr_disc_amt), 
       adjust, rounding)) cr_disc, sum(pin_round(decode(impact_type, 4, portion
       * db_amt, 64, portion * db_amt, 0.0), adjust, rounding)) db_tax, 
       sum(pin_round(decode(impact_type, 4, portion * cr_amt, 64, portion * 
       cr_amt, 0.0), adjust, rounding)) cr_tax, ar_account_obj_id0, poid_id0, 
       resource_id, gl_id
    FROM (SELECT eb.impact_type, greatest(0, decode(greatest(e.earned_end_t - 
                 e.earned_start_t, 43200), 43200, 1, round((e.earned_end_t - 
                 greatest(:b3, e.earned_start_t)) / (24 * 3600), 0) / 
                 round((e.earned_end_t - e.earned_start_t) / (24 * 3600), 0)))
                 portion, greatest(eb.amount, 0) db_amt, least(eb.amount, 0)
                 cr_amt, greatest(eb.discount, 0) db_disc_amt, least(
                 eb.discount, 0) cr_disc_amt, greatest(eb.amount + eb.discount, 
                 0) db_gross_amt, least(eb.amount + eb.discount, 0)
                 cr_gross_amt, i.ar_account_obj_id0, i.poid_id0, 
                 eb.resource_id, eb.gl_id
              FROM ledger_report_gl_segments_t gs, item_t i, 
                   event_bal_impacts_t eb, event_t e
              WHERE gs.obj_id0 = :b6
                AND gs.gl_segment = i.gl_segment
                AND i.effective_t > 0
                AND i.effective_t < :b3
                AND (i.poid_type = '/item/misc'
                OR  i.poid_type = '/item/sponsor'
                OR  i.poid_type IN (SELECT obj_type
                         FROM config_items_t ci, config_events_t ce
                         WHERE ci.obj_id0 = ce.obj_id0
                           AND ci.rec_id = ce.rec_id2
                           AND ce.event_type LIKE 
                               '/event/billing/product/fee/cycle/cycle_forward%'
                         ))
                AND i.poid_db IS NOT NULL
                AND eb.item_obj_id0 = i.poid_id0
                AND eb.resource_id + 0 BETWEEN :b5 AND :b4
                AND eb.gl_id <> 0
                AND e.poid_id0 = eb.obj_id0
                AND e.earned_end_t > :b3
                AND e.poid_type LIKE 
                    '/event/billing/product/fee/cycle/cycle_forward%'
                AND (:b2 IS NULL
                OR  i.ar_account_obj_id0 BETWEEN :b2 AND :b1)) event_info, (
         SELECT rec_id, rounding, decode(rounding_mode, 0, 0, 1, 0.4 / 
                power(10, rounding), 2, (-0.5) / power(10, rounding), 3, 0)
                adjust
              FROM config_beid_balances_t) beid_info
    WHERE event_info.resource_id = beid_info.rec_id
    GROUP BY ar_account_obj_id0, poid_id0, gl_id, resource_id


Optimizer Mode Used:

   FIRST_ROWS 

Total Cost:

   351,253 



Execution Steps:

Step # Step Name 

21  SELECT STATEMENT 
20    SORT [GROUP BY] 
19      FILTER 
15        FILTER 
14        HASH JOIN 
1          PBDPIN.CONFIG_BEID_BALANCES_T TABLE ACCESS [FULL] 
13             NESTED LOOPS 
9            HASH JOIN 
6               PBDPIN.ITEM_T TABLE ACCESS [BY INDEX ROWID] 
5                  NESTED LOOPS 
3                 PBDPIN.LEDGER_REPORT_GL_SEGMENTS_T TABLE ACCESS [BY INDEX ROWID] 
2                PBDPIN.I_LEDGER_REPORT_GL_SEGS__ID INDEX [RANGE SCAN] 
4                   PBDPIN.I_ITEM_GLSEG_EFF__ID INDEX [RANGE SCAN] 
8                PARTITION RANGE [ALL] 
7                PBDPIN.EVENT_BAL_IMPACTS_T TABLE ACCESS [FULL] 
12            PARTITION RANGE [ITERATOR] 
11                PBDPIN.EVENT_T TABLE ACCESS [BY LOCAL INDEX ROWID] 
10                PBDPIN.I_EVENT__ID INDEX [UNIQUE SCAN] 
18        NESTED LOOPS 
16            PBDPIN.CONFIG_ITEMS_T TABLE ACCESS [FULL] 
17            PBDPIN.CONFIG_EVENTS_T TABLE ACCESS [FULL] 


I took statspack.snap (i_snap_level=>7) & took explian plan thru the sprepsql.sql which looked like 
this:

--------------------------------------------------------------------------------
| Operation                      | PHV/Object Name     |  Rows | Bytes|   Cost |
--------------------------------------------------------------------------------
|SELECT STATEMENT                |----- 1738109184 ----|       |      |1019789 |
|SORT GROUP BY                   |                     |     9K|    1M|1019789 |
| FILTER                         |                     |       |      |        |
|  FILTER                        |                     |       |      |        |
|   HASH JOIN                    |                     |     9K|    1M|1019547 |
|    TABLE ACCESS FULL           |CONFIG_BEID_BALANCES |    76 |  836 |      2 |
|    NESTED LOOPS                |                     |     9K|    1M|1019544 |
|     HASH JOIN                  |                     |   146K|   12M| 580404 |
|      HASH JOIN                 |                     |   925K|   43M| 347339 |
|       TABLE ACCESS BY INDEX ROW|LEDGER_REPORT_GL_SEG |     1 |    9 |      2 |
|        INDEX RANGE SCAN        |I_LEDGER_REPORT_GL_S |     1 |      |      1 |
|       TABLE ACCESS FULL        |ITEM_T               |   925K|   35M| 347336 |
|      PARTITION RANGE ALL       |                     |       |      |        |
|       TABLE ACCESS FULL        |EVENT_BAL_IMPACTS_T  |    11M|  402M| 231249 |
|     PARTITION RANGE ITERATOR   |                     |       |      |        |
|      TABLE ACCESS BY LOCAL INDE|EVENT_T              |     1 |   42 |      3 |
|       INDEX UNIQUE SCAN        |I_EVENT__ID          |     1 |      |      2 |
|  NESTED LOOPS                  |                     |     1 |   70 |      4 |
|   TABLE ACCESS FULL            |CONFIG_ITEMS_T       |     1 |   26 |      2 |
|   TABLE ACCESS FULL            |CONFIG_EVENTS_T      |     1 |   44 |      2 |
--------------------------------------------------------------------------------


I have 9iR2 on Solaris 64bit, PGA_AGGREGATE_TARGET=10gb, workarea_size_policy=auto & mts_servers=1. 
So in the procedure, which runs this query i have set session level parameters:

sql_stmt1:='alter session set optimizer_index_cost_adj=10';
execute immediate sql_stmt1;
sql_stmt2:='alter session set optimizer_index_caching=90';
execute immediate sql_stmt2;
sql_stmt3:='alter session set workarea_size_policy=manual';
execute immediate sql_stmt3;
sql_stmt4:='alter session set sort_area_size=204857600';
execute immediate sql_stmt4;
sql_stmt5:='alter session set sort_area_retained_size=204857600';
execute immediate sql_stmt5;
sql_stmt6:='alter session set hash_area_size=504857600';
execute immediate sql_stmt6;
sql_stmt7:='alter session set db_file_multiblock_read_count=32';
execute immediate sql_stmt7;
sql_stmt8:='alter session set hash_multiblock_io_count=32';
execute immediate sql_stmt8;

My concern here is that my hash join takes around 16-17 hours to finish.I tried nested 
loop(USE_NL), hash join (USE_HASH) & driving_site (DRIVING_SITE) hints too. Then i removed all 
hints and nothing changed. I need some guidance from you as to how to improve performance of this 
hash join. 

Prerak 


Followup   February 17, 2006 - 5pm Central time zone:

when you do the hash join (brutally efficient - to process this much data, no indexes) what are you 
waiting for - physical io? temp writes? what?  maybe run for a while with 10046 level 12 trace and 
see what your big waits are (I sort of suspect "physical IO" 

5 stars Tuning Hash Joins   February 17, 2006 - 4pm Central time zone
Reviewer: Prerak from New York, US
Hi Tom,

Forgot to mention no of rows in each tables.

EVENT_T                has 451087086 rows
CONFIG_ITEMS_T            has 7 rows
CONFIG_EVENTS_T            has 11 rows
ITEM_T                has 73858158 rows
LEDGER_REPORT_GL_SEGMENTS_T    has 122 rows
EVENT_BAL_IMPACTS_T        has 57932811 rows
CONFIG_BEID_BALANCES_T        has 76 rows

Prerak 


5 stars Tuning Hash Joins   February 20, 2006 - 3pm Central time zone
Reviewer: Prerak from New York,US
Hi Tom,

Its showing wait event scattered read & sequential read while reading indexed data.Is there other 
way to tune this query?

/orasource/app/oracle/admin/devdb/udump/devdb_ora_11901.trc
Oracle9i Enterprise Edition Release 9.2.0.6.0 - 64bit Production
With the Partitioning and OLAP options
JServer Release 9.2.0.6.0 - Production
ORACLE_HOME = /orasource/app/oracle/product/9.2.0
System name:    SunOS
Node name:      DEVDB01
Release:        5.9
Version:        Generic_118558-04
Machine:        sun4u
Instance name: DEVDB
Redo thread mounted by this instance: 1
Oracle process number: 76
Unix process pid: 11901, image: oracle@DEVDB01 (TNS V1-V3)

*** 2006-02-07 16:05:05.724
*** SESSION ID:(142.10960) 2006-02-07 16:05:05.667
WAIT #6: nam='db file scattered read' ela= 2274 p1=86 p2=3165545 p3=32
WAIT #6: nam='db file scattered read' ela= 2771 p1=86 p2=3165577 p3=32
WAIT #6: nam='db file scattered read' ela= 2505 p1=86 p2=3165609 p3=32
WAIT #6: nam='direct path write' ela= 8 p1=203 p2=190345 p3=126
WAIT #6: nam='db file scattered read' ela= 2384 p1=86 p2=3165641 p3=32
WAIT #6: nam='db file scattered read' ela= 2376 p1=86 p2=3165673 p3=32
WAIT #6: nam='db file scattered read' ela= 2473 p1=86 p2=3165705 p3=32
WAIT #6: nam='db file scattered read' ela= 2743 p1=86 p2=3165737 p3=32
WAIT #6: nam='db file scattered read' ela= 2397 p1=86 p2=3165769 p3=32
WAIT #6: nam='db file scattered read' ela= 2444 p1=86 p2=3165801 p3=32
WAIT #6: nam='db file scattered read' ela= 2393 p1=86 p2=3165833 p3=32
WAIT #6: nam='db file scattered read' ela= 2689 p1=86 p2=3165865 p3=32
WAIT #6: nam='db file scattered read' ela= 2528 p1=86 p2=3165897 p3=32
WAIT #6: nam='db file scattered read' ela= 2506 p1=86 p2=3165929 p3=32
WAIT #6: nam='db file scattered read' ela= 2379 p1=86 p2=3165961 p3=32
WAIT #6: nam='db file scattered read' ela= 2276 p1=86 p2=3165993 p3=32
WAIT #6: nam='db file scattered read' ela= 2437 p1=86 p2=3166025 p3=32
WAIT #6: nam='db file scattered read' ela= 2387 p1=86 p2=3166057 p3=32
WAIT #6: nam='db file scattered read' ela= 2221 p1=86 p2=3166089 p3=32
WAIT #6: nam='db file scattered read' ela= 2480 p1=86 p2=3166121 p3=32
WAIT #6: nam='db file scattered read' ela= 2247 p1=86 p2=3166153 p3=32
WAIT #6: nam='db file scattered read' ela= 2426 p1=86 p2=3166185 p3=32
WAIT #6: nam='db file scattered read' ela= 2407 p1=86 p2=3166217 p3=32
WAIT #6: nam='db file scattered read' ela= 2457 p1=86 p2=3166249 p3=32
WAIT #6: nam='db file scattered read' ela= 2366 p1=86 p2=3166281 p3=32
WAIT #6: nam='db file scattered read' ela= 2245 p1=86 p2=3166313 p3=32
WAIT #6: nam='db file scattered read' ela= 2358 p1=86 p2=3166345 p3=32
WAIT #6: nam='db file scattered read' ela= 2212 p1=86 p2=3166377 p3=32
WAIT #6: nam='db file scattered read' ela= 3921 p1=86 p2=3166409 p3=32
WAIT #6: nam='db file scattered read' ela= 2448 p1=86 p2=3166441 p3=32
WAIT #6: nam='db file scattered read' ela= 2466 p1=86 p2=3166473 p3=32
WAIT #6: nam='db file scattered read' ela= 2408 p1=86 p2=3166505 p3=32
WAIT #6: nam='db file scattered read' ela= 2420 p1=86 p2=3166537 p3=32
WAIT #6: nam='db file scattered read' ela= 2458 p1=86 p2=3166569 p3=32
WAIT #6: nam='db file scattered read' ela= 2466 p1=86 p2=3166601 p3=32
WAIT #6: nam='db file scattered read' ela= 2451 p1=86 p2=3166633 p3=32
WAIT #6: nam='db file scattered read' ela= 2184 p1=86 p2=3166665 p3=32
WAIT #6: nam='db file scattered read' ela= 2342 p1=86 p2=3166697 p3=32
WAIT #6: nam='db file scattered read' ela= 2453 p1=86 p2=3166729 p3=32
WAIT #6: nam='db file scattered read' ela= 2588 p1=86 p2=3166761 p3=32
WAIT #6: nam='db file scattered read' ela= 2302 p1=86 p2=3166793 p3=32
WAIT #6: nam='db file scattered read' ela= 2353 p1=86 p2=3166825 p3=32
WAIT #6: nam='db file scattered read' ela= 2513 p1=86 p2=3166857 p3=32
WAIT #6: nam='db file scattered read' ela= 2371 p1=86 p2=3166889 p3=32
WAIT #6: nam='db file scattered read' ela= 2528 p1=86 p2=3166921 p3=32
WAIT #6: nam='db file scattered read' ela= 2436 p1=86 p2=3166953 p3=32
WAIT #6: nam='db file scattered read' ela= 2415 p1=86 p2=3166985 p3=32
WAIT #6: nam='db file scattered read' ela= 2508 p1=86 p2=3167017 p3=32
WAIT #6: nam='db file scattered read' ela= 2635 p1=86 p2=3167049 p3=32
WAIT #6: nam='db file scattered read' ela= 2415 p1=86 p2=3167081 p3=32
WAIT #6: nam='db file scattered read' ela= 2410 p1=86 p2=3167113 p3=32
WAIT #6: nam='db file scattered read' ela= 2536 p1=86 p2=3167145 p3=32
WAIT #6: nam='db file scattered read' ela= 2546 p1=86 p2=3167177 p3=32
WAIT #6: nam='db file scattered read' ela= 2558 p1=86 p2=3167209 p3=32
WAIT #6: nam='db file scattered read' ela= 2405 p1=86 p2=3167241 p3=32
WAIT #6: nam='db file scattered read' ela= 2420 p1=86 p2=3167273 p3=32
WAIT #6: nam='db file scattered read' ela= 2360 p1=86 p2=3167305 p3=32
WAIT #6: nam='db file scattered read' ela= 2507 p1=86 p2=3167337 p3=32
WAIT #6: nam='db file scattered read' ela= 2386 p1=86 p2=3167369 p3=32
WAIT #6: nam='db file scattered read' ela= 2272 p1=86 p2=3167401 p3=32
WAIT #6: nam='db file scattered read' ela= 2583 p1=86 p2=3167433 p3=32
WAIT #6: nam='db file scattered read' ela= 2432 p1=86 p2=3167465 p3=32
WAIT #6: nam='db file scattered read' ela= 2433 p1=86 p2=3167497 p3=32
WAIT #6: nam='db file scattered read' ela= 2477 p1=86 p2=3167529 p3=32
WAIT #6: nam='db file scattered read' ela= 2449 p1=86 p2=3167561 p3=32
WAIT #6: nam='db file scattered read' ela= 2396 p1=86 p2=3167593 p3=32
WAIT #6: nam='db file scattered read' ela= 2462 p1=86 p2=3167625 p3=32











[DEVDB] more devdb_ora_18621.trc
/orasource/app/oracle/admin/devdb/udump/devdb_ora_18621.trc
Oracle9i Enterprise Edition Release 9.2.0.6.0 - 64bit Production
With the Partitioning and OLAP options
JServer Release 9.2.0.6.0 - Production
ORACLE_HOME = /orasource/app/oracle/product/9.2.0
System name:    SunOS
Node name:      DEVDB01
Release:        5.9
Version:        Generic_118558-04
Machine:        sun4u
Instance name: DEVDB
Redo thread mounted by this instance: 1
Oracle process number: 29
Unix process pid: 18621, image: oracle@DEVDB01 (TNS V1-V3)

*** 2006-02-13 15:54:46.691
*** SESSION ID:(31.25776) 2006-02-13 15:54:46.676
WAIT #6: nam='db file sequential read' ela= 10195 p1=9 p2=2449148 p3=1
WAIT #6: nam='db file sequential read' ela= 65 p1=92 p2=1325447 p3=1
WAIT #6: nam='db file sequential read' ela= 127 p1=43 p2=860015 p3=1
WAIT #6: nam='db file sequential read' ela= 32 p1=92 p2=1310677 p3=1
WAIT #6: nam='db file sequential read' ela= 82 p1=9 p2=2449052 p3=1
WAIT #6: nam='db file sequential read' ela= 35 p1=90 p2=2093212 p3=1
WAIT #6: nam='db file sequential read' ela= 794 p1=9 p2=2449053 p3=1
WAIT #6: nam='db file sequential read' ela= 9449 p1=90 p2=2130959 p3=1
WAIT #6: nam='db file sequential read' ela= 4878 p1=112 p2=1445329 p3=1
WAIT #6: nam='db file sequential read' ela= 81 p1=43 p2=860018 p3=1
WAIT #6: nam='db file sequential read' ela= 9051 p1=165 p2=1443553 p3=1
WAIT #6: nam='db file sequential read' ela= 103 p1=9 p2=2449058 p3=1
WAIT #6: nam='db file sequential read' ela= 76 p1=9 p2=2449054 p3=1
WAIT #6: nam='db file sequential read' ela= 76 p1=9 p2=2449059 p3=1
WAIT #6: nam='db file sequential read' ela= 73 p1=9 p2=2449055 p3=1
WAIT #6: nam='db file sequential read' ela= 68 p1=9 p2=2449060 p3=1
WAIT #6: nam='db file sequential read' ela= 79 p1=136 p2=11797 p3=1
WAIT #6: nam='db file sequential read' ela= 10626 p1=90 p2=2118267 p3=1
WAIT #6: nam='db file sequential read' ela= 65 p1=112 p2=1399999 p3=1
WAIT #6: nam='db file sequential read' ela= 59 p1=9 p2=2449056 p3=1
WAIT #6: nam='db file sequential read' ela= 71 p1=9 p2=2449061 p3=1
WAIT #6: nam='db file sequential read' ela= 320 p1=9 p2=2449062 p3=1
WAIT #6: nam='db file sequential read' ela= 1048 p1=9 p2=2449254 p3=1
WAIT #6: nam='db file sequential read' ela= 12904 p1=9 p2=2650599 p3=1
WAIT #6: nam='db file sequential read' ela= 656 p1=90 p2=2118266 p3=1
WAIT #6: nam='db file sequential read' ela= 71 p1=9 p2=2449064 p3=1
WAIT #6: nam='db file sequential read' ela= 15378 p1=90 p2=2228955 p3=1
WAIT #6: nam='db file sequential read' ela= 59 p1=9 p2=2449063 p3=1
WAIT #6: nam='db file sequential read' ela= 520 p1=43 p2=860019 p3=1
WAIT #6: nam='db file sequential read' ela= 364 p1=24 p2=1776630 p3=1
WAIT #6: nam='db file sequential read' ela= 80 p1=9 p2=2449065 p3=1
WAIT #6: nam='db file sequential read' ela= 75 p1=92 p2=1425066 p3=1
WAIT #6: nam='db file sequential read' ela= 25 p1=90 p2=2100240 p3=1
WAIT #6: nam='db file sequential read' ela= 72 p1=9 p2=2449069 p3=1
WAIT #6: nam='db file sequential read' ela= 5837 p1=90 p2=2221098 p3=1
WAIT #6: nam='db file sequential read' ela= 26 p1=9 p2=2380528 p3=1
WAIT #6: nam='db file sequential read' ela= 25 p1=9 p2=2410865 p3=1
WAIT #6: nam='db file sequential read' ela= 59 p1=9 p2=2449070 p3=1
WAIT #6: nam='db file sequential read' ela= 7969 p1=90 p2=2123047 p3=1
WAIT #6: nam='db file sequential read' ela= 7503 p1=90 p2=2171955 p3=1
WAIT #6: nam='db file sequential read' ela= 51 p1=112 p2=1412022 p3=1
WAIT #6: nam='db file sequential read' ela= 379 p1=9 p2=2449071 p3=1
WAIT #6: nam='db file sequential read' ela= 77 p1=9 p2=2449066 p3=1
WAIT #6: nam='db file sequential read' ela= 29 p1=90 p2=2089254 p3=1
WAIT #6: nam='db file sequential read' ela= 69 p1=9 p2=2449072 p3=1
WAIT #6: nam='db file sequential read' ela= 69 p1=9 p2=2449067 p3=1
WAIT #6: nam='db file sequential read' ela= 73 p1=92 p2=1352412 p3=1
WAIT #6: nam='db file sequential read' ela= 39 p1=90 p2=2089048 p3=1
WAIT #6: nam='db file sequential read' ela= 27 p1=90 p2=2102877 p3=1
WAIT #6: nam='db file sequential read' ela= 28 p1=92 p2=1324163 p3=1
WAIT #6: nam='db file sequential read' ela= 73 p1=136 p2=11798 p3=1
WAIT #6: nam='db file sequential read' ela= 70 p1=9 p2=2449073 p3=1
WAIT #6: nam='db file sequential read' ela= 69 p1=9 p2=2449068 p3=1
WAIT #6: nam='db file sequential read' ela= 9280 p1=92 p2=1516381 p3=1
WAIT #6: nam='db file sequential read' ela= 72 p1=9 p2=2449074 p3=1
WAIT #6: nam='db file sequential read' ela= 30 p1=9 p2=2409403 p3=1
WAIT #6: nam='db file sequential read' ela= 25 p1=9 p2=2415802 p3=1
WAIT #6: nam='db file sequential read' ela= 71 p1=9 p2=2449080 p3=1
WAIT #6: nam='db file sequential read' ela= 11694 p1=90 p2=2130220 p3=1
WAIT #6: nam='db file sequential read' ela= 217 p1=9 p2=2449081 p3=1
WAIT #6: nam='db file sequential read' ela= 29 p1=9 p2=2410567 p3=1
WAIT #6: nam='db file sequential read' ela= 70 p1=9 p2=2449075 p3=1
WAIT #6: nam='db file sequential read' ela= 57 p1=9 p2=2449082 p3=1
WAIT #6: nam='db file sequential read' ela= 64 p1=9 p2=2449077 p3=1
WAIT #6: nam='db file sequential read' ela= 25 p1=9 p2=2449083 p3=1
WAIT #6: nam='db file sequential read' ela= 69 p1=43 p2=860020 p3=1
WAIT #6: nam='db file sequential read' ela= 73 p1=9 p2=2449078 p3=1
WAIT #6: nam='db file sequential read' ela= 31 p1=92 p2=1323566 p3=1
WAIT #6: nam='db file sequential read' ela= 590 p1=92 p2=1698704 p3=1
WAIT #6: nam='db file sequential read' ela= 26 p1=9 p2=2395157 p3=1
WAIT #6: nam='db file sequential read' ela= 10223 p1=9 p2=2642348 p3=1
WAIT #6: nam='db file sequential read' ela= 76 p1=90 p2=2114145 p3=1
WAIT #6: nam='db file sequential read' ela= 1105 p1=90 p2=2118404 p3=1
WAIT #6: nam='db file sequential read' ela= 32 p1=92 p2=1342704 p3=1
WAIT #6: nam='db file sequential read' ela= 2643 p1=90 p2=2118260 p3=1
WAIT #6: nam='db file sequential read' ela= 74 p1=9 p2=2449085 p3=1
WAIT #6: nam='db file sequential read' ela= 59 p1=9 p2=2449079 p3=1
WAIT #6: nam='db file sequential read' ela= 64 p1=136 p2=11799 p3=1
WAIT #6: nam='db file sequential read' ela= 61 p1=9 p2=2449086 p3=1
WAIT #6: nam='db file sequential read' ela= 280 p1=9 p2=2449087 p3=1
WAIT #6: nam='db file sequential read' ela= 634 p1=90 p2=2162638 p3=1
WAIT #6: nam='db file sequential read' ela= 11513 p1=9 p2=2657242 p3=1
WAIT #6: nam='db file sequential read' ela= 112 p1=9 p2=2449092 p3=1
WAIT #6: nam='db file sequential read' ela= 106 p1=90 p2=2118224 p3=1
WAIT #6: nam='db file sequential read' ela= 28 p1=9 p2=2395910 p3=1
WAIT #6: nam='db file sequential read' ela= 27 p1=9 p2=2394417 p3=1
WAIT #6: nam='db file sequential read' ela= 111 p1=9 p2=2449088 p3=1
WAIT #6: nam='db file sequential read' ela= 76 p1=9 p2=2449093 p3=1
WAIT #6: nam='db file sequential read' ela= 82 p1=92 p2=1385298 p3=1
WAIT #6: nam='db file sequential read' ela= 73 p1=9 p2=2449089 p3=1
WAIT #6: nam='db file sequential read' ela= 110 p1=9 p2=2449094 p3=1
WAIT #6: nam='db file sequential read' ela= 79 p1=9 p2=2449090 p3=1
WAIT #6: nam='db file sequential read' ela= 67 p1=9 p2=2449095 p3=1
WAIT #6: nam='db file sequential read' ela= 25 p1=9 p2=2395033 p3=1
WAIT #6: nam='db file sequential read' ela= 27 p1=90 p2=2087329 p3=1
WAIT #6: nam='db file sequential read' ela= 3846 p1=90 p2=2118108 p3=1
WAIT #6: nam='db file sequential read' ela= 49 p1=9 p2=2415780 p3=1
WAIT #6: nam='db file sequential read' ela= 98 p1=9 p2=2449096 p3=1
WAIT #6: nam='db file sequential read' ela= 73 p1=90 p2=2117687 p3=1
WAIT #6: nam='db file sequential read' ela= 63 p1=9 p2=2449102 p3=1
WAIT #6: nam='db file sequential read' ela= 71 p1=9 p2=2449097 p3=1
WAIT #6: nam='db file sequential read' ela= 644 p1=9 p2=2449098 p3=1
WAIT #6: nam='db file sequential read' ela= 97 p1=43 p2=860022 p3=1
WAIT #6: nam='db file sequential read' ela= 74 p1=9 p2=2449103 p3=1
WAIT #6: nam='db file sequential read' ela= 1186 p1=90 p2=2118272 p3=1
WAIT #6: nam='db file sequential read' ela= 207 p1=136 p2=11800 p3=1
WAIT #6: nam='db file sequential read' ela= 30 p1=90 p2=2097444 p3=1
WAIT #6: nam='db file sequential read' ela= 7029 p1=90 p2=2117826 p3=1
WAIT #6: nam='db file sequential read' ela= 33 p1=112 p2=1413019 p3=1
WAIT #6: nam='db file sequential read' ela= 32 p1=9 p2=2449099 p3=1
WAIT #6: nam='db file sequential read' ela= 71 p1=9 p2=2449104 p3=1
WAIT #6: nam='db file sequential read' ela= 77 p1=43 p2=860021 p3=1
 


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

hmm.

crystal ball is broken, I cannot see your terminal, so no.  I have no information for you. 

5 stars Tuning Hash Joins   February 20, 2006 - 3pm Central time zone
Reviewer: Prerak from New York, US
Sorry for the above reply. Its only db file sequential reads....not the scattered reads.

Prerak 


5 stars About: "a little question about CBO behaviour"   February 21, 2006 - 4am Central time zone
Reviewer: Dmytro from Kyiv, Ukraine
 It's just amazing how little things can make major changes, that make your code work. Variables 
rules, PL/SQL functions in SQL queries must die! :)) No more hints, no more headache, just simple 
and plain code. :)
 Sorry for not replying for so long - there were other tasks, which overhelmed me and I was able to 
return to this problem only now.
 Using variable in place of direct function call resolve the problem and our reports now take no 
longer than few minutes.
 Thanks, Tom. :) 


4 stars Optimizer in PLAN_TABLE   February 27, 2006 - 6am Central time zone
Reviewer: ina from Germany
Tom, really great tipps on this site!

Generating the estimated execution plan of my queries, I use "explain plan for" and then select the 
information I am interested in from the plan_table.
For the optimizer column of the plan_table, I get "ANALYZED" quite often. In the documentation, I 
just found that there are 4 optimizer modes, RULE, ALL_ROWS, FIRST_ROW and CHOOSE. What is then 
meant by "ANALYZED" there?

Thanks for help!
Ina 


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

means the step you are looking at may have been influenced by the existence of statistics.  The 
thing it looked at was analyzed, statistics were present. 

5 stars "Simple" Queries on Dictionary Tables Have Slow Parsing   March 27, 2006 - 5pm Central time zone
Reviewer: Jay from New York, NY
Hi, Tom,

After analyzing the slow performance of a third party software on one of our 10gr2 databases, I 
found most of the time it spent was on parsing some seemingly simple queries on data dictionary 
views. For example: 

SELECT PARTITIONED
FROM
 SYS.DBA_TABLES WHERE TABLE_NAME='' AND OWNER=''


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      1.83       1.79          0          0          0           0
Execute      4      0.01       0.00          0          0          0           0
Fetch        4      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       12      1.84       1.79          0          0          0           0

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 40
 

SELECT COMPRESSION
FROM
 ALL_TABLES WHERE 1=2


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

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 40

I tried several times (after the queries were out of pool), once right after the DB was re-started 
and I was the only logged in user, the parsing on these several queries consistently took long time 
(ie. a couple of seconds) while other apparently more complex queries which involve many joins are 
parsed much faster on the same database. And this packaged software works generally well and fast 
on our other databases (including both 9i and 10gr2 database). Particulary, the parsing time on my 
another database is negligible when the queries are executed first time (not in v$sql) while the 
two databases are almost identical except the fast database is on a 8 CPU x 800 MHz HP machine with 
16G memory while the slow one is on a 4 CPU x 200 MHz HP machine with 3G memory (both servers are 
~90% idle when I was testing). So my question is, is the speed of the CPUs the determining factor 
here? If yes, why those several seemingly simple queries consistently slower to be parsed than 
others of similar or more complexity on a same DB?

Thanks 


Followup   March 27, 2006 - 8pm Central time zone:

did you think to look at the view definitions underneath those simple queries.

You'll find them to be "rather complex".


Are you using the CBO by force on the other machines?  I see all_rows here - is it by chance 
"choose" elsewhere - and do you have statistics?


(and wow, where 1=2, hmm - think this guys could have used - oh, I don't know "describe" apis to 
figure out the size and shape rather than parsing a query that will never actually be used??) 

3 stars OK   March 28, 2006 - 3am Central time zone
Reviewer: A reader 
Hi Tom,
Could you please provide the LINK for TKPROF and 
explain plan?? 


Followup   March 28, 2006 - 7am Central time zone:

http://download-east.oracle.com/docs/cd/B19306_01/server.102/b14211/toc.htm

4 stars Queries on Dictionary Views Have Long Parsing Time   March 28, 2006 - 9am Central time zone
Reviewer: Jay from New York, NY
By "complex" I meant some queries like this (on the same slow DB)

SELECT 'YES'
FROM
 DUAL WHERE EXISTS (SELECT 'x' FROM SYS.DBA_TABLES WHERE OWNER = 'DEFCON' AND
  TABLE_NAME = 'LU_FREQUENCY' AND TEMPORARY='Y' AND TABLE_NAME LIKE 'RUPD$%')
  OR EXISTS (SELECT 'X' FROM SYS.DBA_SNAPSHOT_LOGS WHERE LOG_OWNER = 'DEFCON'
  AND LOG_TABLE = 'LU_FREQUENCY')


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

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 60

It also involves a lot of fixed tables and other dictionary views but its parsing time is shorter. 
Which makes me think maybe CPU speed is not the only determining factor. (But again, it may)

I got a trace from the other (faster) database. I added spaces in the command to make sure it 
parses:

select compression
from
 all_tables where 1 =2


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

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 51

It also uses ALL_ROWS (both dbs are 10g, I didn't change the default). The execution plan was the 
same too. However, the parsing was much faster (0.23s vs. >2s).  


Followup   March 28, 2006 - 4pm Central time zone:

tell them BIND VARIABLES ARE GOOD

you will reduce time spent parsing
you will not kill my database instance
you will find things work better.


if they didn't do hard parsing, this would be a non-issue.


are the stats in the databases "the same", do you have them for one and not for the other? 

2 stars   March 28, 2006 - 2pm Central time zone
Reviewer: A reader 
Guess  the system tables are analyzed in both the databases 


Followup   March 28, 2006 - 8pm Central time zone:

"guess"? 

5 stars Re: Queries on Dictionary Views Have Long Parsing Time   March 29, 2006 - 9am Central time zone
Reviewer: Jay from New York, NY
That's a good catch. Apparently they didn't use bind variables. 

Aside from that, the stats on both DBs were collected. 


3 stars Dynamic Optimization   April 10, 2006 - 12pm Central time zone
Reviewer: Chris Slattery from Dublin, Ireland.
Tom, does the optimizer change plans based on instance workload ? 

The main thing I can see is PGA_AGGREGATE_TARGET ... if more users were connected surely the amount 
per user will decrease therefore decreasing sort space for example, possibly making NL join better 
than a hash join ... 

Of course that leads into a discussion then of when the optimizer would notice this for a query in 
the pool already. 

Can't see it in my [admittedly quick] scan of the doco. 
 


Followup   April 11, 2006 - 10am Central time zone:

It will not change the plan based on the current workload, it changes the amount of resources it is 
willing to let each operation have. 

4 stars plan_table output,   April 19, 2006 - 2pm Central time zone
Reviewer: A reader 
I have plan_table created in 9.2.0.5 database and in 9.2.0.1 database.

The objects in both the databases are IDENTICAL.  Even the data too.

I did explain plan for an identitical sql statement using the following technique:  
"explain plan set statement_id = 'p1' for"

On a 9.2.0.5 database, I got the following result:

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------


-------------------------------------------------------------------------------------------
| Id  | Operation                     |  Name                     | Rows  | Bytes | Cost  |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                           |     1 |    56 |     3 |
|   1 |  SORT AGGREGATE               |                           |     1 |    56 |       |
|   2 |   NESTED LOOPS                |                           |     1 |    56 |     3 |
|*  3 |    TABLE ACCESS BY INDEX ROWID| INVESTMENT_DATA           |     1 |    33 |     2 |
|*  4 |     INDEX RANGE SCAN          | FNDX_INVEST_DATA_VERSION  |   110 |       |     1 |
|*  5 |    TABLE ACCESS BY INDEX ROWID| INVESTMENT_ATTRIB         |     1 |    23 |     1 |
|*  6 |     INDEX UNIQUE SCAN         | PK_INVESTMENT_ATTRIB      |     1 |       |       |
-------------------------------------------------------------------------------------------

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

   3 - filter("IDAT"."PLANNING_CODE"='xx')
   4 - access("IDAT"."PORTFOLIO_CODE"='134267' AND "IDAT"."VERSION_NUMBER"=1)
   5 - filter("IA"."ATTRIBUTE_TYPE"='MES' AND "IA"."FIELD_TYPE"='C' AND
              "IA"."TIME_PHASE_IND"='N' AND "IA"."DEFAULT_POS_NEG"='N')
   6 - access("IDAT"."INVEST_MODEL_CODE"="IA"."INVEST_MODEL_CODE" AND
              "IDAT"."INVEST_ATTRIB"="IA"."INVEST_ATTRIB")
              
Note: cpu costing is off

and on the other database (9.2.0.1), the output I got is:

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------


-------------------------------------------------------------------------------------------
| Id  | Operation                     |  Name                     | Rows  | Bytes | Cost  |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                           |       |       |       |
|   1 |  SORT AGGREGATE               |                           |       |       |       |
|   2 |   NESTED LOOPS                |                           |       |       |       |
|*  3 |    TABLE ACCESS BY INDEX ROWID| INVESTMENT_DATA           |       |       |       |
|*  4 |     INDEX RANGE SCAN          | FNDX_INVEST_DATA_VERSION  |       |       |       |
|*  5 |    TABLE ACCESS BY INDEX ROWID| INVESTMENT_ATTRIB         |       |       |       |
|*  6 |     INDEX UNIQUE SCAN         | PK_INVESTMENT_ATTRIB      |       |       |       |
-------------------------------------------------------------------------------------------

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

   3 - filter("IDAT"."PLANNING_CODE"='xx')
   4 - access("IDAT"."PORTFOLIO_CODE"='134267' AND "IDAT"."VERSION_NUMBER"=1)
   5 - filter("IA"."DEFAULT_POS_NEG"='N' AND "IA"."TIME_PHASE_IND"='N' AND "IA"."FIELD_T
              YPE"='C' AND "IA"."ATTRIBUTE_TYPE"='MES')
   6 - access("IDAT"."INVEST_MODEL_CODE"="IA"."INVEST_MODEL_CODE" AND "IDAT"."INVEST_ATT
              RIB"="IA"."INVEST_ATTRIB"

Note: rule based optimization

Everything above is same except the statement "Note" at the bottom.  

I don't understand what does that "Note:" mean and why it is different?

I checked the optimizer_mode parameter in both the databases and it says "CHOOSE".

Thanks,

 


Followup   April 19, 2006 - 5pm Central time zone:

... The objects in both the databases are IDENTICAL.  Even the data too. ....

really, you backed up the database and restored it?



you gathered statistics in the one database and are using the cost based optimizer (the one that 
has the rows column filled in), or you have set the optimizer mode to rule in one and not the 
other, or you have set the optimizer mode to all/first_rows in one and choose in the other, or .... 
(eg: something is hugely different)

the other database is using the RBO (rule based optimizer)


these databases are hugely NOT THE SAME, very much NOT IDENTICAL. 

4 stars follow up,   April 20, 2006 - 10am Central time zone
Reviewer: A reader 
YOu are absolutely right.  The schema where the plan shows rows column as null was not analyzed. 

That is why "Note: rule based optimization" at the bottom of the plan. 

On other schema where the rows column is filled up with values, what does the Note cpu costing is 
off mean?

Thanks, 


Followup   April 20, 2006 - 12pm Central time zone:

means you are in 9i whereby cpu costing (on in 10g by default) is off.  I different computation 
method for the cost based optimizer. 

5 stars Reading Explain Plan   May 5, 2006 - 4pm Central time zone
Reviewer: Sanji from WI, US
Hello Tom,

This is for academic purposes.
I am trying to interpret the following explain plan. (The query though, needs to be changed) 

SELECT     :B1 , 'NULL',PARENT_TABLE_NAME,PARENT_COLUMN_NAME,CHILD_TABLE_NAME,CHILD_COLUMN_NAME 
 FROM     CNTESTBED.SDE_BUILD_FK_CONSTRAINTS T , 
    CNTESTBED.SDE_WORK_TAB_COLUMNS P1 , 
    CNTESTBED.SDE_WORK_TAB_COLUMNS P2 
  WHERE BUILD_ID = :B2 
   AND  P1.TABLE_NAME = T.PARENT_TABLE_NAME 
   AND  P1.COLUMN_NAME = T.PARENT_COLUMN_NAME 
   AND  P1.REQUEST_ID = :B1 
   AND  P2.TABLE_NAME = T.CHILD_TABLE_NAME 
   AND  P2.COLUMN_NAME = T.CHILD_COLUMN_NAME 
   AND  P2.REQUEST_ID = :B1 
UNION 
SELECT     :B1 , 'NULL',PARENT_TABLE_NAME,PARENT_COLUMN_NAME,CHILD_TABLE_NAME,CHILD_COLUMN_NAME 
 FROM     CNTESTBED.SDE_BUILD_FK_CONSTRAINTS S , 
    CNTESTBED.SDE_WORK_TAB_COLUMNS P1 , 
    CNTESTBED.SDE_WORK_TAB_COLUMNS P2 
  WHERE BUILD_ID = :B3 
   AND  P1.TABLE_NAME = S.PARENT_TABLE_NAME 
   AND  P1.COLUMN_NAME = S.PARENT_COLUMN_NAME 
   AND  P1.REQUEST_ID = :B1
   AND  P2.TABLE_NAME = S.CHILD_TABLE_NAME 
   AND  P2.COLUMN_NAME = S.CHILD_COLUMN_NAME
   AND  P2.REQUEST_ID = :b1



---------------------------------------------------------------------------------------------------
| Id  | Operation             | Name                      | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                           |     2 |   240 |   404  (52)| 00:00:05 |
|   1 |  SORT UNIQUE          |                           |     2 |   240 |   404  (52)| 00:00:05 |
|   2 |   UNION-ALL           |                           |       |       |            |          |
|   3 |    NESTED LOOPS       |                           |     1 |   120 |   201   (2)| 00:00:03 |
|*  4 |     HASH JOIN         |                           |    14 |  1232 |   173   (3)| 00:00:03 |
|*  5 |      TABLE ACCESS FULL| SDE_BUILD_FK_CONSTRAINTS  |   665 | 37240 |    40   (5)| 00:00:01 |
|*  6 |      INDEX RANGE SCAN | SDE_WORK_TAB_COLUMNS_UNQ1 |  7957 |   248K|   132   (1)| 00:00:02 |
|*  7 |     INDEX RANGE SCAN  | SDE_WORK_TAB_COLUMNS_UNQ1 |     1 |    32 |     2   (0)| 00:00:01 |
|   8 |    NESTED LOOPS       |                           |     1 |   120 |   201   (2)| 00:00:03 |
|*  9 |     HASH JOIN         |                           |    14 |  1232 |   173   (3)| 00:00:03 |
|* 10 |      TABLE ACCESS FULL| SDE_BUILD_FK_CONSTRAINTS  |   665 | 37240 |    40   (5)| 00:00:01 |
|* 11 |      INDEX RANGE SCAN | SDE_WORK_TAB_COLUMNS_UNQ1 |  7957 |   248K|   132   (1)| 00:00:02 |
|* 12 |     INDEX RANGE SCAN  | SDE_WORK_TAB_COLUMNS_UNQ1 |     1 |    32 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------

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

   4 - access("P1"."TABLE_NAME"="T"."PARENT_TABLE_NAME" AND
              "P1"."COLUMN_NAME"="T"."PARENT_COLUMN_NAME")
   5 - filter("BUILD_ID"=TO_NUMBER(:B2))
   6 - access("P1"."REQUEST_ID"=TO_NUMBER(:B1))
   7 - access("P2"."REQUEST_ID"=TO_NUMBER(:B1) AND
              "P2"."TABLE_NAME"="T"."CHILD_TABLE_NAME" AND 
"P2"."COLUMN_NAME"="T"."CHILD_COLUMN_NAME")
   9 - access("P1"."TABLE_NAME"="S"."PARENT_TABLE_NAME" AND
              "P1"."COLUMN_NAME"="S"."PARENT_COLUMN_NAME")
  10 - filter("BUILD_ID"=TO_NUMBER(:B3))
  11 - access("P1"."REQUEST_ID"=TO_NUMBER(:B1))
  12 - access("P2"."REQUEST_ID"=TO_NUMBER(:B1) AND
              "P2"."TABLE_NAME"="S"."CHILD_TABLE_NAME" AND 
"P2"."COLUMN_NAME"="S"."CHILD_COLUMN_NAME")

Would the the tree stucture be like this

        2
           / \    
          /   \
         /       \
        3        8
       / \       / \
      4   7   9  12
         / \     / \
        5   6   10 11

or would it be like this


        2
           / \    
          /   \
         /       \
        3        8
       / \       / \
      4   7   12  9
         / \         / \
        5   6       11  10



Thanks
Sanji 


5 stars Tree structure   May 24, 2006 - 3pm Central time zone
Reviewer: Sanji from Milwaukee, Wisconsin
I believe there was a mistake in the earlier post. 
Could you please clarify the doubt. Would the structure be like this

                2
               / \    
              /   \
             /     \
            3       8
           / \     / \
          4   7   9  12
         / \     /\
        5   6   10 11



or would it be like this


                2
               /\    
              /  \
             /    \
            3      8
           / \    / \
          4   7  12  9
         / \        / \
        5   6      11 10


Thanks
Sanji 


Followup   May 25, 2006 - 1pm Central time zone:

whats the difference conceptually between them...

same orders of steps there. 

5 stars Interpreting Explain plan   May 30, 2006 - 2pm Central time zone
Reviewer: Sanji from WI, US
:) That's precisely what i was trying to figure out. Checked with Oracle Documentation and 
Effective Oracle by Design, but there too the examples are the same, mentioned in this forum 
(unless until i missed out on other examples).
Could you please direct me to the link/ study material where i can get information regarding 
interpreting "complex" explain plans.

Regards
Sanji 


Followup   May 30, 2006 - 6pm Central time zone:

but this isn't "complex" - it is a simple tree?

with hash joins, we can decide to change the order mid-stream (driving table) so top or bottom 
doesn't really matter technically.

 

5 stars nvl function used on primary keys   June 7, 2006 - 7am Central time zone
Reviewer: A reader 
Hi

I am checking SQL statements in our application which performs very badly. I have identified the 
problem but I cant get a solution to my developers, hoping you could throw some lights.

The problem is in the application the users are forced to enter some values in a forms but not all 
of values are compulsory, this leaded to a situation, for example using EMP table, to query that 
table our developers did this

select *
from emp
where empno = nvl(?, empno)
and ename = nvl(?, ename);

the NVL function changes the execution plan dramatically. Obviously this doesnt seem logical, empno 
is a primary key how on earth will it ever get null values? The answer my developers gave me is 
that sometimes users are not forced to enter all primary key values.

How can we overcome this problem? Should we use NVL in the application? (Before sending to the SQL 
statement) 
 


4 stars Wrong execution plan after analyze table   June 7, 2006 - 2pm Central time zone
Reviewer: Branka from VA, USA
Tom,
I have strange situation, and hope that you can help me.
I have 2 tables. If I delete statistics on both of them, query that join them run 00:00:00.02 sec.
If I analyze "big" table, it run same time, and same execution plan.
When I analyze second table, it run 00:00:08.08 sec, and use full table scan on "big" (3,5 mil 
records) table.
It is in DEV, and nobody use any of this tables.

14:00:10 pacr@PACRD> 
14:00:11 pacr@PACRD> 
14:00:11 pacr@PACRD> 
14:00:11 pacr@PACRD> 
14:00:11 pacr@PACRD> 
14:00:11 pacr@PACRD> exec  DBMS_STATS.DELETE_TABLE_STATS ( ownname=>'PACR', tabname 
=>'P_SECURITY_REVIEW_QUEUE')

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.01
14:00:31 pacr@PACRD> exec  DBMS_STATS.DELETE_TABLE_STATS ( ownname=>'PACR', tabname 
=>'P_APPLICATION')

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.01
14:00:57 pacr@PACRD> SELECT count(a.APPLICATION_SERIAL_NUMBER)
14:01:02   2     FROM   p_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
14:01:02   3     ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
14:01:02   4     WHERE    ap.SECURITY_STATUS = 2
14:01:02   5     AND ( a.assignment_classification = 'E')
14:01:02   6     AND ( a.FK_SL_LANG_CD = 'EN')
14:01:03   7  /

COUNT(A.APPLICATION_SERIAL_NUMBER)
----------------------------------
                              1963

Elapsed: 00:00:00.02

14:01:10 pacr@PACRD> 
14:01:10 pacr@PACRD> explain plan for
14:01:22   2  SELECT count(a.APPLICATION_SERIAL_NUMBER)
14:01:22   3     FROM   p_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
14:01:22   4     ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
14:01:22   5     WHERE    ap.SECURITY_STATUS = 2
14:01:22   6     AND ( a.assignment_classification = 'E')
14:01:22   7     AND ( a.FK_SL_LANG_CD = 'EN')
14:01:23   8  /

Explained.

Elapsed: 00:00:00.00
14:01:24 pacr@PACRD> 
14:01:24 pacr@PACRD> select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
-------------------------------

------------------------------------------------------------------------------------------
| Id  | Operation                     |  Name                    | Rows  | Bytes | Cost  |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                          |       |       |       |
|   1 |  SORT AGGREGATE               |                          |       |       |       |
|   2 |   NESTED LOOPS                |                          |       |       |       |
|*  3 |    TABLE ACCESS FULL          | P_SECURITY_REVIEW_QUEUE  |       |       |       |
|*  4 |    TABLE ACCESS BY INDEX ROWID| P_APPLICATION            |       |       |       |
|*  5 |     INDEX UNIQUE SCAN         | PK_P_APPLICATION         |       |       |       |
------------------------------------------------------------------------------------------

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

   3 - filter(TO_NUMBER("AP"."SECURITY_STATUS")=2)
   4 - filter("A"."FK_SL_LANG_CD"='EN' AND "A"."ASSIGNMENT_CLASSIFICATION"='E')
   5 - access("AP"."APPLICATION_SERIAL_NUMBER"="A"."APPLICATION_SERIAL_NUMBER")

Note: rule based optimization

20 rows selected.

Elapsed: 00:00:00.05
14:01:40 pacr@PACRD> exec dbms_stats.gather_table_stats( ownname=>'PACR', tabname 
=>'P_APPLICATION', cascade=>true ,method_opt => 'FOR ALL COLUMNS SIZE AUTO')

PL/SQL procedure successfully completed.

Elapsed: 00:09:31.05
14:11:48 pacr@PACRD> 
14:11:48 pacr@PACRD> 
14:11:49 pacr@PACRD> SELECT count(a.APPLICATION_SERIAL_NUMBER)
14:14:05   2     FROM   p_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
14:14:05   3     ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
14:14:05   4     WHERE    ap.SECURITY_STATUS = 2
14:14:05   5     AND ( a.assignment_classification = 'E')
14:14:05   6     AND ( a.FK_SL_LANG_CD = 'EN');

COUNT(A.APPLICATION_SERIAL_NUMBER)
----------------------------------
                              1963

Elapsed: 00:00:00.02

14:14:09 pacr@PACRD> explain plan for
14:14:16   2  SELECT count(a.APPLICATION_SERIAL_NUMBER)
14:14:17   3     FROM   p_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
14:14:17   4     ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
14:14:17   5     WHERE    ap.SECURITY_STATUS = 2
14:14:17   6     AND ( a.assignment_classification = 'E')
14:14:17   7     AND ( a.FK_SL_LANG_CD = 'EN')
14:14:17   8  /

Explained.

Elapsed: 00:00:00.01
14:14:19 pacr@PACRD> select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
-------------------------------

------------------------------------------------------------------------------------------
| Id  | Operation                     |  Name                    | Rows  | Bytes | Cost  |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                          |     1 |    31 |    84 |
|   1 |  SORT AGGREGATE               |                          |     1 |    31 |       |
|   2 |   NESTED LOOPS                |                          |    39 |  1209 |    84 |
|*  3 |    TABLE ACCESS FULL          | P_SECURITY_REVIEW_QUEUE  |    39 |   663 |     6 |
|*  4 |    TABLE ACCESS BY INDEX ROWID| P_APPLICATION            |     1 |    14 |     2 |
|*  5 |     INDEX UNIQUE SCAN         | PK_P_APPLICATION         |     1 |       |     1 |
------------------------------------------------------------------------------------------

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

   3 - filter(TO_NUMBER("AP"."SECURITY_STATUS")=2)
   4 - filter("A"."ASSIGNMENT_CLASSIFICATION"='E' AND "A"."FK_SL_LANG_CD"='EN')
   5 - access("AP"."APPLICATION_SERIAL_NUMBER"="A"."APPLICATION_SERIAL_NUMBER")

Note: cpu costing is off

20 rows selected.

Elapsed: 00:00:00.05
14:14:34 pacr@PACRD> exec dbms_stats.gather_table_stats( ownname=>'PACR', tabname 
=>'P_SECURITY_REVIEW_QUEUE', cascade=>true ,method_opt => 'FOR ALL COLUMNS SIZE AUTO')

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.07
14:17:02 pacr@PACRD> 
14:17:02 pacr@PACRD>  SELECT count(a.APPLICATION_SERIAL_NUMBER)
14:17:11   2      FROM   p_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
14:17:11   3      ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
14:17:11   4      WHERE    ap.SECURITY_STATUS = 2
14:17:11   5      AND ( a.assignment_classification = 'E')
14:17:11   6     AND ( a.FK_SL_LANG_CD = 'EN')
14:17:11   7  
14:17:12 pacr@PACRD> /

COUNT(A.APPLICATION_SERIAL_NUMBER)
----------------------------------
                              1963

Elapsed: 00:00:08.08
14:17:22 pacr@PACRD> explain plan for
14:17:31   2   SELECT count(a.APPLICATION_SERIAL_NUMBER)
14:17:32   3      FROM   p_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
14:17:32   4      ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
14:17:32   5      WHERE    ap.SECURITY_STATUS = 2
14:17:32   6      AND ( a.assignment_classification = 'E')
14:17:32   7     AND ( a.FK_SL_LANG_CD = 'EN')
14:17:32   8  /

Explained.

Elapsed: 00:00:00.00
14:17:33 pacr@PACRD> select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
-------------------------------

---------------------------------------------------------------------------------
| Id  | Operation            |  Name                    | Rows  | Bytes | Cost  |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |                          |     1 |    25 |  3206 |
|   1 |  SORT AGGREGATE      |                          |     1 |    25 |       |
|*  2 |   HASH JOIN          |                          |  4097 |   100K|  3206 |
|*  3 |    TABLE ACCESS FULL | P_SECURITY_REVIEW_QUEUE  |  4097 | 45067 |     6 |
|*  4 |    TABLE ACCESS FULL | P_APPLICATION            |   570K|  7797K|  3126 |
---------------------------------------------------------------------------------

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

   2 - access("AP"."APPLICATION_SERIAL_NUMBER"="A"."APPLICATION_SERIAL_NUMBER")
   3 - filter(TO_NUMBER("AP"."SECURITY_STATUS")=2)
   4 - filter("A"."ASSIGNMENT_CLASSIFICATION"='E' AND "A"."FK_SL_LANG_CD"='EN')

Note: cpu costing is off

19 rows selected.

Elapsed: 00:00:00.05
14:17:40 pacr@PACRD> spool off

 


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

are the estimated cardinalities even close the "real"

suggest you do not use method opt at all to start. 

3 stars Wrong execution plan after analyze table   June 7, 2006 - 4pm Central time zone
Reviewer: Branka from VA,USA
It didn't help.
Why not to use method opt at all ?
What else can I try (other than not to have statistics on that table)?
Thanks
Branka

 16:08:20 pacr@PACRD> exec  DBMS_STATS.DELETE_TABLE_STATS ( ownname=>'PACR', tabname 
=>'P_SECURITY_REVIEW_QUEUE')

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
16:08:22 pacr@PACRD> exec dbms_stats.gather_table_stats( ownname=>'PACR', tabname 
=>'P_SECURITY_REVIEW_QUEUE', cascade=>true )

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.04
16:08:35 pacr@PACRD> SELECT count(a.APPLICATION_SERIAL_NUMBER)
16:08:41   2     FROM   p_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
16:08:41   3     ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
16:08:41   4    WHERE    ap.SECURITY_STATUS = 2
16:08:41   5    AND ( a.assignment_classification = 'E')
16:08:41   6    AND ( a.FK_SL_LANG_CD = 'EN')
16:08:41   7  /

COUNT(A.APPLICATION_SERIAL_NUMBER)
----------------------------------
                              1963

Elapsed: 00:00:07.09
16:08:50 pacr@PACRD> select count(*) from p_APPLICATION;

  COUNT(*)
----------
   3533001

Elapsed: 00:00:02.07
select count(*) from p_APPLICATION
where assignment_classification = 'E'
and FK_SL_LANG_CD = 'EN';

  COUNT(*)
----------
    570487

select count(*) from P_SECURITY_REVIEW_QUEUE
where SECURITY_STATUS = 2;

  COUNT(*)
----------
      4097
 


Followup   June 7, 2006 - 5pm Central time zone:

can you please tell us if the estimated cardinalities on each step of the plan *are anywhere close 
to accurate*.


 

3 stars Wrong execution plan after analyze table   June 7, 2006 - 5pm Central time zone
Reviewer: Branka from VA, USA
Estimated cardinalities are accurate. I put all selects in my respond. 


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

umm, well if one plan says "39" and the other says "4097"

?  

It is estimating:

|*  4 |    TABLE ACCESS FULL | P_APPLICATION            |   570K|  7797K|  3126 
|
---------------------------------------------------------------------------------


   4 - filter("A"."ASSIGNMENT_CLASSIFICATION"='E' AND "A"."FK_SL_LANG_CD"='EN')


does that where clause retrieve 570,000 records or ?? 

4 stars Wrong execution plan after analyze table   June 7, 2006 - 6pm Central time zone
Reviewer: branka from VA, USA
First plan is without statistics on P_SECURITY_REVIEW_QUEUE and P_APPLICATION table.
Second plan is with statistics on P_APPLICATION table.
Third plan is with statistics on both table.
Third plan has all cardinality correct.

pacr@PACRD> select count(*) from p_APPLICATION
  2  where assignment_classification = 'E'
  3  and FK_SL_LANG_CD = 'EN';


  COUNT(*)
----------
    570487
 


Followup   June 7, 2006 - 7pm Central time zone:

so, is 39 or 4907 right for that other predicate? 

3 stars Wrong execution plan   June 7, 2006 - 7pm Central time zone
Reviewer: Branka from VA USA
4097 is correct value for other predicate.

3 - filter(TO_NUMBER("AP"."SECURITY_STATUS")=2)

select count(*) from P_SECURITY_REVIEW_QUEUE
where SECURITY_STATUS = 2;

  COUNT(*)
----------
      4097
 


Followup   June 7, 2006 - 8pm Central time zone:

ok, can we see a tkprof of both the "good and the bad" 

5 stars nvl function used on primary keys   June 8, 2006 - 3am Central time zone
Reviewer: A reader 
Hi

I have read the link you posted but I cant find useful information from it? Do you mean we should 
use dynamic SQL?

TIA 


Followup   June 8, 2006 - 9am Central time zone:

yes.

It shows how to dynamically construct the predicate based on the non-null inputs to a query, using 
binding. 

3 stars Wrong execution plan   June 8, 2006 - 2pm Central time zone
Reviewer: Branka from VA, USA
"GOOD"
SELECT count(a.APPLICATION_SERIAL_NUMBER)
   FROM   p_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
     ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
   WHERE    ap.SECURITY_STATUS = 2
     AND ( a.assignment_classification = 'E')
    AND ( a.FK_SL_LANG_CD = 'EN')

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          0          0           0
Fetch        2      0.11       0.10          0      12338          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.12       0.11          0      12338          0           1

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

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE 
   1963   NESTED LOOPS  
   4097    TABLE ACCESS FULL P_SECURITY_REVIEW_QUEUE 
   1963    TABLE ACCESS BY INDEX ROWID P_APPLICATION 
   4096     INDEX UNIQUE SCAN PK_P_APPLICATION (object id 41898)


Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       2        0.00          0.00
  SQL*Net message from client                     2       15.10         15.10


"BAD"


SELECT count(a.APPLICATION_SERIAL_NUMBER)
   FROM   p_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
     ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
   WHERE    ap.SECURITY_STATUS = 2
     AND ( a.assignment_classification = 'E')
    AND ( a.FK_SL_LANG_CD = 'EN')

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      8.59      11.75      28264      32310          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      8.59      11.76      28264      32310          0           1

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

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE 
   1963   HASH JOIN  
   4097    TABLE ACCESS FULL OBJ#(41896) 
 570487    TABLE ACCESS FULL OBJ#(41895) 


Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       2        0.00          0.00
  db file scattered read                       2445        0.03          3.84
  db file sequential read                       606        0.01          0.33
  SQL*Net message from client                     2       27.81         27.81

 


Followup   June 8, 2006 - 3pm Central time zone:

ok, what are your optimizer related parameters set to (how about a list of all non-default ones) 

3 stars wrong execution plan   June 8, 2006 - 4pm Central time zone
Reviewer: Branka from VA, USA
Paramater Name            Value    
aq_tm_processes            1    
audit_file_dest            XXXXXXX    
audit_trail            OS    
background_dump_dest        XXXXXX
compatible            9.2.0.0.0    
control_files            XXXXXXX
core_dump_dest            XXXXXX
db_block_size            8192    
db_cache_size            100663296    
db_domain            FALSE    
db_file_multiblock_read_count    16    
db_name                XXXX    
dblink_encrypt_login        TRUE    
fast_start_mttr_target        300    
hash_join_enabled        TRUE    
instance_name            XXXX    
java_pool_size            83886080    
job_queue_processes        10    
large_pool_size            16777216    
open_cursors            300    
pga_aggregate_target        25165824    
processes            150    
query_rewrite_enabled        FALSE    
remote_login_passwordfile    EXCLUSIVE    
remote_os_authent        FALSE    
shared_pool_size        285212672    
sort_area_size            524288    
star_transformation_enabled    FALSE    
timed_statistics        TRUE    
undo_management            AUTO    
undo_retention            10800    
undo_tablespace            UNDOTBS1    
user_dump_dest            XXXX
utl_file_dir            XXXX
 


Followup   June 8, 2006 - 4pm Central time zone:

Ok, in this case - everything looks "OK" - it did the right thing, but right now it believes all IO 
is going to be physical IO (and it is the physical IO that is the big time sink here).

It sees the 5,000 index range scans+table access by index rowid steps and says "cheaper to full 
scan", but in your case - it is not.

Have you any system statistics (NOT stats on sys owned tables, but statistics on the cpu speed, 
single block IO, multiblock IO times?)

Are you predominantly "a transactional system" or "a warehouse/reporting system"? 

4 stars Wrong execution plan   June 8, 2006 - 4pm Central time zone
Reviewer: Branka from VA, USA
I don't have any system statistics. 
We are predominantly "a transactional system"
I don't understand, why it make wrong decition only on this 2 tables, out of 200-300 tables.
Do you suggest to collect system statistics? 


Followup   June 8, 2006 - 7pm Central time zone:

wait, one more thing I'd like to see 

the plan with statistics on both, but using a first_rows or INDEX/USE_NL hint to get the prefered 
plan - to compare to the default.


It would be interesting to TEST system statistics 
and we might, maybe, consider adjusting optimizer_index* parameters. 

5 stars Wrong execution plan   June 8, 2006 - 4pm Central time zone
Reviewer: Branka from VA USA
I change optimizer_index_cost_adj        and optimizer_index_caching,
and see result.

pacr@PACRD> 
pacr@PACRD> SELECT count(a.APPLICATION_SERIAL_NUMBER)
  2     FROM   p_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
  3       ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
  4     WHERE    ap.SECURITY_STATUS = 2
  5       AND ( a.assignment_classification = 'E')
  6      AND ( a.FK_SL_LANG_CD = 'EN')
  7  
pacr@PACRD> 
pacr@PACRD> set time on
16:26:48 pacr@PACRD> set timing on
16:26:51 pacr@PACRD> /


COUNT(A.APPLICATION_SERIAL_NUMBER)
----------------------------------
                              1963

Elapsed: 00:00:08.01
16:27:01 pacr@PACRD> 
16:27:01 pacr@PACRD> 
16:27:01 pacr@PACRD> 
16:27:01 pacr@PACRD> 
16:27:01 pacr@PACRD> 
16:27:01 pacr@PACRD> 
16:27:01 pacr@PACRD> 
16:27:01 pacr@PACRD> 
16:27:01 pacr@PACRD> 
16:27:01 pacr@PACRD> alter session set optimizer_index_caching =40
16:27:29   2  ;

Session altered.

Elapsed: 00:00:00.00
16:27:31 pacr@PACRD> alter session set optimizer_index_cost_adj        =40;

Session altered.

Elapsed: 00:00:00.00
16:27:40 pacr@PACRD> SELECT count(a.APPLICATION_SERIAL_NUMBER)
16:27:43   2  FROM   p_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
16:27:47   3    ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
16:27:47   4  WHERE    ap.SECURITY_STATUS = 2
16:27:47   5    AND ( a.assignment_classification = 'E')
16:27:47   6   AND ( a.FK_SL_LANG_CD = 'EN');


COUNT(A.APPLICATION_SERIAL_NUMBER)
----------------------------------
                              1963
 


Followup   June 8, 2006 - 8pm Central time zone:

yes, i know that will do it, but.... it affects lots of (every) other plans too. 

5 stars Wrong execution plan   June 8, 2006 - 4pm Central time zone
Reviewer: Branka from VA, USA
Forgot timing row.
16:27:40 pacr@PACRD> SELECT count(a.APPLICATION_SERIAL_NUMBER)
16:27:43   2  FROM   p_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
16:27:47   3    ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
16:27:47   4  WHERE    ap.SECURITY_STATUS = 2
16:27:47   5    AND ( a.assignment_classification = 'E')
16:27:47   6   AND ( a.FK_SL_LANG_CD = 'EN');


COUNT(A.APPLICATION_SERIAL_NUMBER)
----------------------------------
                              1963

Elapsed: 00:00:00.03
16:27:49 pacr@PACRD> explain plan for 
16:29:41   2  SELECT count(a.APPLICATION_SERIAL_NUMBER)
16:29:49   3  FROM   p_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
16:29:53   4    ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
16:29:53   5  WHERE    ap.SECURITY_STATUS = 2
16:29:53   6    AND ( a.assignment_classification = 'E')
16:29:53   7   AND ( a.FK_SL_LANG_CD = 'EN')
16:29:53   8  /

Explained.

Elapsed: 00:00:00.00
16:29:54 pacr@PACRD> select * from table(dbms_xplan.display());


PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------


------------------------------------------------------------------------------------------
| Id  | Operation                     |  Name                    | Rows  | Bytes | Cost  |
-------------------------------------------------------