Skip to Main Content
  • Questions
  • Tuning SQL Statements Using Explain Plan

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Vinod.

Asked: May 20, 2000 - 4:51 am UTC

Last updated: October 21, 2019 - 6:28 am UTC

Version: 8i

Viewed 50K+ times! This question is

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 Connor 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:

</code> http://docs.oracle.com/cd/B10501_01/server.920/a96533/ex_plan.htm#16972

In fact, the entire document:

http://docs.oracle.com/cd/B10501_01/server.920/a96533/toc.htm <code>

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


Addenda: Updated links to recent versions of the documentation here:

https://docs.oracle.com/en/database/oracle/oracle-database/19/tgdba/index.html

Rating

  (376 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Tuning in development, test, production

Schesser, April 23, 2002 - 12:16 pm UTC

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.

Tom Kyte
April 23, 2002 - 1:27 pm UTC

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.


Query plan chaging question?

William, April 28, 2002 - 10:15 pm UTC

Tom,

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

Tom Kyte
April 29, 2002 - 7:29 am UTC

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)



Mike, April 29, 2002 - 12:21 pm UTC

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)
 

Tom Kyte
April 29, 2002 - 12:59 pm UTC

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)
 

Rajiv, August 08, 2002 - 10:35 am UTC

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.



Tom Kyte
August 08, 2002 - 1:08 pm UTC

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



A reader, August 08, 2002 - 1:42 pm UTC

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. 

Tom Kyte
August 08, 2002 - 2:44 pm UTC

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.


A reader, August 08, 2002 - 4:07 pm UTC

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.

Tom Kyte
August 09, 2002 - 7:58 am UTC

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)

A reader, August 10, 2002 - 5:12 pm UTC

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.

Tom Kyte
August 11, 2002 - 9:31 am UTC

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

Explain Plan document.

Kashif, January 16, 2003 - 1:38 pm UTC

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

Tom Kyte
January 16, 2003 - 7:48 pm UTC

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.


Fabulous!

Kashif, January 17, 2003 - 6:08 pm UTC

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

Tom Kyte
January 17, 2003 - 6:34 pm UTC

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




For anyone interested...

Kashif, January 28, 2003 - 1:06 pm UTC

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

2 Points

Robert, March 26, 2003 - 12:56 pm UTC

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

A reader, November 07, 2003 - 5:12 pm UTC

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
 

Tom Kyte
November 07, 2003 - 6:05 pm UTC

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

Reading Explain Plan

A reader, November 07, 2003 - 6:19 pm UTC

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

Tom Kyte
November 08, 2003 - 10:12 am UTC

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.

Forth coming book

A reader, November 07, 2003 - 6:25 pm UTC

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

Tom Kyte
November 08, 2003 - 10:12 am UTC

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

cpu costing

Prince, November 08, 2003 - 6:13 am UTC

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. 

Tom Kyte
November 08, 2003 - 10:24 am UTC

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.


Cardinality in EXPLAIN PLAN

A reader, November 08, 2003 - 9:36 am UTC

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>
 

Tom Kyte
November 08, 2003 - 10:28 am UTC

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

Re: cpu costing

Prince Faran, November 09, 2003 - 2:52 am UTC

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. 

Tom Kyte
November 09, 2003 - 7:05 am UTC


it just had less "functionality"

EXPLAIN PLAN Restrictions

Kamal Kishore, November 18, 2003 - 8:53 am UTC

Hi Tom,
In the following document at
</code> http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96533/ex_plan.htm#15821 <code>

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,


Tom Kyte
November 21, 2003 - 7:52 am UTC

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.

I think the response could do with some extra info

Gary, November 23, 2003 - 5:16 pm UTC

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

Unique scans in a NESTED Loop

A reader, December 15, 2003 - 6:25 pm UTC

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

Tom Kyte
December 16, 2003 - 7:14 am UTC

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. 

Pls help

A reader, December 16, 2003 - 1:39 pm UTC

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?

Tom Kyte
December 16, 2003 - 2:36 pm UTC

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.

Poor clustering factor

A reader, December 16, 2003 - 5:38 pm UTC

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.

Tom Kyte
December 16, 2003 - 6:57 pm UTC

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

 

Pls. clarify

A reader, December 16, 2003 - 8:34 pm UTC

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

Tom Kyte
December 17, 2003 - 6:41 am UTC

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.

Tuning???

A reader, December 17, 2003 - 2:56 pm UTC

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?

Tom Kyte
December 18, 2003 - 9:13 am UTC

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)
 
 
<b>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:</b>
 
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

<b>ahh, now it got it right.  good data = better plans.  </b>

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. 

Execution Plan vs Explain Plan

A reader, December 19, 2003 - 3:55 pm UTC

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

Tom Kyte
December 20, 2003 - 9:33 am UTC

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.

AUTOTRACE

A reader, December 21, 2003 - 6:44 pm UTC

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

Tom Kyte
December 21, 2003 - 6:59 pm UTC

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.

Table not analyzed

A reader, December 21, 2003 - 7:27 pm UTC

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


Tom Kyte
December 21, 2003 - 7:53 pm UTC

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!

Pls. clarify

A reader, December 21, 2003 - 10:47 pm UTC

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?

Tom Kyte
December 22, 2003 - 9:06 am UTC

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.




How to determine if data colocated

A reader, January 06, 2004 - 12:14 pm UTC

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

Tom Kyte
January 06, 2004 - 2:08 pm UTC

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
 
 

Row proximity

A reader, January 06, 2004 - 8:05 pm UTC

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?

Tom Kyte
January 06, 2004 - 8:43 pm UTC

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.

can we ignore the exec plan?

steve, January 30, 2004 - 4:08 pm UTC

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


Tom Kyte
January 30, 2004 - 8:06 pm UTC

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"

Have you heard of this ... ?

Gabe, February 13, 2004 - 1:18 pm UTC

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

Tom Kyte
February 13, 2004 - 1:46 pm UTC

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"

Question on tuning

PRS, February 13, 2004 - 2:20 pm UTC

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


Tom Kyte
February 13, 2004 - 2:57 pm UTC

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

Answers to the questions you asked

PRS, February 13, 2004 - 3:43 pm UTC

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

Tom Kyte
February 13, 2004 - 4:10 pm UTC

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.




Howto explain plan sql with type casting ?

A reader, February 16, 2004 - 6:43 pm UTC

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



Tom Kyte
February 17, 2004 - 8:02 am UTC

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( <b>number_array(1,2,3)</b> 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.
 

Howto explain plan sql with type casting ?

A reader, February 17, 2004 - 9:20 am UTC

I did try a to_number(:c) ... guess that did not get automagically cast to a number_array with one element.

Thank you again.

question

PRS, February 17, 2004 - 1:47 pm UTC

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. 

Tom Kyte
February 17, 2004 - 3:04 pm UTC

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)


Answer

PRS, February 17, 2004 - 3:46 pm UTC

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

Tom Kyte
February 17, 2004 - 6:57 pm UTC

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.

questions

PRS, February 18, 2004 - 9:15 am UTC

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

Tom Kyte
February 18, 2004 - 8:53 pm UTC

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.

slow query in stored procedure but very fast as single SQL

A reader, February 23, 2004 - 2:41 pm UTC

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



Tom Kyte
February 23, 2004 - 4:56 pm UTC

can you show us

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

to start with.

here is the tkprof

A reader, February 24, 2004 - 6:25 am UTC

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



Tom Kyte
February 24, 2004 - 8:21 am UTC

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 ?

To "A reader"

Kim Berg Hansen, February 24, 2004 - 9:29 am UTC

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.



Ideal Value of COST

Dhrubo, April 13, 2004 - 7:33 am UTC

Hi Tom,
Is there any thing called "Ideal value of COST".Is this COST a function of CPU COST & IO COST?Please explain

Tom Kyte
April 13, 2004 - 8:17 am UTC

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.

Reading Explain plan

parag jayant patankar, May 12, 2004 - 7:27 am UTC

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

Tom Kyte
May 12, 2004 - 7:57 am UTC

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.

10g

SR, May 21, 2004 - 2:32 am UTC

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.

Tom Kyte
May 21, 2004 - 10:46 am UTC

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.

SR, May 24, 2004 - 1:28 am UTC

Thanks a million, Tom - That cleared all the clouds !

Thanks, but am I interpreting this right

A reader, June 01, 2004 - 6:14 am UTC

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.

Tom Kyte
June 01, 2004 - 8:44 am UTC

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.

Oh My Goodness !!

A reader, June 02, 2004 - 3:35 am UTC

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.



Tom Kyte
June 02, 2004 - 8:29 am UTC

correct.

Clarification

YenYang, June 03, 2004 - 8:44 am UTC

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)




Tom Kyte
June 03, 2004 - 1:15 pm UTC

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

Hash Join --Full Scan

READER, June 16, 2004 - 10:21 am UTC

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 .

Tom Kyte
June 16, 2004 - 1:04 pm UTC

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.

Question probably not understood

Logan Palanisamy, June 16, 2004 - 8:10 pm UTC

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.



Tom Kyte
June 17, 2004 - 7:58 am UTC

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)<b>
   2    1     INDEX (FAST FULL SCAN) OF 'T1_IDX' (NON-UNIQUE) (Cost=4 Card=10000000 Bytes=300000000)</b>
   3    1     TABLE ACCESS (FULL) OF 'T2' (Cost=9619 Card=10000000 Bytes=300000000)
 <b>
fast full scan cause index = skinnier table for this query.
 </b>
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)
 
<b>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</b> 
 
 
ops$tkyte@ORA9IR2> set autotrace off
 

Fast Full scan ...

Reader, June 17, 2004 - 7:42 am UTC

I would appreciate your views on a fast full scan vs FTS on making hask key on a 50 million row table.

Thanks

Tom Kyte
June 17, 2004 - 10:19 am UTC

see above, it just happens.

Why this

Goh Seong Hin, June 18, 2004 - 12:24 am UTC

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

Tom Kyte
June 18, 2004 - 10:36 am UTC

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"

same query, differet executing sys/system different plan, how so?!

A reader, July 01, 2004 - 7:41 am UTC

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?

Tom Kyte
July 01, 2004 - 11:13 am UTC

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.

how do you get the parse,fetch statistics

ramakrishna, July 07, 2004 - 10:59 pm UTC

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

Tom Kyte
July 08, 2004 - 8:11 am UTC

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.

A reader, July 29, 2004 - 7:39 am UTC

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




Tom Kyte
July 29, 2004 - 11:55 am UTC

so, how many cpus do you have.

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

employee table lookup

Sudhir, July 29, 2004 - 11:36 am UTC

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



Tom Kyte
July 29, 2004 - 1:26 pm UTC

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

My point is

A reader, July 29, 2004 - 9:46 pm UTC

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


Tom Kyte
July 30, 2004 - 7:34 am UTC

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

what have you improved here?

for the reason ...

A reader, July 30, 2004 - 8:03 pm UTC

you don't need to know which column the user specified key exists.

thanks

Tom Kyte
July 31, 2004 - 10:52 am UTC

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.

ok, here is one way

A reader, August 01, 2004 - 7:16 am UTC

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

Tom Kyte
August 01, 2004 - 11:05 am UTC

"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






How to do this?

A reader, August 20, 2004 - 3:14 pm UTC

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

Tom Kyte
August 21, 2004 - 11:09 am UTC

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.

How to do this?

A reader, August 21, 2004 - 10:38 am UTC

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

Tom Kyte
August 21, 2004 - 12:16 pm UTC

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.

How to do this?

A reader, August 21, 2004 - 11:37 pm UTC

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

Tom Kyte
August 22, 2004 - 8:18 am UTC

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.

A reader, August 22, 2004 - 9:31 am UTC

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

Tom Kyte
August 22, 2004 - 4:51 pm UTC

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.

db scattered file wait event

A reader, August 22, 2004 - 3:09 pm UTC

I tried the measurements you mention in

</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:4433887271030, <code>

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

Tom Kyte
August 22, 2004 - 5:08 pm UTC

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.

mbrc

A reader, August 22, 2004 - 8:38 pm UTC

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

Tom Kyte
August 23, 2004 - 7:29 am UTC

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"

mbrc

A reader, August 23, 2004 - 9:25 am UTC

"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

Tom Kyte
August 23, 2004 - 10:21 am UTC

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"




Different results with different execution plans

A reader, August 26, 2004 - 10:30 am UTC

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.

Tom Kyte
August 26, 2004 - 10:39 am UTC

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

15 Table Join

chetan, October 18, 2004 - 6:41 am UTC

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


Tom Kyte
October 18, 2004 - 8:54 am UTC

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




A reader, October 20, 2004 - 5:30 pm UTC

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



Tom Kyte
October 20, 2004 - 8:54 pm UTC

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.



A reader, November 05, 2004 - 1:22 pm UTC

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

Tom Kyte
November 05, 2004 - 5:40 pm UTC

create index on t(date_column)?

assuming that date_column is selective enough.

A reader, November 06, 2004 - 6:52 pm UTC

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
 

Tom Kyte
November 06, 2004 - 9:07 pm UTC

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)



A reader, November 07, 2004 - 4:57 pm UTC

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>  

Tom Kyte
November 07, 2004 - 5:15 pm UTC

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.

A reader, November 07, 2004 - 6:25 pm UTC

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

Tom Kyte
November 08, 2004 - 9:41 am UTC

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




A reader, November 23, 2004 - 11:45 pm UTC

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?



Tom Kyte
November 24, 2004 - 7:03 am UTC

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
 

A reader, November 24, 2004 - 9:51 am UTC

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?

Tom Kyte
November 24, 2004 - 10:19 am UTC

how would a MV help in this case?

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

Question about execution plan

POLINA, December 27, 2004 - 12:07 pm UTC

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 
 

Tom Kyte
December 27, 2004 - 12:23 pm UTC

because full scans are not evil.

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



I just didn't understand well the order of the above explain plan.

polina, December 27, 2004 - 3:26 pm UTC

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

Tom Kyte
December 27, 2004 - 4:40 pm UTC

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.


Continue question

Polina, December 28, 2004 - 2:28 am UTC

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



Tom Kyte
December 28, 2004 - 10:21 am UTC

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
</code> 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 <code>

a hash table may or may not fit into ram, depends on how big it is, how much ram you got.

Which should I believe?

Daniel, January 06, 2005 - 1:47 pm UTC

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
 

Tom Kyte
January 06, 2005 - 2:05 pm UTC

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"


 

I believe

Daniel, January 07, 2005 - 7:27 am UTC

Great. Thank you!

Just a clarification

A reader, January 19, 2005 - 3:35 pm UTC

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

Tom Kyte
January 20, 2005 - 10:04 am UTC

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.

I have a question on range and full scan

Raja, January 26, 2005 - 4:08 am UTC

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


Tom Kyte
January 26, 2005 - 8:51 am UTC

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

Tuning SQL query

Arya, February 14, 2005 - 12:00 pm UTC

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 

Tom Kyte
February 14, 2005 - 2:42 pm UTC

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

Tuning SQL query

Arya, February 14, 2005 - 1:31 pm UTC

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

Redundant predicates in the where clause

Logan Palanisamy, February 14, 2005 - 3:52 pm UTC

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;



Tom Kyte
February 14, 2005 - 6:01 pm UTC

well, technically, the same as:

select * from emp where emp_name is not null;




Tuning SQL

Arya, February 14, 2005 - 6:10 pm UTC

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

Tom Kyte
February 14, 2005 - 6:50 pm UTC

(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?)



Two digit years

Gary, February 14, 2005 - 7:57 pm UTC

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.

Tom Kyte
February 14, 2005 - 8:02 pm UTC

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.

Size of string changes the explain plan

Bipul, February 15, 2005 - 8:28 am UTC

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


Tom Kyte
February 15, 2005 - 3:32 pm UTC

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.

Data set returned is similar

bipul, February 16, 2005 - 9:17 am UTC

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



get explain plan from system table

Sean, February 16, 2005 - 5:17 pm UTC

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.



Tom Kyte
February 16, 2005 - 5:41 pm UTC

v$sql_plan is available in software written this century, but not last century (meaning, in 9i and above -- yes)

How many tables in a query is optimal

BK, March 07, 2005 - 10:06 am UTC

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

Tom Kyte
March 07, 2005 - 3:54 pm UTC

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.

Tunning SQL query

Thomas Varekat, March 18, 2005 - 10:06 am UTC

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)



Tom Kyte
March 18, 2005 - 10:44 am UTC

are you running this in toad?

Tunning Sql statements with Group by

Thomas Varekat, March 21, 2005 - 1:45 am UTC

Yes i am running the query in code.


Tuning Sql statements

Thomas Varekat, March 21, 2005 - 1:53 am UTC

Yes. I am running the sql query in toad. But the explain plan was from sql-plus

Tom Kyte
March 21, 2005 - 10:25 am UTC

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.

Explain plan question

veeresh, March 22, 2005 - 2:19 am UTC

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



Tom Kyte
March 22, 2005 - 11:09 am UTC

you cannot, they are two statements, explain plan is a statement thing.

nvl(deptno,0) - ugh.

Tuning Sql statements

Thomas Varekat, March 23, 2005 - 3:29 am UTC

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.

Tom Kyte
March 23, 2005 - 8:47 am UTC

tkprof it.

show us something "real" -- something we can see.

tkprof will show us the work performed.

analyze

safrin, March 23, 2005 - 9:20 am UTC

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.





Tom Kyte
March 23, 2005 - 9:27 am UTC

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.

Unique situation

Utpal, March 24, 2005 - 3:20 am UTC

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 ?

Tom Kyte
March 24, 2005 - 8:47 am UTC

test case?

tkprof results?

anything?

CBO vs RBO

Nitesh, March 31, 2005 - 6:41 am UTC

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

Tom Kyte
March 31, 2005 - 8:09 am UTC

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.

full table scan because of subquery

Sean Li, April 10, 2005 - 2:09 pm UTC

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>  

my query processing 31 records at a time only

prasad, April 29, 2005 - 4:57 pm UTC

In the package,one Query - each time processing 30 - 32 records only why ??

any performance issue ??


Tom Kyte
April 29, 2005 - 6:45 pm UTC

sorry, does not make sense, don't know what you are trying to say

Explain Plan On Update --Locks the table.

Neeraj Nagpal, May 09, 2005 - 12:36 pm UTC

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 
 

Tom Kyte
May 09, 2005 - 2:35 pm UTC

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" 

Thanks Very Much

Neeraj Nagpal, May 09, 2005 - 8:01 pm UTC

TOM, Thanks so much for your help!


Explain Plan Interpretation

Dennis, May 10, 2005 - 1:12 am UTC

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


Tom Kyte
May 10, 2005 - 8:06 am UTC

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.



dennis, May 16, 2005 - 4:22 am UTC

Thanks Tom

autotrace plan is dfferent comapred with tkprof

A reader, May 23, 2005 - 9:45 am UTC

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?

Tom Kyte
May 23, 2005 - 2:24 pm UTC

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

re-write SQL

Laurie Murray, May 24, 2005 - 9:39 am UTC

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)

Tom Kyte
May 24, 2005 - 12:58 pm UTC

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?

Here are the PK's.

Laurie Murray, May 24, 2005 - 3:49 pm UTC

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



Tom Kyte
May 24, 2005 - 4:11 pm UTC

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.

explain plan for and TKPROF

PINGU, July 28, 2005 - 7:22 am UTC

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?

Tom Kyte
July 28, 2005 - 9:13 am UTC

search this site for

"bind variable peeking"

Does CBO get it horribly wrong?

Marco Coletti, July 29, 2005 - 3:18 pm UTC

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


Tom Kyte
July 29, 2005 - 5:44 pm UTC

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.

Alberto Dell'Era, July 30, 2005 - 5:44 am UTC

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

CBO still wrong

Marco Coletti, August 02, 2005 - 5:30 pm UTC

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


Help required

Anil Pant, August 24, 2005 - 8:38 am UTC

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

Tom Kyte
August 24, 2005 - 2:09 pm UTC

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.



A reader, August 26, 2005 - 12:07 pm UTC

Thanks - Marco Coletti for letting us know about this.

CBO and number of disks

Sven, September 28, 2005 - 8:20 am UTC

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


Tom Kyte
September 28, 2005 - 10:23 am UTC

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.

What do you think about this ? Your opinion

A reader, October 04, 2005 - 4:31 pm UTC

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;

Tom Kyte
October 04, 2005 - 8:27 pm UTC

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

yes l's are variables...

A reader, October 05, 2005 - 9:05 am UTC


Tom Kyte
October 05, 2005 - 11:24 am UTC

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.

taking your suggesting!!!

A reader, October 05, 2005 - 11:28 am UTC

It will be left

Parse

Aru, October 19, 2005 - 7:11 pm UTC

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.

Tom Kyte
October 19, 2005 - 7:58 pm UTC

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.




Puzzled timings of similar query...

Shailesh Saraff, October 21, 2005 - 7:22 am UTC

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)

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

Tom Kyte
October 21, 2005 - 8:35 am UTC

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.

why toad shows correct plan and sqlplus doesnt

A reader, October 25, 2005 - 5:08 pm UTC

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?

Tom Kyte
October 26, 2005 - 11:32 am UTC

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.

explain plan

Parag J Patankar, November 10, 2005 - 6:00 am UTC

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

Tom Kyte
November 11, 2005 - 11:43 am UTC

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.

Explain plan output

Parag J Patankar, November 12, 2005 - 1:33 am UTC

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

Tom Kyte
November 12, 2005 - 10:54 am UTC

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.
 

this is cool stuff, may I have more please...

Kevin Meade, November 14, 2005 - 4:53 pm UTC

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

Tom Kyte
November 15, 2005 - 7:31 am UTC

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

Tuning revisited in context of a unique index

Devopam Mittra, November 14, 2005 - 5:35 pm UTC

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

Tom Kyte
November 15, 2005 - 7:33 am UTC

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?

If there is no one to one correspondance then I don't undertand this

Kevin Meade, November 15, 2005 - 5:22 pm UTC

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

Tom Kyte
November 16, 2005 - 8:32 am UTC

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)



Re:Tuning revisited in context of a unique index

Devopam Mittra, November 15, 2005 - 7:27 pm UTC

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

Tom Kyte
November 16, 2005 - 8:36 am UTC

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.

explain plan output in one line

Parag J Patankar, November 16, 2005 - 1:22 am UTC

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

Tom Kyte
November 16, 2005 - 9:00 am UTC

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.

Re:Tuning revisited in context of a unique index

Devopam Mittra, November 17, 2005 - 3:33 am UTC

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



Tom Kyte
November 17, 2005 - 8:12 am UTC

why is it a reasonable assumption?

What is the thoughts behind this assumption?


have you traced your application to see what is taking "long"?

Re:Tuning revisited in context of a unique index

Devopam Mittra, November 18, 2005 - 2:54 am UTC

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

Tom Kyte
November 18, 2005 - 10:45 am UTC

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



explain plan interpretation

dhamodharan.l, November 22, 2005 - 9:11 am UTC

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.

Tom Kyte
November 22, 2005 - 10:04 am UTC

ctl-f for

Reading an Explain Plan

I've described how to read a plan.


Index Access for a table

Vivek Sharma, November 25, 2005 - 9:18 am UTC

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



Tom Kyte
November 25, 2005 - 10:57 am UTC

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.



Sorry Tom....this is in continuation of my previous post

Vivek Sharma, November 25, 2005 - 11:07 am UTC

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


Tom Kyte
November 25, 2005 - 1:40 pm UTC

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.

reader

A reader, November 29, 2005 - 3:31 pm UTC

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.

Tom Kyte
November 30, 2005 - 11:02 am UTC

I did???? I mean that was the crux of the posting you cut this from - it goes through this step by step.

reader

A reader, November 30, 2005 - 11:12 am UTC

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

reader

A reader, December 10, 2005 - 1:55 am UTC

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




Tom Kyte
December 10, 2005 - 5:22 am UTC

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.

tkprof script

Reader, December 26, 2005 - 2:48 pm UTC

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


Tom Kyte
December 26, 2005 - 2:58 pm UTC

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

TKPROF

reader, December 26, 2005 - 11:01 pm UTC

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


Tom Kyte
December 27, 2005 - 9:37 am UTC

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.


 

THANK YOU !

reader, December 27, 2005 - 7:24 pm UTC


your user

Reader, January 11, 2006 - 7:50 am UTC

In the above script is your user identified externally .

Thanks ,



Tom Kyte
January 12, 2006 - 10:26 am UTC

yes? is that a problem?

Explain Plan

a, January 12, 2006 - 10:12 am UTC

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?

Tom Kyte
January 12, 2006 - 11:10 am UTC

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.

How to retrieve long running sql queries ran by a particular user at a past time period

A reader, January 13, 2006 - 2:17 pm UTC

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,

Tom Kyte
January 15, 2006 - 3:13 pm UTC

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.

Detecting query plan changes for selected queries

Menon, January 16, 2006 - 8:19 pm UTC

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 </code> https://portal.hotsos.com/products/laredo <code>

Btw, if there are other such existing products (esp. free ones), let me know.

Tom Kyte
January 16, 2006 - 9:04 pm UTC

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)

thanx...

Menon, January 17, 2006 - 1:24 am UTC

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

Tom Kyte
January 17, 2006 - 8:40 am UTC

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"

hmm..

Menon, January 17, 2006 - 1:14 pm UTC

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

User user-function instead????

A reader, January 20, 2006 - 10:05 am UTC

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

Tom Kyte
January 20, 2006 - 10:47 am UTC

are you sure the operation is done twice. if so, "how"

Reg. last query

A reader, January 20, 2006 - 11:57 am UTC

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

Tom Kyte
January 20, 2006 - 12:52 pm UTC

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.

Thank you

A reader, January 23, 2006 - 3:51 am UTC


elapsed time

Mehmood, January 27, 2006 - 5:10 am UTC

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.


Tom Kyte
January 27, 2006 - 8:41 am UTC

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"

elapsed time

Mehmood, January 28, 2006 - 2:49 am UTC

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.

Tom Kyte
January 28, 2006 - 1:02 pm UTC

why are you using parallel at all for such small stuff.

elapsed time

Mehmood, January 29, 2006 - 3:37 am UTC

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.

Tom Kyte
January 29, 2006 - 8:31 am UTC

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>

problem resolved

Mehmood, January 30, 2006 - 2:15 am UTC

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.


problem resolved

Mehmood, January 30, 2006 - 2:18 am UTC

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.


plan differerence when executing standalone versus PL/SQL

Menon, January 31, 2006 - 10:53 am UTC

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

Tom Kyte
January 31, 2006 - 3:31 pm UTC

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)

clarification of the above question

Menon, January 31, 2006 - 1:09 pm UTC

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?

Tom Kyte
January 31, 2006 - 3:37 pm UTC

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.

thanx!

Menon, January 31, 2006 - 4:15 pm UTC

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

Tom Kyte
February 01, 2006 - 2:26 am UTC

in 9ir2, plsql obeys the optimizer settting - you can set up a quick test to verify that it is happening for you.

Full table scans on the dual table

ravinder matte, January 31, 2006 - 5:55 pm UTC

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

Tom Kyte
February 01, 2006 - 2:31 am UTC

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?

thanx Tom!

Menon, February 01, 2006 - 5:37 am UTC

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

Have a little question about CBO behaviour

Dmytro, February 06, 2006 - 10:31 am UTC

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?

Tom Kyte
February 07, 2006 - 12:59 am UTC

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

<b>
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
</b>

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.

 

My stupid mistake....

Dmytro, February 08, 2006 - 6:52 am UTC

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.

Tom Kyte
February 08, 2006 - 8:14 am UTC

what are you binding in there - a string, or a DATE?

One more mistake... :)

A reader, February 08, 2006 - 6:57 am UTC

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)*/

About binding string or date

Dmytro, February 08, 2006 - 9:25 am UTC

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.


Tom Kyte
February 08, 2006 - 10:15 am UTC

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

?

Remote objects stats

Reader, February 13, 2006 - 7:22 am UTC

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?
 

Tom Kyte
February 13, 2006 - 8:44 am UTC

... If my query envolves remote tables (assume we use CBO) - does CBO look at remote
objects stats or not? ...

yes it does.

Explain Plan indicates full table scan

Bhaskar, February 17, 2006 - 8:33 am UTC

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

Tom Kyte
February 17, 2006 - 2:49 pm UTC

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.

Tuning HASH JOIN

Prerak Mehta, February 17, 2006 - 3:40 pm UTC

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

Tom Kyte
February 17, 2006 - 5:22 pm UTC

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"

Tuning Hash Joins

Prerak, February 17, 2006 - 4:02 pm UTC

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

Tuning Hash Joins

Prerak, February 20, 2006 - 3:54 pm UTC

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


Tom Kyte
February 21, 2006 - 7:24 am UTC

hmm.

crystal ball is broken, I cannot see your terminal, so no. I have no information for you.

Tuning Hash Joins

Prerak, February 20, 2006 - 3:56 pm UTC

Sorry for the above reply. Its only db file sequential reads....not the scattered reads.

Prerak

About: "a little question about CBO behaviour"

Dmytro, February 21, 2006 - 4:34 am UTC

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

Optimizer in PLAN_TABLE

ina, February 27, 2006 - 6:47 am UTC

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

Tom Kyte
February 27, 2006 - 7:17 am UTC

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.

"Simple" Queries on Dictionary Tables Have Slow Parsing

Jay, March 27, 2006 - 5:12 pm UTC

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

Tom Kyte
March 27, 2006 - 8:24 pm UTC

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

OK

A reader, March 28, 2006 - 3:45 am UTC

Hi Tom,
Could you please provide the LINK for TKPROF and
explain plan??

Queries on Dictionary Views Have Long Parsing Time

Jay, March 28, 2006 - 9:49 am UTC

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

Tom Kyte
March 28, 2006 - 4:08 pm UTC

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?

A reader, March 28, 2006 - 2:45 pm UTC

Guess the system tables are analyzed in both the databases

Tom Kyte
March 28, 2006 - 8:13 pm UTC

"guess"?

Re: Queries on Dictionary Views Have Long Parsing Time

Jay, March 29, 2006 - 9:25 am UTC

That's a good catch. Apparently they didn't use bind variables.

Aside from that, the stats on both DBs were collected.

Dynamic Optimization

Chris Slattery, April 10, 2006 - 12:05 pm UTC

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.


Tom Kyte
April 11, 2006 - 10:26 am UTC

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.

plan_table output,

A reader, April 19, 2006 - 2:50 pm UTC

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,



Tom Kyte
April 19, 2006 - 5:20 pm UTC

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

follow up,

A reader, April 20, 2006 - 10:26 am UTC

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,

Tom Kyte
April 20, 2006 - 12:30 pm UTC

means you are in 9i whereby cpu costing (on in 10g by default) is off. I different computation method for the cost based optimizer.

Reading Explain Plan

Sanji, May 05, 2006 - 4:01 pm UTC

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

Tree structure

Sanji, May 24, 2006 - 3:47 pm UTC

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

Tom Kyte
May 25, 2006 - 1:15 pm UTC

whats the difference conceptually between them...

same orders of steps there.

Interpreting Explain plan

Sanji, May 30, 2006 - 2:34 pm UTC

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

Tom Kyte
May 30, 2006 - 6:46 pm UTC

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.



nvl function used on primary keys

A reader, June 07, 2006 - 7:36 am UTC

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)


Wrong execution plan after analyze table

Branka, June 07, 2006 - 2:28 pm UTC

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



Tom Kyte
June 07, 2006 - 3:40 pm UTC

are the estimated cardinalities even close the "real"

suggest you do not use method opt at all to start.

Wrong execution plan after analyze table

Branka, June 07, 2006 - 4:17 pm UTC

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


Tom Kyte
June 07, 2006 - 5:38 pm UTC

can you please tell us if the estimated cardinalities on each step of the plan *are anywhere close to accurate*.




Wrong execution plan after analyze table

Branka, June 07, 2006 - 5:46 pm UTC

Estimated cardinalities are accurate. I put all selects in my respond.

Tom Kyte
June 07, 2006 - 6:36 pm UTC

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

Wrong execution plan after analyze table

branka, June 07, 2006 - 6:58 pm UTC

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


Tom Kyte
June 07, 2006 - 7:07 pm UTC

so, is 39 or 4907 right for that other predicate?

Wrong execution plan

Branka, June 07, 2006 - 7:12 pm UTC

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


Tom Kyte
June 07, 2006 - 8:24 pm UTC

ok, can we see a tkprof of both the "good and the bad"

nvl function used on primary keys

A reader, June 08, 2006 - 3:28 am UTC

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

Tom Kyte
June 08, 2006 - 9:08 am UTC

yes.

It shows how to dynamically construct the predicate based on the non-null inputs to a query, using binding.

Wrong execution plan

Branka, June 08, 2006 - 2:27 pm UTC

"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



Tom Kyte
June 08, 2006 - 3:41 pm UTC

ok, what are your optimizer related parameters set to (how about a list of all non-default ones)

wrong execution plan

Branka, June 08, 2006 - 4:02 pm UTC

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


Tom Kyte
June 08, 2006 - 4:07 pm UTC

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

Wrong execution plan

Branka, June 08, 2006 - 4:15 pm UTC

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?

Tom Kyte
June 08, 2006 - 7:59 pm UTC

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.

Wrong execution plan

Branka, June 08, 2006 - 4:32 pm UTC

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


Tom Kyte
June 08, 2006 - 8:00 pm UTC

yes, i know that will do it, but.... it affects lots of (every) other plans too.

Wrong execution plan

Branka, June 08, 2006 - 4:37 pm UTC

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 |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 25 | 1645 |
| 1 | SORT AGGREGATE | | 1 | 25 | |
| 2 | NESTED LOOPS | | 4097 | 100K| 1645 |
|* 3 | TABLE ACCESS FULL | P_SECURITY_REVIEW_QUEUE | 4097 | 45067 | 6 |
|* 4 | TABLE ACCESS BY INDEX ROWID| P_APPLICATION | 1 | 14 | 1 |
|* 5 | INDEX UNIQUE SCAN | PK_P_APPLICATION | 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

voodoo

voodoo, June 08, 2006 - 6:56 pm UTC

Maybe set optimizer_index_cacheing way up to 95 and see if it doesn't get rid of that full table scan. The general idea being a transaction oriented system would be more dependent on indexes.

Wrong execution plan

Branka, June 12, 2006 - 10:34 am UTC

So what is solution?
I tried same query in Production database (same data as in dev), but it doesn't want to change plan.
I tried with several optimizer_index_caching and optimizer_index_cost_adj, and it didn't help.

Wrong execution plan

Branka, June 13, 2006 - 1:57 pm UTC

What will happen with this query when we move to Oracle 10g?
I understand that all tables have to be analyzed in 10g.

Tom Kyte
June 13, 2006 - 5:03 pm UTC

dynamic sampling will kick in if you have not analyzed a table

Wrong execution plan

Branka, June 15, 2006 - 9:29 am UTC

Can you suggest what to do with this table for now? (table that create bad execution plan when have statistics).
Is it best to leave that table without statistics, than to setup monitoring parameter for all other tables, and than DBMS_JOB, that geather stale statistics?
What will be with that once when we move to 10g, where we don't have monitor parameter? Would I need to make sure that all tables are no monitor, or Oracle will do it for me?


Tom Kyte
June 15, 2006 - 4:34 pm UTC

have you tried histograms so it "understands" this really badly skewed data you have produced by using this "fake" date?

(do you have Jonathan Lewis book - Cost Based Oracle? You'll want it)

Wrong execution plan

Branka, June 19, 2006 - 12:19 pm UTC

histograms didn't help.
execute dbms_stats.gather_table_stats(ownname => 'PACR',tabname =>'P_APPLICATION', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE, method_opt => 'for all columns size auto', degree => DBMS_STATS.DEFAULT_DEGREE);

INDEX/USE_NL hint did help
explain plan for
2 SELECT /*+ INDEX (a, PK_P_APPLICATION) USE_NL (a b) */ count(a.APPLICATION_SERIAL_NUMBER)
3 FROM P_APPLICATION a join P_SECURITY_REVIEW_QUEUE ap
4 ON ap.APPLICATION_SERIAL_NUMBER = a.APPLICATION_SERIAL_NUMBER
5 WHERE ap.SECURITY_STATUS = 2
6 AND ( a.assignment_classification = 'E')
7 AND ( a.FK_SL_LANG_CD = 'EN')
8 /

Explained.

Elapsed: 00:00:00.00
pacr@PACRD> select * from table(dbms_xplan.display());


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

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 25 | 8200 |
| 1 | SORT AGGREGATE | | 1 | 25 | |
| 2 | NESTED LOOPS | | 4097 | 100K| 8200 |
|* 3 | TABLE ACCESS FULL | P_SECURITY_REVIEW_QUEUE | 4097 | 45067 | 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.04
pacr@PACRD> SELECT /*+ INDEX (a, PK_P_APPLICATION) USE_NL (a b) */ count(a.APPLICATION_SER
IAL_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');


COUNT(A.APPLICATION_SERIAL_NUMBER)
----------------------------------
1963

Elapsed: 00:00:00.01

Wrong excution plan

Branka, June 20, 2006 - 2:05 pm UTC

I attached wrong dbms_stats.
I wanted to say that this one didn't help.

execute dbms_stats.gather_schema_stats(
ownname => 'PACR',
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
method_opt => 'for all columns size skewonly',
degree => DBMS_STATS.DEFAULT_DEGREE);

One more question.
Does book Cost-Based Oracle Fundamentals apply to Oracle 10g database?

Tom Kyte
June 21, 2006 - 9:36 am UTC

well, is that the dbms_stats command I asked you to use?

yes, the book applies to 10g and before.

Number of rows fetched

Vikram Romeo, June 22, 2006 - 4:02 pm UTC

Hi Tom,

When a long running query is executing, how do we find out how many rows have been fetched so far. Is there any V$ table for finding that out

Appreciate your response.

Regards,
Vikram Romeo

Tom Kyte
June 22, 2006 - 4:21 pm UTC

the client fetching from the cursor can see that, but it is not really exposed outside of that client.

Number of rows fetched

Vikram Romeo, June 22, 2006 - 4:43 pm UTC

Thanks for the prompt response Tom,

But Iam just running a SQL query .. I am not opening any cursor explicitly in a pl/sql or something. Just a simple SQL.

I want to know whether my SQL is doing any activity or is it just "idle".

Any ideas Tom?

Regards,
Vikram Romeo

Tom Kyte
June 23, 2006 - 9:47 am UTC

select status from v$session

will return whether you are active, inactive, using a shared server (active) or not using a shared server and so on....

Tuning in development, test, production

Jdam, June 28, 2006 - 8:48 am UTC

Hi Tom, I have the same concerns about tuning sql in development and production for the facts already mention by Schesser, could you elaborate more about your answer "If you use CBO -- the plans can and will change. They can change from day to
day."

Thanks
Jdam

Tom Kyte
June 28, 2006 - 9:08 am UTC

what more can we say? The CBO develops plans based on statistics. As statistics change, plans will change.

Tuning in development, test, production

emob, June 28, 2006 - 10:16 am UTC

You suggested using:
Select status from v$session

I got back:
9:38:49 AM Start SQL Editor Execution ...
9:38:49 AM Processing ...
9:38:49 AM select status from v$session
9:38:49 AM *
9:38:49 AM ORA-00942: table or view does not exist
9:38:49 AM *** Script stopped due to error ***

Is this a rights problem?

Thanks.

Tom Kyte
June 28, 2006 - 10:34 am UTC

well, you sort of need to have access to the underlying view.

v$session is something you need to have been granted access to, and apparently, you have not been.

SQL stmt

Abid Ali khan, July 06, 2006 - 5:05 am UTC

hi tom, its Ali here, can u plz anser my Querry
there are 3 records
Number Country
8688686886 XYZ
8686886 ABC
68686886 CDF

so the Qierry is
U have to find all the data related to certain number,if u pass the number 868,v have to get first 2 records,


Tom Kyte
July 08, 2006 - 9:38 am UTC

"U" isn't here. I've yet to meet this "U" individual - big backlog of requests for their time.

Read up on "LIKE", you'll convert the number to a string and use like

where to_char(num) like :x || '%'

bind in 868

A reader, August 02, 2006 - 11:19 am UTC

Tom

I have a query plan from a SQL*Plus autotrace as following:

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=RULE
1 0 SORT (ORDER BY)
2 1 FILTER
3 2 TABLE ACCESS (BY INDEX ROWID) OF 'ACT_SCH_APPS_ALL'
4 3 INDEX (RANGE SCAN) OF 'ASA_SCHEME_OFFICE_LOT' (NON-U
NIQUE)

5 2 NESTED LOOPS
6 5 NESTED LOOPS
7 6 TABLE ACCESS (BY INDEX ROWID) OF 'PARTY_ROLES'
8 7 INDEX (UNIQUE SCAN) OF 'PARR_BRN_UK' (UNIQUE)
9 6 TABLE ACCESS (BY INDEX ROWID) OF 'CLAIM_EVENTS'
10 9 INDEX (RANGE SCAN) OF 'CLAE_PROI_IDX' (NON-UNIQU
E)

11 5 AND-EQUAL
12 11 INDEX (RANGE SCAN) OF 'INVO_APP_IDX' (NON-UNIQUE)
13 11 INDEX (RANGE SCAN) OF 'INVO_PARR_IDX' (NON-UNIQUE)


Now, am I correct in staing the first step to be executed is

8 7 INDEX (UNIQUE SCAN) OF 'PARR_BRN_UK' (UNIQUE)

That sounds incorrect because the actual query goes because it needs to do, first to supply the index with values :

3 2 TABLE ACCESS (BY INDEX ROWID) OF 'ACT_SCH_APPS_ALL'

The actual query which may be irrelevant in this case:


SELECT cdm_office.NAME
( cdm_farm.ao
( cdm_business.farm
( asa_claimant_id )))
office
, cdm_business.farm ( asa_claimant_id ) farm, asa_claimant_id brn
FROM act_sch_apps
WHERE asa_as_code = 'LMCM05'
AND asa_status IS NULL
AND NOT EXISTS (
SELECT -- ordered
NULL
FROM party_roles, involvements, claim_events
WHERE clae_proi_id = 291
AND invo_app_id = clae_app_id
AND invo_parr_id = parr_id
AND parr_brn = asa_claimant_id )
ORDER BY office, farm, brn


So why does the explain plan say it does Step
first?

8 7 INDEX (UNIQUE SCAN) OF 'PARR_BRN_UK' (UNIQUE)

Tom Kyte
August 02, 2006 - 12:33 pm UTC

it does this:

3 2 TABLE ACCESS (BY INDEX ROWID) OF 'ACT_SCH_APPS_ALL'
4 3 INDEX (RANGE SCAN) OF 'ASA_SCHEME_OFFICE_LOT' (NON-U
NIQUE)

and then filters that output using the output of the nested loops join query below it.

ctl-f for

1.3.3 Reading an Explain Plan

on this page.

A reader, August 02, 2006 - 2:49 pm UTC

2 1 FILTER
3 2 TABLE ACCESS (BY INDEX ROWID) OF 'ACT_SCH_APPS_ALL'

5 2 NESTED LOOPS

Tom,

Is this because for in order to do Step 2, it has to do Step 3 and Step 5 first (before Step 2).

But, because they are both at the same 'Rank' in relation to Step 2, the 'Top' one, i.e. Step 3 is done first.

For every row arriving from
3 2 TABLE ACCESS (BY INDEX ROWID)

Is 'supplied' to
5 2 NESTED LOOPS

and filtered at Step 2?

Tom Kyte
August 03, 2006 - 7:43 am UTC

top down.

build the tree and then using the "approach" outlined above:

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

extract sql and its explain plan,

A reader, August 02, 2006 - 5:03 pm UTC

We have an application that can run any time during the day.

We want to extract all the SQL's that runs within the application and generate the explain plan and store the file in some location.

Our database is 10gR1 and has AWR running in the background for every hour. I din't see the EXPLAIN PLAN in the text report.

What is the best way to do this? I am thinking of tkprof but starting tkprof right after the session starts is the trick.

Thanks,


Fetching Huge Records with Derived Field

Rahul Dutta, August 03, 2006 - 2:43 am UTC

Hi Tom,

I am facing this problem where I have to do some calculation on the data in a table with more than half million records and it is taking very long time. Please suggest some tips.
========================
The query as follows:
=========================
SELECT
A.LATEST_LAN_FLG,
A.CUSTOMER_KEY,
A.BUSS_CD,
A.LAN,
(SELECT COUNT(*) FROM MINING.CRASH_FACT C WHERE C.CUSTOMERID=A.CUSTOMER_KEY
AND C.DIMREPMONTH>=C.DIMSTARTMONTH AND C.DIMREPMONTH<=TO_CHAR(ADD_MONTHS(SYSDATE,-2),'YYYYMM'))SUM_LANS_MOB_BY_CP,

(SELECT COUNT(DISTINCT LAN ) FROM MINING.CRASH_MONTHLYFACT C WHERE C.CUSTOMERID=A.CUSTOMER_KEY
AND C.DIMDISBMONTH<=TO_CHAR(ADD_MONTHS(SYSDATE,-2),'YYYYMM'))CNT_LANS_ALL_BY_CP,

(SELECT MAX(D.TENOR) FROM MINING.CRASH_MONTHLYFACT D WHERE D.CUSTOMERID=A.CUSTOMER_KEY
AND D.DIMDISBMONTH<=TO_CHAR(ADD_MONTHS(SYSDATE,-2),'YYYYMM')) MAX_TENOR_BY_CP,

(SELECT COUNT(DISTINCT DIMREPMONTH) FROM MINING.CRASH_FACT C WHERE C.CUSTOMERID=A.CUSTOMER_KEY AND C.DIMREPMONTH>=C.DIMSTARTMONTH
AND C.DIMREPMONTH<=TO_CHAR(ADD_MONTHS(SYSDATE,-2),'YYYYMM') AND C.CURRBOUNCES>=1 )CNT_BOUNCE_TIMES_BY_CP ,

(SELECT COUNT(DISTINCT DIMREPMONTH) FROM MINING.CRASH_FACT C WHERE C.CUSTOMERID=A.CUSTOMER_KEY AND C.DIMREPMONTH>=C.DIMSTARTMONTH
AND C.DIMREPMONTH BETWEEN TO_CHAR(ADD_MONTHS(SYSDATE,-13),'YYYYMM') AND TO_CHAR(ADD_MONTHS(SYSDATE,-2),'YYYYMM') AND C.DIMCURRBKT=2)DPD_1_TO_30_BY_12MTH,

(SELECT COUNT(DISTINCT LAN) FROM MINING.CRASH_FACT C WHERE C.CUSTOMERID=A.CUSTOMER_KEY AND C.DIMREPMONTH>=C.DIMSTARTMONTH
AND C.DIMREPMONTH BETWEEN TO_CHAR(ADD_MONTHS(SYSDATE,-13),'YYYYMM') AND TO_CHAR(ADD_MONTHS(SYSDATE,-2),'YYYYMM'))CNT_LIVE_LAN_BY_12MTH,

(SELECT COUNT(*) FROM MINING.CRASH_FACT C WHERE C.LAN=A.LAN
AND C.DIMREPMONTH>=C.DIMSTARTMONTH AND C.DIMREPMONTH<=TO_CHAR(ADD_MONTHS(SYSDATE,-2),'YYYYMM'))FEEDER_LAN_MOB_BY_CP

FROM CRM.LOANS A
WHERE A.LATEST_LAN_FLG='Y'
AND A.BUSS_CD IN ('O','P','V')

=====================================
Plan is as follows The Cost is coming at 5005 (most of which is due to the FTS on CRM.LOAN Table. Can you please suggest some tips??
=====================================
SELECT STATEMENT, GOAL = CHOOSE 12087192 503633 5005 CHOOSE
SORT AGGREGATE 21 1
TABLE ACCESS BY GLOBAL INDEX ROWID 441 21 24 CRASH_FACT ANALYZED
INDEX RANGE SCAN 21 3 CRASHFACT_CUST ANALYZED
SORT GROUP BY 27 1
TABLE ACCESS BY INDEX ROWID 27 1 5 CRASH_MONTHLYFACT ANALYZED
INDEX RANGE SCAN 1 3 CRASH_MONTH1 ANALYZED
SORT AGGREGATE 18 1
TABLE ACCESS BY INDEX ROWID 18 1 5 CRASH_MONTHLYFACT ANALYZED
INDEX RANGE SCAN 1 3 CRASH_MONTH1 ANALYZED
SORT GROUP BY 24 1
TABLE ACCESS BY GLOBAL INDEX ROWID 72 3 24 CRASH_FACT ANALYZED
INDEX RANGE SCAN 21 3 CRASHFACT_CUST ANALYZED
SORT GROUP BY 24 1
FILTER
TABLE ACCESS BY GLOBAL INDEX ROWID 24 1 24 CRASH_FACT ANALYZED
INDEX RANGE SCAN 21 3 CRASHFACT_CUST ANALYZED
SORT GROUP BY 34 1
FILTER
TABLE ACCESS BY GLOBAL INDEX ROWID 102 3 24 CRASH_FACT ANALYZED
INDEX RANGE SCAN 21 3 CRASHFACT_CUST ANALYZED
SORT AGGREGATE 27 1
TABLE ACCESS BY GLOBAL INDEX ROWID 405 15 18 CRASH_FACT ANALYZED
INDEX RANGE SCAN 15 3 CRASHFACT_LAN ANALYZED
TABLE ACCESS FULL 12087192 503633 5005 LOANS ANALYZED


Thanks
Rahul


A query about SQL writting

RM, August 09, 2006 - 9:30 am UTC

Hi Tom, I have a scenario where I am trying to tune a long running query. Its a simple select statement to a view. e.g.
select * from view1;

View1 query view2.
View2 query view3 and view4.
View3 query view5.
View4 query View6 and view7.
View5 query union all of 2 tables.
View6 query 5 big tables in select and from clause.
View7 query a Materialise view.

Whats your view point towards this type of sql setup. I am thinking this to be major culprit in the long execution of this query. Can you suggest anything to reduce the execution time.

One more. Does HIGH COST in the explain plan determines the performance problem and long execution time, why yes and why not??

Tom Kyte
August 09, 2006 - 10:57 am UTC

I don't necessarily like views of views of views of views.

Typically - what is supplied and done by view1 is not entirely needed by view2 (does stuff view2 does not really need) and so on.

So you get "baggage"

I like one level views.


A high cost might be indicative of a very expensive query, yes. The cost is a function of the PREDICTED work to be performed.

Different execution plans

A reader, August 10, 2006 - 6:20 am UTC

Hi Tom,
we have a query which takes 31 seconds when called through JDBC.
The same sql statement, when executed from sqlplus takes only 1 second.

When we took help of our dba to find out why it takes 31 seconds in production,
our dba found that one table is undergoing full table scan when called from JDBC.
This was also found from V$SQL_PLAN.

However, explain plan shows index access and we get the output in just 1 second.

How can we find out what is the reason for this discrepancy?

Thanks.

Tom Kyte
August 10, 2006 - 9:30 am UTC

when you do it in sqlplus, is that in "production" as well - or something entirely different.

Same database

Giridhar, August 11, 2006 - 2:52 am UTC

Hi Tom,
Whatever we are doing in sqlplus is in production, accessing the same database which was being accessed by JDBC.
As we used bind variables in our JDBC program, we tried as follows:

We pass account number as input for our query and the column which is used is CHAR(9).
Hence we tested with following cases


a) var act1 char(9)
var act2 char(9)
var act3 char(9)

exec :act1 := '5JC010588';
exec :act2 := '5JC010604';
exec :act3 := '5JC010620';

Execute the query which is like

SELECT /*+ ORDERED */ COLUMN_A,COLUMN_B,......FROM
TABLE_A N, TABLE_B P, TABLE_C S WHERE
N.ACCTNUM = P.ACCTNUM AND P.CSP_NUM != 'USD999997' AND P.CSP_NUM = S.CSP_NUM(+) AND
N.CSP_NUM = P.CSP_NUM AND N.ACCTNUM IN ( :act1 , :act2 , :act3 )
.......


This is taking just less than a second, But it still does full table scan of one of the large tables.

b) var act1 varchar2(9)
var act2 varchar2(9)
var act3 varchar2(9)

exec :act1 := '5JC010588';
exec :act2 := '5JC010604';
exec :act3 := '5JC010620';

Execute the query which is like

SELECT /*+ ORDERED */ COLUMN_A,COLUMN_B,......FROM
TABLE_A N, TABLE_B P, TABLE_C S WHERE
N.ACCTNUM = P.ACCTNUM AND P.CSP_NUM != 'USD999997' AND P.CSP_NUM = S.CSP_NUM(+) AND
N.CSP_NUM = P.CSP_NUM AND N.ACCTNUM IN ( :act1 , :act2 , :act3 )
.......


This is taking around 30 to 40 seconds, But it still does full table scan of one of the large tables. All the other indexes remain same, just by changing the datatype from varchar to char, i see a drastic improvement in performance.

I requested my DBA to find out the exact execution plan from V$SQL_PLAN today
to see if there is any difference in execution plans for the above two cases.
Only difference in the above two cases is the variable type i used , char in the first case
and varchar2 in second case.

Thanks for your help tom.




Tom Kyte
August 11, 2006 - 10:47 am UTC

and what pray tell is the actual STRUCTURE OF THE TABLE

without an example that we can reproduce with, not even going to look too hard.

LOSE THE HINT, just don't do that.

full table scan

A reader, August 11, 2006 - 2:40 pm UTC

I have a query like

select a,b,c..

where a.id=b.id
and b.uid=c.uid
and a.re_is in ( select ....)
and a.hist_date = ( select max(hist_date) from a a1
where a1.id=a.id)

this is having a full table scan on select max query.
Is there any other way I can write it to make it faster as this is taking lot of time.


Tom Kyte
August 11, 2006 - 2:58 pm UTC

great, no create tables, no indexing scheme, no plan, nothing.

I've a feeling this query was rewritten as an efficient job.


looks like you want the "max record" for A - but you know what, without the query itself - we cannot say if analytics could be used or not.

You see - you get the max hist_date from A by ID, but you constrain A by other restrictions in the outer query.... So, who knows what could be done.

run fast after stats removed on one table

A reader, September 06, 2006 - 7:47 am UTC

we have following query runs hours if stats on all tables collected

SELECT pw.proj_id, pw1.proj_id as parent_proj_id
from PROJWBS pw
, PROJWBS pw1
WHERE pw.proj_node_flag = 'Y'
AND pw1.wbs_id = pw.parent_wbs_id
AND pw.proj_id IN (SELECT u.proj_id FROM UACCESS u WHERE u.user_id = 10499)

analyze statement:
analyze table <tab> compute statistics;
the explain plan:
NESTED LOOPS
MERGE JOIN CARTESIAN
INDEX FAST FULL SCAN NDX_PROJWBS_PERF1
BUFFER SORT
SORT UNIQUE
INDEX RANGE SCAN PK_UACCESS
INDEX RANGE SCAN NDX_PROJWBS_PERF1

it runs 1 second if the stats on usccess only removed.
explain plan after stats remove on uaccess:
NESTED LOOPS
NESTED LOOPS SEMI
INDEX FAST FULL SCAN NDX_PROJWBS_PERF1
INDEX UNIQUE SCAN PK_UACCESS
TABLE ACCESS BY INDEX ROWID PROJWBS
INDEX UNIQUE SCAN PK_PROJWBS

Could you please explain on the Bad plan?


What's Buffer Sort?

A reader, September 09, 2006 - 6:34 pm UTC

I understood you did not take above my question for the reason of lacking of info.
it's 9.2.0.7 database.

i have to try to understanding the following plan:

NESTED LOOPS
MERGE JOIN CARTESIAN
INDEX FAST FULL SCAN NDX_PROJWBS_PERF1
BUFFER SORT
SORT UNIQUE
INDEX RANGE SCAN PK_UACCESS
INDEX RANGE SCAN NDX_PROJWBS_PERF1

What is the Buffer Sort? I failed to find in the oracle documents. And again could you please explain the bad execution plan?


Tom Kyte
September 10, 2006 - 9:17 am UTC

lack of information implies no comments really can be or should be made. You should not use analyze (dbms_stats please) and you should (you) compare the estimated cardinalities of the explain plan with the realities shown in a tkprof "row source operation" - looking for large disparities and then asking "why" (it might be obvious)

The BUFFER SORT operation can be used by optimizer when it thinks that temporarily storing the input row source and sorting it by a key column might be useful in eventually doing join; this would make the join efficient in terms of IO. This is no different from an ordinary sort in terms of overhead.


Primary Key index not being used

Deep, September 11, 2006 - 8:40 am UTC

Hi Tom,

Have a query in hand which does not use a primary key index of a table even though only one row needs to be fetched from the said table.

Does it have anything to do with optimizer_index_cost_adj parameter?

my query is :

select a.*,b.* from t1 a,t2 b
where t1.id=t2.oid;

It is doing full table scans of all the tables. For t2 it is ok to have a FTS as it is very small.

For t1 id is the primary key.

All the statistics is also up to date.

my optimizer_mode=all_rows.

Even after setting the optimizer_index_cost_adj=10 it is still doing the FTS of t1.



Tom Kyte
September 11, 2006 - 10:27 am UTC

umm, looks to me that it has to get eventually many/most of the rows from both tables - sure, a single row might join to one row over there - but you have "many of these single rows"


first_rows_N optimization would lead to the index, but beware of what you ask for!!!!

</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:6749454952894#6760861174154 <code>

How to explain cardinality of view to the optimizer

Dmytro, September 18, 2006 - 8:10 am UTC

Hello Tom! I have a little problem with optimizer. Asked through a few forums, but nobody really help.
I have two projects on me, somehow connected. And there was one table, that was used by both of the projects, but it was doubled for some reason - one in the first project schema and one in the other. There are some minor differences between them, but the most important is, that in the first project, history of data changes was stored in the same table with actual data, only having date of change set and "deleted" records have status 2 ("disabled").
For some reason, now we must "merge" the tables, to have the same data in both projects and we need all the data to be taken from the first project.
So I create a view like this:
CREATE OR REPLACE VIEW TERRITORIES
AS
SELECT field1 AS fielda
,field2 AS fieldb
..........
FROM project1.territories
WHERE field1 IS NOT NULL
AND date_changed IS NULL
AND status = 1;

So, all seems to be great, but... There are 45000 records in the project1 table and only few of them are filtered by our WHERE-condition (there were(and will be) only a few changes in this table), but optimizer shows cardinality estimate of 2047 for select from this view instead of 44700 or so...
Bad thing is, that there are few reports joined to this table (and now view) and their plans changed quite a bit after table become a view...
What can be done about this, except hinting every query we had with this view, to make optimizer make right guess about cardinality of the view...
I try to create hiostograms on status column, but it not worked. Appreciate your help.


Tom Kyte
September 18, 2006 - 1:33 pm UTC

if you add a level 3 dynamic sample hint - what then?



SELECT /*+ dynamic_sampling(t 3) */
field1 AS fielda
,field2 AS fieldb
..........
FROM project1.territories t
WHERE field1 IS NOT NULL
AND date_changed IS NULL
AND status = 1;

Wow!

Dmytro, September 18, 2006 - 2:04 pm UTC

That really helps. Now select(*) from view shows true cardinality - more than 40000. I'll check plans for our reports, but now they must be just right.
Now it is clear to me, that I need to read some book about optimization.
Thanks Tom.

But still...

Dmytro, September 18, 2006 - 2:34 pm UTC

Hm... its strange to me, but it seems, that sampling works just fine with select from the view, but stops working after joining to the other...
For example, I have second view without WHERE clause, just select from the table with territory names in project1 schema.
I write something like this:
select * from territories t join territory_names tn on tn.tn_id = t.tn_tn_id
and my plan is like this:

SELECT STATEMENT, GOAL = CHOOSE Cost=48 Cardinality=2048 Bytes=88064
HASH JOIN Cost=48 Cardinality=2048 Bytes=88064
TABLE ACCESS FULL Object owner=PROJECT1 Object name=TERRITORIES Cost=32 Cardinality=2048 Bytes=51200
TABLE ACCESS FULL Object owner=PROJECT1 Object name=TERRITORY_NAMES Cost=14 Cardinality=26076 Bytes=469368

but simple select from territories gives us plan like this:

SELECT STATEMENT, GOAL = CHOOSE Cost=32 Cardinality=40954 Bytes=1023850
TABLE ACCESS FULL Object owner=PROJECT1 Object name=TERRITORIES Cost=32 Cardinality=40954 Bytes=1023850

Am I missing something?..

Suggestion

Reader, September 20, 2006 - 9:02 am UTC

Sir,
Following is the table structure:

Table T1
COL1
ADDRESS1
ADDRESS2
ADDRESS3

Data looks like this:

COL1 ADDRESS1 ADDRESS2 ADDRESS3
1 ABC PQR XYZ
2 LMN OPQ QRT
3 XYZ TTT PPP

Following is the table structure:

Table T2
COL1
COL5

Data looks like this:

COL1 COL2
1 A
2 B

I want to print the data in this way:

COL1 ADDRESS_LINE COL5
1 ABC A
1 PQR A
1 XYZ A
2 LMN B
2 OPQ B
2 QRT B

Of the two approaches (Query1 and Query2) can you suggest which is the better one?

Query1:
SELECT T1.COL1, T1.ADDRESS1 ADDRESS_LINE, T2.COL5
FROM T1, T2
WHERE T1.COL1 = T2.COL1
UNION
SELECT T1.COL1, T1.ADDRESS2 ADDRESS_LINE, T2.COL5
FROM T1, T2
WHERE T1.COL1 = T2.COL1
UNION
SELECT T1.COL1, T1.ADDRESS3 ADDRESS_LINE, T2.COL5
FROM T1, T2
WHERE T1.COL1 = T2.COL1


Query2:
SELECT T1.COL1, T1.ADDRESS_LINE, T2.COL5
FROM (
SELECT T1.COL,
DECODE(R, 1, ADDRESS1, 2, ADDRESS2, 3, ADDRESS3) ADDRESS_LINE
FROM T1,
(
SELECT ROWNUM R
FROM T1
WHERE ROWNUM <=3)) T1,
T2
WHERE T1.COL1 = T2.COL1

Regards


Tom Kyte
September 20, 2006 - 3:10 pm UTC

query 2, but you can use

(select 1 r from dual union all select 2 from dual union all select 3 from dual)

or

(select level r from dual connect by level <= 3)


since t1 might not always have three rows...

Too good

Reader, September 21, 2006 - 12:58 am UTC

Thanks Tom for the reply.

Question On a statement from the Manual

Neeraj Nagpal, September 25, 2006 - 5:15 pm UTC

I was reading about the Explain Plan Properties from the -- Database Tuning with the Oracle Tuning Pack -- manual at </code> http://download-west.oracle.com/docs/cd/B10501_01/em.920/a86647/vmqstats.htm#1015761 <code>
-- I could'nt get exact sense out of this. Could you please elaborate this for me.



Full Table scans as non-driving tables in nested loop joins

There may be an opportunity for speeding up the access to the non-driving table when it is joined in the nested loops fashion via a full table scan lookup. Often an appropriatae concatenation of columns from the non-driving table is all that is required to perform the lookup. The rule -of-thumb indicator is found on any Explain Plan TABLE ACCESS (FULL) object which is a child of a NESTED LOOPS object and is not the first in the chain of tables being joined.


Thanks Always For your Help,
Neeraj Nagpal

Tom Kyte
September 26, 2006 - 2:21 am UTC

I believe they are referring to a query like this:


ops$tkyte%ORA10GR2> set autotrace traceonly explain
ops$tkyte%ORA10GR2> select /*+ USE_NL(emp dept) */ *
  2    from emp, dept
  3   where emp.deptno = dept.deptno;

Execution Plan
----------------------------------------------------------
Plan hash value: 4192419542

------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time
------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |    14 |  1638 |     9   (0)| 00:00:0
|   1 |  NESTED LOOPS      |      |    14 |  1638 |     9   (0)| 00:00:0
|   2 |   TABLE ACCESS FULL| DEPT |     4 |   120 |     3   (0)| 00:00:0
|*  3 |   TABLE ACCESS FULL| EMP  |     4 |   348 |     2   (0)| 00:00:0
------------------------------------------------------------------------

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

   3 - filter("EMP"."DEPTNO"="DEPT"."DEPTNO")

Note
-----
   - dynamic sampling used for this statement
<b>
and are saying "emp, this non-driving table, is being full scanned for every row in dept.  That might not be efficient.  Therefore, if you index the obvious set of columns, it might be better"

Actually, in this case, I would say a pair of full scans and a hash join would be in general better - unless you need to optimize for response time, in which case the following would make sense:</b>

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> create index emp_idx on emp(deptno);

Index created.

ops$tkyte%ORA10GR2> select /*+ USE_NL(emp dept) */ *
  2    from emp, dept
  3   where emp.deptno = dept.deptno;

Execution Plan
----------------------------------------------------------
Plan hash value: 1619650520

------------------------------------------------------------------------
| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%C
------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |    14 |  1638 |     6
|   1 |  TABLE ACCESS BY INDEX ROWID| EMP     |     4 |   348 |     1
|   2 |   NESTED LOOPS              |         |    14 |  1638 |     6
|   3 |    TABLE ACCESS FULL        | DEPT    |     4 |   120 |     3
|*  4 |    INDEX RANGE SCAN         | EMP_IDX |     5 |       |     0
------------------------------------------------------------------------

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

   4 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")

Note
-----
   - dynamic sampling used for this statement
 

Thanks So Much For your response..

Neeraj, September 26, 2006 - 1:27 pm UTC

Tom,

Thanks so much for your response. If both the tables involved in the NL join happen to be huge tables --  then would it be even more efficient to have both the child objects of  NESTED LOOP be assessed by the INDEX?? Like in the following example:


  1  select
  2  /*+ USE_NL(properties) */
  3  Properties.FIPS_STATE_COUNTY,
  4  Properties.FIPS_TOWNSHIP,
  5  Properties.APN_SOURCE
  6  from Properties,COUNTY_CONTROL cc2
  7  where Properties.fips_state_County = cc2.fips_state_county and
  8  Properties.fips_township = cc2.fips_township
  9  and source_owner = 'FNC' AND
 10  properties.datetime_last_update >=
 11  TO_DATE ('28-aug-2006 21:02:14', 'DD-MON-YYYY HH24:MI:SS')   AND
 12  properties.datetime_last_update <
 13  TO_DATE ('21-sep-2006 00:00:00', 'DD-MON-YYYY HH24:MI:SS')
 14* AND ( properties.fips_state_county >= '13001' AND properties.fips_state_county <= '13999' /*GA*
09:53:20 ADBPROD@PROD SQL> /

Execution Plan
----------------------------------------------------------
   0 null SELECT STATEMENT Optimizer=CHOOSE (Cost=5710 Card=227809 Byt null
          es=19363765)

   1    0   NESTED LOOPS (Cost=5710 Card=227809 Bytes=19363765)        null
   2    1     TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'PROPERTIES' (Co null
          st=5710 Card=646411 Bytes=45895181)

   3    2       INDEX (RANGE SCAN) OF 'IDX_PROPERITES_REPL' (NON-UNIQU null
          E) (Cost=671 Card=2783) <----

   4    1     INDEX (UNIQUE SCAN) OF 'COUNTY_CONTROL_PK_1' (UNIQUE)   <----



Thanks,
Neeraj Nagpal 

Tom Kyte
September 26, 2006 - 4:57 pm UTC

if both tables are "OF ANY SIZE"

then:

if you want to optimize to get the LAST ROW RETURNED AS FAST AS YOU CAN
then
probably two full scans and a hash join is best, I would not
want a single index used
ELSE if you want to optmize to get the FIRST ROWS RETURNED as fast as you can
and can wait hours for the last row (interactive application)
then
a full scan of one table and nested loops index into another is
probably best
end if.


</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:6749454952894#6760861174154 <code>

Thanks.

Neeraj Nagpal, September 26, 2006 - 6:27 pm UTC

Thanks for your prompt answer.

Neeraj Nagpal

identify whether development is occuring in the production environment

Dawar, October 17, 2006 - 7:05 pm UTC

Tom,

which Oracle table should be checked first in order to try to identify whether development is occuring in the production environment?


cheers,
Dawar

Tom Kyte
October 18, 2006 - 7:46 am UTC

DBA_TAB_PRIVS and DBA_SYS_PRIVS

do users that should not have privileges, have them....

Dawar, October 17, 2006 - 7:55 pm UTC

I am confused btw the following tables

sys.source$, sys.obj$, sys.views$,sys.user$ & sys.code$



Tom Kyte
October 18, 2006 - 7:48 am UTC

easy to fix.

stop thinking about them.


You would use
*_SOURCE (*=DBA, USER, ALL)
*_OBJECTS
*_VIEWS
*_USERS (no user_users of course!)


quite easy to fix, wish all of the problems posted on this site were that easy!

cardinality and the explain plan

Ryan, October 25, 2006 - 10:44 am UTC

I am looking at an explain plan with set autotrace on. I left the explain plan off of this because it wrapped and was not readable. I was not able to format it.

Table C has 53,000 rows. When I query dba_tables.num_rows it states that there are 53,000 rows. How can I have 'rows' of 1634 on a full table scan? Shouldn't it be all the rows in the table since Oracle is doing a full table scan?

Now we do have have a where clause that filters the number of rows returned. However, my understanding of a full table scan is that oracle reads up to the high water mark. Shouldn't that be all the rows? Or is it just the rows returned?

Same with bytes. If Oracle does a full table scan that reads 150 MBs of data, but only return 30k of data, it looks like the bytes column will say 30k. Is this correct?

I get the same number when I look at the cardinality column in the 'explain plan for'. I had though that Oracle got the rows for a full table scan from dba_tables.num_rows? I guess not.


Tom Kyte
October 25, 2006 - 10:59 am UTC

the rows shows the number of rows flowing OUT OF the step..


so, select * from t; that would show 53,000

select * from t where <predicate>; that would show the guess of 1,634 rows after the predicate is applied.

Insert into <tab> select *...

dev, October 27, 2006 - 3:21 am UTC

Which one is faster and Why?
1.Insert into <tab_name> select *from <tab_name_1>
OR
2.Create table <tab_name> as select *from <tab_name_1

Tom Kyte
October 27, 2006 - 7:46 am UTC

create table as select will be a direct path operation, it can skip undo and even redo generation.

insert (without append) cannot do that, it writes to the buffer cache and always generates undo and redo.


for a small table, because of the fact that create table would do direct path operations - insert might be faster (buffered IO). for large sets, the opposite would likely be true.

so "it depends"

explain plan followup...

Ryan, October 30, 2006 - 1:13 pm UTC

wouldn't it be more useful if for a full tablescan the rows or bytes section showed the number of rows or bytes actually accessed? It makes it easier to look for bottlenecks. You can get this with a 10046 trace (by looking at the cr=), but this way you can do it without having to run a trace?

Tom Kyte
October 30, 2006 - 3:10 pm UTC

not for the EXPLAIN plan, the "cost" gives you that.

for the ROW SOURCE OPERATION - we see that, you see the actuals - that (found in the tkprof) shows you what really happened.

The explain plan is the GUESS that after scanning table T, we'll get X rows as output. The COST of scanning table T is there (that is the cost of the full table scan), the results of scanning table T are what are relevant to the steps further on up.

Explain Plan

Anna, November 07, 2006 - 7:39 am UTC

Sorry Tom i had to use this link for posting i never get an oppurtunity... can you please help me to identify the problem in this SQL query it take more then 1 min to return data and i want to bring this down to probably 5 sec

SELECT /*+ ALL_ROWS PARALLEL(oeh 20) */ DISTINCT reqh.shipcmf ST_CMF,
reqh.reqnumber Req_No,
reqh.contract Req_Contract,
reqh.divpulldate Req_Div_Pull,
reqh.closedate Req_Closed,
reql.catalognumber Req_Item,
(NVL(reql.ordqty, 0) - NVL(reql.voidqty, 0)) Req_Actual_Qty,
reql.shipqty Req_Ship_Qty,
DECODE(ott.transaction_type_id, 1001, 'ATO', 1335, 'D/S', 1550, 'ATO', 1554, 'D/S') Order_Type,
oeh.header_id hdr_id,
oeh.sold_to_org_id sold_to_org,
oeh.order_number Order_No,
oeh.ordered_date Ordered,
oel.ordered_item O_Ln_Item,
oel.ordered_quantity O_Ln_Qty,
NVL(oel.fulfilled_quantity, 0) O_Ln_Fill_Qty,
oel.flow_status_code O_Ln_Status
FROM dw.reqheaders@DW_LNK reqh,
DW.REQLINES@DW_LNK reql,
apps.oe_order_headers_all@adpds_score_lnk oeh,
apps.oe_order_lines_all@adpds_score_lnk oel,
apps.oe_transaction_types_tl@adpds_score_lnk ott
WHERE reqh.reqnumber = oeh.attribute11
AND reqh.contract = oeh.attribute12
AND reqh.reqnumber = reql.reqnumber
AND reql.catalognumber = oel.ordered_item
AND reql.erline = oel.orig_sys_line_ref
AND oeh.header_id = oel.header_id
AND oeh.order_type_id IN (1001, 1335, 1550, 1554)
AND oeh.order_type_id = ott.transaction_type_id
AND oeh.flow_status_code <> 'CANCELLED'
AND oel.flow_status_code <> 'CANCELLED'
AND oeh.cancelled_flag = 'N'
AND oel.cancelled_flag = 'N'
AND TRUNC(oeh.creation_date) BETWEEN '01-OCT-2006' AND '01-NOV-2006'
AND NOT EXISTS (SELECT /*+ ALL_ROWS */ DISTINCT 1
FROM DW.FSORDHIST@DW_LNK soh,
dw.fsorddtlhist@DW_LNK sol
WHERE oeh.header_id = soh.header_id
AND oel.attribute12 = sol.reqnumber || '*' || sol.sonumber || '*' || sol.solinenumber
AND oel.ordered_item = sol.catalognumber
AND oeh.attribute11 = sol.reqnumber)



----------------------------------------------------
Operation Object Name Rows Bytes Cost Object Node In/Out PStart PStop

SELECT STATEMENT Optimizer Mode=HINT: ALL_ROWS 1 12901
HASH UNIQUE 1 886 12901
FILTER
NESTED LOOPS 1 886 869
NESTED LOOPS 7 5 K 861
NESTED LOOPS 4 1 K 851
FILTER 4 52 1
REMOTE ADPDS_SCORE_LNK.HOFFMAN.DS.ADP.COM SERIAL
REMOTE .REQHEADERS 1 46 2 DW_LNK.HOFFMAN.DS.ADP.COM SERIAL
REMOTE .OE_ORDER_LINES_ALL 2 916 3 ADPDS_SCORE_LNK.HOFFMAN.DS.ADP.COM SERIAL
REMOTE .REQLINES 1 61 2 DW_LNK.HOFFMAN.DS.ADP.COM SERIAL
MERGE JOIN CARTESIAN 1 38 12031
REMOTE .FSORDDTLHIST 1 25 4 DW_LNK.HOFFMAN.DS.ADP.COM SERIAL
BUFFER SORT 9 117 12027
REMOTE .FSORDHIST 9 117 12027 DW_LNK.HOFFMAN.DS.ADP.COM SERIAL
----------------------------------------------
can you tell me where is this query getting stuck and what can be donw about it....

Tom Kyte
November 07, 2006 - 4:39 pm UTC

is 5 seconds even remotely reasonable.

first thing for you would be: lose parallel, parallel is not for queries taking few seconds, if you need parallel for "few seconds", there is a big problem right there and then. Heck, it could take a couple of seconds just to set up to get ready to go for parallel query.

Second, look at the estimated card= values there, are they even CLOSE to reality. if so, this would not take a minute probably, if not, you need to ask "why"

A reader, November 08, 2006 - 5:13 am UTC

I got rid of parallel after your suggestion but beleive me that hint made my query run in 45 sec instead of 1:16 Sec, i may be wrong in analysing that....

i did not understnad this one clearly
"estimated card= values there, are they even CLOSE to
reality. if so, this would not take a minute probably, if not, you need to ask
"why" ....

do you mean the data here is vast and it is bound to take the time it right now... or..??

Tom Kyte
November 08, 2006 - 8:30 am UTC

are the cardinality values (the rows, the estimated number of rows) even close to being correct in your explain plan above.

A reader, November 09, 2006 - 7:45 am UTC

So you mean to say i must be missing some thing in the Where clause which qualifys as a genuin relation....

To my knoweldge i have given all possible where clauses
and even then i just cant get rid of those Cardinality values.

What would you do in such case?

Tom Kyte
November 09, 2006 - 9:00 am UTC

I want you to tell me if the guess the optimizer is making on the rows is CORRECT or NOT

that is all.



ops$tkyte%ORA9IR2> create table t ( x int );

Table created.

ops$tkyte%ORA9IR2> set autotrace traceonly explain

ops$tkyte%ORA9IR2> select /*+ all_rows */ * from t;

Execution Plan
----------------------------------------------------------
   0
SELECT STATEMENT Optimizer=HINT: ALL_ROWS (Cost=2 Card=82 Bytes=1066)


   1    0
  TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=82 Bytes=1066)


<b>way off...</b>


ops$tkyte%ORA9IR2> exec dbms_stats.gather_table_stats(user, 'T')

PL/SQL procedure successfully completed.

ops$tkyte%ORA9IR2> select /*+ all_rows */ * from t;

Execution Plan
----------------------------------------------------------
   0
SELECT STATEMENT Optimizer=HINT: ALL_ROWS (Cost=2 Card=1 Bytes=13)


   1    0
  TABLE ACCESS (FULL) OF 'T' (Cost=2 Card=1 Bytes=13)

<b>Not way off</b>

that is what I am asking you - way off or NOT way off 

A reader, November 09, 2006 - 10:07 am UTC

Table Stats are all gathered.....Its an automatic process
where Stats are gathered automatically.

Tom Kyte
November 09, 2006 - 2:29 pm UTC

and that....

does not answer anything asked here.

at all.

Change in execution path

Rajesh, November 10, 2006 - 4:40 am UTC

Hi Tom,

In our production database the query does a full table scan and is very slow.In our test database it uses and index range scan and is considerably faster.The test system has less data, but I also believe the use of the index improves performance in this case.

SQL>l
  1  Select table_name, index_name, column_name, column_position from dba_ind_columns
  2  where table_name in ('REVISIONS','SUBSCRIPTION') and table_owner='STELLENT_CON'
  3* order by table_name,index_name,column_position
(DSIMS@DBPRD)SQL>/

TABLE_NAME                INDEX_NAME                     COLUMN_NAME                         COLUMN_POSITION            
------------------------- ------------------------------ ----------------------------------- ---------------            
REVISIONS                 DCHECKOUTUSER                  DCHECKOUTUSER                                     1            
REVISIONS                 DDOCACCOUNT                    DDOCACCOUNT                                       1            
REVISIONS                 DDOCNAME                       DDOCNAME                                          1            
REVISIONS                 DFLAG1                         DFLAG1                                            1            
REVISIONS                 DINDATE                        DINDATE                                           1            
REVISIONS                 DINDEXERSTATE                  DINDEXERSTATE                                     1            
REVISIONS                 DOUTDATE                       DOUTDATE                                          1            
REVISIONS                 DRELEASEDATE                   DRELEASEDATE                                      1            
REVISIONS                 DRELEASESTATE                  DRELEASESTATE                                     1            
REVISIONS                 DREVCLASSID_2                  DREVCLASSID                                       1            
REVISIONS                 DSTATUS                        DSTATUS                                           1            
REVISIONS                 PK_REVISIONS                   DID                                               1            
REVISIONS                 REVISIONINDEX                  DREVISIONID                                       1            
REVISIONS                 REVISIONINDEX                  DREVCLASSID                                       2            
SUBSCRIPTION              PK_SUBSCRIPTION                DSUBSCRIPTIONALIAS                                1            
SUBSCRIPTION              PK_SUBSCRIPTION                DSUBSCRIPTIONALIASTYPE                            2            
SUBSCRIPTION              PK_SUBSCRIPTION                DSUBSCRIPTIONID                                   3            
SUBSCRIPTION              PK_SUBSCRIPTION                DSUBSCRIPTIONTYPE                                 4            


SQL>desc STELLENT_CON.SUBSCRIPTION
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 DSUBSCRIPTIONALIAS                        NOT NULL VARCHAR2(50)
 DSUBSCRIPTIONALIASTYPE                    NOT NULL VARCHAR2(30)
 DSUBSCRIPTIONEMAIL                                 VARCHAR2(80)
 DSUBSCRIPTIONID                           NOT NULL VARCHAR2(255)
 DSUBSCRIPTIONTYPE                         NOT NULL VARCHAR2(30)
 DSUBSCRIPTIONCREATEDATE                   NOT NULL DATE
 DSUBSCRIPTIONNOTIFYDATE                            DATE
 DSUBSCRIPTIONUSEDDATE                              DATE


SQL>desc STELLENT_CON.REVISIONS
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 DID                                       NOT NULL NUMBER(38)
 DDOCNAME                                  NOT NULL VARCHAR2(30)
 DDOCTYPE                                  NOT NULL VARCHAR2(30)
 DDOCTITLE                                 NOT NULL VARCHAR2(255)
 DDOCAUTHOR                                         VARCHAR2(50)
 DREVCLASSID                               NOT NULL NUMBER(38)
 DREVISIONID                               NOT NULL NUMBER(38)
 DREVLABEL                                          VARCHAR2(10)
 DISCHECKEDOUT                             NOT NULL NUMBER(1)
 DCHECKOUTUSER                                      VARCHAR2(30)
 DSECURITYGROUP                            NOT NULL VARCHAR2(30)
 DCREATEDATE                                        DATE
 DINDATE                                            DATE
 DOUTDATE                                           DATE
 DSTATUS                                            VARCHAR2(20)
 DRELEASESTATE                                      CHAR(1)
 DFLAG1                                             CHAR(1)
 DWEBEXTENSION                                      VARCHAR2(255)
 DPROCESSINGSTATE                                   CHAR(1)
 DMESSAGE                                           VARCHAR2(255)
 DDOCACCOUNT                                        VARCHAR2(30)
 DRELEASEDATE                                       DATE
 DRENDITION1                                        CHAR(1)
 DRENDITION2                                        CHAR(1)
 DINDEXERSTATE                                      CHAR(1)
 DPUBLISHTYPE                                       CHAR(1)
 DPUBLISHSTATE                                      CHAR(1)


Here is the good plan and bad plan

GOOD PLAN
==========

SQL>explain plan for SELECT Subscription.*, Revisions.*
  2  FROM STELLENT_CON.SUBSCRIPTION, STELLENT_CON.Revisions
  3  WHERE  SUBSCRIPTION.dSubscriptionAlias = 'joseph.a.zimmerlin' AND
  4      Subscription.dSubscriptionAliasType = 'user' AND
  5      Subscription.dSubscriptionType = 'Basic' AND
  6      Revisions.dID IN (SELECT MAX(dID) FROM STELLENT_CON.Revisions where
  7              Revisions.dDocName = Subscription.dSubscriptionID);

Explained.


SQL>@?/rdbms/admin/utlxpls.sql

PLAN_TABLE_OUTPUT                                                               
--------------------------------------------------------------------------------
Plan hash value: 534145969                                                      
                                                                                
--------------------------------------------------------------------------------
-------------------                                                             
                                                                                
| Id  | Operation                       | Name            | Rows  | Bytes | Cost
 (%CPU)| Time     |                                                             
                                                                                
--------------------------------------------------------------------------------
-------------------                                                             
                                                                                
|   0 | SELECT STATEMENT                |                 |     1 |   197 |    3
6   (0)| 00:00:01 |                                                             
                                                                                
|   1 |  NESTED LOOPS                   |                 |     1 |   197 |     
3   (0)| 00:00:01 |                                                             
                                                                                
|   2 |   TABLE ACCESS BY INDEX ROWID   | SUBSCRIPTION    |     1 |    55 |     
2   (0)| 00:00:01 |                                                             
                                                                                
|*  3 |    INDEX RANGE SCAN             | PK_SUBSCRIPTION |     1 |       |     
1   (0)| 00:00:01 |                                                             
                                                                                
|   4 |   TABLE ACCESS BY INDEX ROWID   | REVISIONS       |     1 |   142 |     
1   (0)| 00:00:01 |                                                             
                                                                                
|*  5 |    INDEX UNIQUE SCAN            | PK_REVISIONS    |     1 |       |     
0   (0)| 00:00:01 |                                                             
                                                                                
|   6 |     SORT AGGREGATE              |                 |     1 |    17 |     
       |          |                                                             
                                                                                
|   7 |      TABLE ACCESS BY INDEX ROWID| REVISIONS       |     1 |    17 |     
3   (0)| 00:00:01 |                                                             
                                                                                
|*  8 |       INDEX RANGE SCAN          | DDOCNAME        |     1 |       |     
1   (0)| 00:00:01 |                                                             
                                                                                
--------------------------------------------------------------------------------
-------------------                                                             
                                                                                
                                                                                
Predicate Information (identified by operation id):                             
---------------------------------------------------                             
                                                                                
   3 - access("SUBSCRIPTION"."DSUBSCRIPTIONALIAS"='joseph.a.zimmerlin' AND      
              "SUBSCRIPTION"."DSUBSCRIPTIONALIASTYPE"='user' AND                
              "SUBSCRIPTION"."DSUBSCRIPTIONTYPE"='Basic')                       
       filter("SUBSCRIPTION"."DSUBSCRIPTIONTYPE"='Basic')                       
   5 - access("REVISIONS"."DID"= (SELECT MAX("DID") FROM "STELLENT_CON"."REVISIO
NS"                                                                             
                                                                                
              "REVISIONS" WHERE "REVISIONS"."DDOCNAME"=:B1))                    
   8 - access("REVISIONS"."DDOCNAME"=:B1)                                       

26 rows selected.



BAD PLAN
========

SQL>explain plan for SELECT Subscription.*, Revisions.*
  2  FROM STELLENT_CON.SUBSCRIPTION, STELLENT_CON.Revisions
  3  WHERE  SUBSCRIPTION.dSubscriptionAlias = 'joseph.a.zimmerlin' AND
  4      Subscription.dSubscriptionAliasType = 'user' AND
  5      Subscription.dSubscriptionType = 'Basic' AND
  6      Revisions.dID IN (SELECT MAX(dID) FROM STELLENT_CON.Revisions where
  7              Revisions.dDocName = Subscription.dSubscriptionID);

Explained.

(DSIMS@DBPRD)SQL>@?/rdbms/admin/utlxpls.sql

PLAN_TABLE_OUTPUT                                                               
--------------------------------------------------------------------------------
Plan hash value: 3881148907                                                     
                                                                                
--------------------------------------------------------------------------------
--------------------                                                            
                                                                                
| Id  | Operation                        | Name            | Rows  | Bytes | Cos
t (%CPU)| Time     |                                                            
                                                                                
--------------------------------------------------------------------------------
--------------------                                                            
                                                                                
|   0 | SELECT STATEMENT                 |                 |     1 |   230 |   2
91   (3)| 00:00:04 |                                                            
                                                                                
|*  1 |  FILTER                          |                 |       |       |    
        |          |                                                            
                                                                                
|   2 |   HASH GROUP BY                  |                 |     1 |   230 |   2
91   (3)| 00:00:04 |                                                            
                                                                                
|   3 |    MERGE JOIN CARTESIAN          |                 |  8571 |  1925K|   2
89   (3)| 00:00:04 |                                                            
                                                                                
|   4 |     TABLE ACCESS BY INDEX ROWID  | REVISIONS       |     1 |    29 |    
 3   (0)| 00:00:01 |                                                            
                                                                                
|   5 |      NESTED LOOPS                |                 |     1 |    84 |    
 6   (0)| 00:00:01 |                                                            
                                                                                
|   6 |       TABLE ACCESS BY INDEX ROWID| SUBSCRIPTION    |     1 |    55 |    
 3   (0)| 00:00:01 |                                                            
                                                                                
|*  7 |        INDEX RANGE SCAN          | PK_SUBSCRIPTION |     1 |       |    
 2   (0)| 00:00:01 |                                                            
                                                                                
|*  8 |       INDEX RANGE SCAN           | DDOCNAME        |     1 |       |    
 1   (0)| 00:00:01 |                                                            
                                                                                
|   9 |     BUFFER SORT                  |                 | 64072 |  9135K|   2
88   (3)| 00:00:04 |                                                            
                                                                                
|  10 |      TABLE ACCESS FULL           | REVISIONS       | 64072 |  9135K|   2
83   (3)| 00:00:04 |                                                            
                                                                                
--------------------------------------------------------------------------------
--------------------                                                            
                                                                                
                                                                                
Predicate Information (identified by operation id):                             
---------------------------------------------------                             
                                                                                
   1 - filter("REVISIONS"."DID"=MAX("DID"))                                     
   7 - access("SUBSCRIPTION"."DSUBSCRIPTIONALIAS"='joseph.a.zimmerlin' AND      
              "SUBSCRIPTION"."DSUBSCRIPTIONALIASTYPE"='user' AND                
              "SUBSCRIPTION"."DSUBSCRIPTIONTYPE"='Basic')                       
       filter("SUBSCRIPTION"."DSUBSCRIPTIONTYPE"='Basic')                       
   8 - access("REVISIONS"."DDOCNAME"="SUBSCRIPTION"."DSUBSCRIPTIONID")          

27 rows selected.


Thank you,
Rajesh 

Tom Kyte
November 10, 2006 - 8:54 am UTC

why do you believe that, have you tried hinting the query in your tests in production to see if your belief is accurate.

same query different plan

Ramprsad, November 30, 2006 - 5:25 am UTC

Hi Tom,

The below query takes 10 min to execute.

SELECT DISTINCT
O.CustomerID,
O.OrderID,
SD.SampleNumber,
SD.Test
FROM SMXPSU.Orders O
INNER JOIN SMXPSU.Projects P
ON O.CustomerID=P.CustomerID AND O.ProjectID=P.ProjectID
INNER JOIN SMXPSU.ProjectDefs PD
ON P.CustomerID=PD.CustomerID AND P.ProjectID=PD.ProjectID
INNER JOIN SMXPSU.SampleDetails SD
ON O.OrderID=SD.OrderID AND P.Matrix=SD.Matrix AND P.Test=SD.Test AND
P.Method=SD.Method
LEFT JOIN SMXPSU.Invoices I
ON SD.OrderID=I.OrderID AND SD.SampleNumber=I.SampleNumber AND SD.Test=I.Test
LEFT JOIN SMXPSU.Ampro_IncompleteOrders A
ON SD.OrderID=A.OrderID AND SD.Samplenumber=A.SampleNumber
WHERE I.InvoiceID IS NULL AND A.OrderID IS NULL AND A.SampleNumber IS NULL



Same query as above - written in "old" Oracle style - executes in 8 seconds


SELECT DISTINCT
O.CustomerID,
O.OrderID,
SD.SampleNumber,
SD.Test
FROM SMXPSU.Orders O,
SMXPSU.Projects P,
SMXPSU.ProjectDefs PD,
SMXPSU.SampleDetails SD,
SMXPSU.Invoices I,
SMXPSU.Ampro_IncompleteOrders A
WHERE O.CustomerID = P.CustomerID
AND O.ProjectID = P.ProjectID
AND P.CustomerID = PD.CustomerID
AND P.ProjectID = PD.ProjectID
AND O.OrderID = SD.OrderID
AND P.Matrix = SD.Matrix
AND P.Test = SD.Test
AND P.Method = SD.Method
AND SD.OrderID = I.OrderID(+)
AND SD.SampleNumber = I.SampleNumber(+)
AND SD.Test = I.Test(+)
AND SD.OrderID = A.OrderID(+)
AND SD.Samplenumber = A.SampleNumber(+)
AND I.InvoiceID IS NULL
AND A.OrderID IS NULL
AND A.SampleNumber IS NULL


SELECT DISTINCT
O.CustomerID,
O.OrderID,
SD.SampleNumber,
SD.Test
FROM SMXPSU.Orders O
INNER JOIN SMXPSU.Projects P
ON O.CustomerID=P.CustomerID AND O.ProjectID=P.ProjectID
INNER JOIN SMXPSU.ProjectDefs PD
ON P.CustomerID=PD.CustomerID AND P.ProjectID=PD.ProjectID
INNER JOIN SMXPSU.SampleDetails SD
ON O.OrderID=SD.OrderID AND P.Matrix=SD.Matrix AND P.Test=SD.Test AND
P.Method=SD.Method
LEFT JOIN SMXPSU.Invoices I
ON SD.OrderID=I.OrderID AND SD.SampleNumber=I.SampleNumber AND SD.Test=I.Test
LEFT JOIN SMXPSU.Ampro_IncompleteOrders A
ON SD.OrderID=A.OrderID AND SD.Samplenumber=A.SampleNumber
WHERE I.InvoiceID IS NULL AND A.OrderID IS NULL AND A.SampleNumber IS NULL

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.02 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 93 471.29 569.55 4860 96618455 0 1372
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 95 471.29 569.58 4860 96618455 0 1372

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

Rows Row Source Operation
------- ---------------------------------------------------
1372 SORT UNIQUE
39404 FILTER
791619 NESTED LOOPS OUTER
791571 FILTER
821891 NESTED LOOPS OUTER
805134 NESTED LOOPS
437484 NESTED LOOPS
437484 HASH JOIN
4197 TABLE ACCESS FULL PROJECTS
5458 TABLE ACCESS FULL ORDERS
437484 INDEX UNIQUE SCAN PK_PROJECTDEFS (object id 30761)
805134 TABLE ACCESS BY INDEX ROWID SAMPLEDETAILS
380667292 INDEX RANGE SCAN ID_SD_MATRX (object id 30799)
30320 TABLE ACCESS BY INDEX ROWID RESULTS
2578412 INDEX RANGE SCAN PK_RESULTS (object id 30831)
752215 INDEX RANGE SCAN PK_INVOICES (object id 30828)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 93 0.00 0.00
db file sequential read 4756 1.32 70.01
db file scattered read 20 0.94 1.12
latch free 3 0.00 0.00
SQL*Net message from client 93 13.60 20.83
********************************************************************************




SELECT DISTINCT
O.CustomerID,
O.OrderID,
SD.SampleNumber,
SD.Test
FROM SMXPSU.Orders O,
SMXPSU.Projects P,
SMXPSU.ProjectDefs PD,
SMXPSU.SampleDetails SD,
SMXPSU.Invoices I,
SMXPSU.Ampro_IncompleteOrders A
WHERE O.CustomerID = P.CustomerID
AND O.ProjectID = P.ProjectID
AND P.CustomerID = PD.CustomerID
AND P.ProjectID = PD.ProjectID
AND O.OrderID = SD.OrderID
AND P.Matrix = SD.Matrix
AND P.Test = SD.Test
AND P.Method = SD.Method
AND SD.OrderID = I.OrderID(+)
AND SD.SampleNumber = I.SampleNumber(+)
AND SD.Test = I.Test(+)
AND SD.OrderID = A.OrderID(+)
AND SD.Samplenumber = A.SampleNumber(+)
AND I.InvoiceID IS NULL
AND A.OrderID IS NULL
AND A.SampleNumber IS NULL

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 93 2.76 6.58 3100 553931 0 1372
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 95 2.76 6.59 3100 553931 0 1372

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

Rows Row Source Operation
------- ---------------------------------------------------
1372 SORT UNIQUE
39404 NESTED LOOPS
39404 NESTED LOOPS
21619 FILTER
56746 NESTED LOOPS OUTER
56734 FILTER
62389 NESTED LOOPS OUTER
58142 NESTED LOOPS
58142 TABLE ACCESS FULL SAMPLEDETAILS
58142 TABLE ACCESS BY INDEX ROWID ORDERS
58142 INDEX UNIQUE SCAN PK_ORDERS (object id 30758)
5655 TABLE ACCESS BY INDEX ROWID RESULTS
347254 INDEX RANGE SCAN PK_RESULTS (object id 30831)
35127 INDEX RANGE SCAN PK_INVOICES (object id 30828)
39404 TABLE ACCESS BY INDEX ROWID PROJECTS
39729 INDEX RANGE SCAN PK_ (object id 30746)
39404 INDEX UNIQUE SCAN PK_PROJECTDEFS (object id 30761)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 93 0.00 0.00
db file sequential read 2849 0.03 3.62
db file scattered read 85 0.00 0.11
SQL*Net message from client 93 0.58 7.70


Why the execution plan has been changed? Whant needs to be done to get same execution plan and same execution time?

Thanks,
Ram


Tom Kyte
November 30, 2006 - 10:02 am UTC

Advantages and Disadvantages

Deepak, December 06, 2006 - 10:01 am UTC

Hi Tom,

Would like to have your comments on the following...

> Does the parallel query have any disadvantages at times? Under what circumstances should I think of disabling the parallel query option in a multi-CPU box?


> What if I set the parallel DEGREE of all the tables of my database to a number 'n' (n may be 2,3,4,5...) which is greater than the number of CPUs in my box keeping an eye on the future addition of new CPUs to my box. Will there be any overhead because of that?

Sorry for asking these trivial questions.



Tom Kyte
December 07, 2006 - 8:38 am UTC

of course - everything, EVERYTHING has

a) times you want to use it
b) times you do not want to use it.


if you have 32 cpus, but you have 100 concurrent users - using parallel query doesn't even begin to make sense - since you already have 3 times as many things trying to use the cpu as cpus! If you let all 100 concurrent users use parallel 8 - you would have 24 times as many things.....





Cardinality in index scan step

Sanji, December 06, 2006 - 3:08 pm UTC

Tom
The env is Oracle 9i Rel2, HP-UX11i.
I extracted a query from v$session_wait with persistent "db file sequential read" wait event, and generated the explain plan through autotrace

select TO_CHAR(API.COMPANY,'FM0999'), API.VENDOR, API.INVOICE, TO_CHAR(API.SUFFIX,'FM099'),
from LAWSON.APINVOICE API
where API.COMPANY=:a
and API.VENDOR=:b
and API.INVOICE=:c
and API.SUFFIX=:d
and API.CANCEL_SEQ=:e
order by API.COMPANY,API.VENDOR, API.INVOICE, API.SUFFIX, API.CANCEL_SEQ

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=484)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'APINVOICE' (Cost=4 Card=1 Bytes=484)
2 1 INDEX (UNIQUE SCAN) OF 'APISET1' (UNIQUE) (Cost=3 Card=10589683)

From v$session_wait P1/P2, the sequential file wait was on APISET1 index.

Table APINVOICE's record count is 13470630
The cardinality from INDEX Unique Scan step is 10589683. . Does this mean that the optimizer had to traverse 10589683 (tentatively) rowids to arrive at the one required by the query ?

Thanks
Sanji

Tom Kyte
December 07, 2006 - 8:51 am UTC

do you have a full example with the create table and dbms_stats.set_table_stats/index_stats to reproduce with - that looks odd.




Mistake

sanji, December 07, 2006 - 10:17 am UTC

I made a mistake. The query from v$session_wait was infact

select TO_CHAR(API.COMPANY,'FM0999'), API.VENDOR, API.INVOICE, TO_CHAR(API.SUFFIX,'FM099'),
TO_CHAR(API.CANCEL_SEQ,'FM0999'), TO_CHAR(API.CANCEL_DATE, 'YYYYMMDD'), TO_CHAR(API.BATCH_NUM,'FM099999'),
TO_CHAR(API.BATCH_DATE, 'YYYYMMDD'), API.VOUCHER_NBR, API.AUTH_CODE, API.PROC_LEVEL, API.ACCR_CODE, API.INVOICE_TYPE,
API.INV_CURRENCY, API.PAY_CURRENCY, TO_CHAR(API.INVOICE_DTE, 'YYYYMMDD'), API.PURCH_FR_LOC, API.PO_NUMBER,
TO_CHAR(API.PO_RELEASE,'FM0999'), API.PO_CODE, API.DESCRIPTION, API.BASE_INV_AMT, API.BASE_ACT_AMT,
TO_CHAR(API.BASE_ND,'FM0'), API.TRAN_INV_AMT, API.TRAN_ALOW_AMT, API.TRAN_TXBL_AMT, TO_CHAR(API.TRAN_ND,'FM0'),
API.TRAN_TAX_AMT, API.BASE_DISC_AMT, API.TRAN_DISC_AMT, API.BASE_TOT_PMT, API.TRAN_TOT_PMT, API.BASE_TOT_DIST,
API.TRAN_TOT_DIST, API.TRAN_TOT_TAX, API.TRAN_TOT_TXBL, API.TRAN_PAID_AMT, API.ORIG_CNV_RATE, API.ANTICIPATION,
API.DISCOUNT_RT, TO_CHAR(API.DISC_DATE, 'YYYYMMDD'), TO_CHAR(API.DUE_DATE, 'YYYYMMDD'), TO_CHAR(API.NBR_SPLIT_PMT,'FM099'),
API.SPLIT_PMT_SCH, TO_CHAR(API.NBR_RECUR_PMT,'FM099'), API.RECUR_FREQ, API.REMIT_TO_CODE, API.CASH_CODE, API.BANK_INST_CODE,
API.CURR_RECALC, API.TAX_CODE, API.INCOME_CODE, API.DIST_CODE, TO_CHAR(API.REC_STATUS,'FM0'), TO_CHAR(API.CREATE_DATE, 'YYYYMMDD'),
TO_CHAR(API.DISTRIB_DATE, 'YYYYMMDD'), API.OPERATOR, TO_CHAR(API.CREATION_TIME,'FM099999'), API.VENDOR_GROUP, API.PAY_VENDOR,
API.PAY_GROUP, API.INVOICE_GROUP, TO_CHAR(API.LAST_DIST_SEQ,'FM0999'), TO_CHAR(API.LAST_PMT_SEQ,'FM0999'), API.DISCOUNT_CODE,
API.INVOICE_SOURCE, API.INVC_REF_TYPE, API.APPROVED_FLAG, API.APPRV_OPERATOR,TO_CHAR(API.RETURN_NUMBER,'FM0999999999'),
API.JRNL_BOOK_NBR, API.TAX_POINT, TO_CHAR(API.OBJ_ID,'FM099999999999'), TO_CHAR(API.RECON_DATE, 'YYYYMMDD'), TO_CHAR(API.POD_PRINTED,'FM0'),
API.MATCH_REF_NBR, API.MATCH_FL, API.TERMS_CD, TO_CHAR(API.RCPT_INV_DATE, 'YYYYMMDD'), API.RETAIL_AMT, TO_CHAR(API.MATCH_STATUS,'FM0'),
API.REASON_CODE, API.HANDLING_CODE, API.MATCH_AMT, API.AOC_ALLOW_AMT, API.LOCATION, TO_CHAR(API.MATCH_OBJ_ID,'FM099999999999'),
API.CBPRINT_FL, API.MATCH_TABLE, API.TAX_CODE_CNTL, TO_CHAR(API.LAST_MATCH_LN,'FM0999'), API.MATCH_LEVEL,
TO_CHAR(API.MATCH_DATE, 'YYYYMMDD'), API.PO_INV_TAX, API.BYPASS_MATCH, API.SERVICE_FL, API.SERVICE_AMT, API.BUYER, API.FINAL_DST_FLAG,
API.NOTC, API.STAT_PROC, API.SHIP_VIA, API.UNLOADING_PORT, TO_CHAR(API.INTRASTAT_NBR,'FM099999999999'), API.DROPSHIP_FL, API.FOB_CODE,
TO_CHAR(API.JBK_SEQ_NBR,'FM0999999999'), API.L_INDEX
from LAWSON.APINVOICE API
where API.COMPANY=:a
and API.VENDOR=:b
and API.INVOICE=:c
and API.SUFFIX=:d
and API.CANCEL_SEQ=:e

and the explain plan

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=484)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'APINVOICE' (Cost=4 Card=1 Bytes=484)
2 1 INDEX (UNIQUE SCAN) OF 'APISET1' (UNIQUE) (Cost=3 Card=10589683)

The table definition is
CREATE TABLE APINVOICE (
COMPANY NUMBER(4) NOT NULL,VENDOR CHAR(9) NOT NULL,INVOICE CHAR(22) NOT NULL,
SUFFIX NUMBER(3) NOT NULL,CANCEL_SEQ NUMBER(4) NOT NULL,CANCEL_DATE DATE NOT NULL,
BATCH_NUM NUMBER(6) NOT NULL,BATCH_DATE DATE NOT NULL,VOUCHER_NBR CHAR(10) NOT NULL,
AUTH_CODE CHAR(3) NOT NULL,PROC_LEVEL CHAR(5) NOT NULL,ACCR_CODE CHAR(4) NOT NULL,
INVOICE_TYPE CHAR(1) NOT NULL,INV_CURRENCY CHAR(5) NOT NULL,PAY_CURRENCY CHAR(5) NOT NULL,
INVOICE_DTE DATE NOT NULL,PURCH_FR_LOC CHAR(4) NOT NULL,PO_NUMBER CHAR(14) NOT NULL,
PO_RELEASE NUMBER(4) NOT NULL,PO_CODE CHAR(4) NOT NULL,DESCRIPTION CHAR(30) NOT NULL,
BASE_INV_AMT NUMBER(15,2) NOT NULL,BASE_ACT_AMT NUMBER(15,2) NOT NULL,BASE_ND NUMBER(1) NOT NULL,
TRAN_INV_AMT NUMBER(15,2) NOT NULL,TRAN_ALOW_AMT NUMBER(15,2) NOT NULL,TRAN_TXBL_AMT NUMBER(15,2) NOT NULL,
TRAN_ND NUMBER(1) NOT NULL,TRAN_TAX_AMT NUMBER(15,2) NOT NULL,BASE_DISC_AMT NUMBER(15,2) NOT NULL,
TRAN_DISC_AMT NUMBER(15,2) NOT NULL,BASE_TOT_PMT NUMBER(15,2) NOT NULL,TRAN_TOT_PMT NUMBER(15,2) NOT NULL,
BASE_TOT_DIST NUMBER(15,2) NOT NULL,TRAN_TOT_DIST NUMBER(15,2) NOT NULL,TRAN_TOT_TAX NUMBER(15,2) NOT NULL,
TRAN_TOT_TXBL NUMBER(15,2) NOT NULL,TRAN_PAID_AMT NUMBER(15,2) NOT NULL,ORIG_CNV_RATE NUMBER(12,6) NOT NULL,
ANTICIPATION CHAR(1) NOT NULL,DISCOUNT_RT NUMBER(5,5) NOT NULL,DISC_DATE DATE NOT NULL,
DUE_DATE DATE NOT NULL,nBR_SPLIT_PMT NUMBER(3) NOT NULL,SPLIT_PMT_SCH CHAR(1) NOT NULL,
NBR_RECUR_PMT NUMBER(3) NOT NULL,RECUR_FREQ CHAR(1) NOT NULL,REMIT_TO_CODE CHAR(4) NOT NULL,
CASH_CODE CHAR(4) NOT NULL,BANK_INST_CODE CHAR(3) NOT NULL,CURR_RECALC CHAR(1) NOT NULL,
TAX_CODE CHAR(10) NOT NULL,INCOME_CODE CHAR(4) NOT NULL,DIST_CODE CHAR(9) NOT NULL,
REC_STATUS NUMBER(1) NOT NULL,CREATE_DATE DATE NOT NULL,DISTRIB_DATE DATE NOT NULL,
OPERATOR CHAR(10) NOT NULL,CREATION_TIME NUMBER(6) NOT NULL,VENDOR_GROUP CHAR(4) NOT NULL,
PAY_VENDOR CHAR(9) NOT NULL,PAY_GROUP CHAR(4) NOT NULL,INVOICE_GROUP CHAR(4) NOT NULL,
LAST_DIST_SEQ NUMBER(4) NOT NULL,LAST_PMT_SEQ NUMBER(4) NOT NULL,DISCOUNT_CODE CHAR(10) NOT NULL,
INVOICE_SOURCE CHAR(1) NOT NULL,INVC_REF_TYPE CHAR(2) NOT NULL,APPROVED_FLAG CHAR(1) NOT NULL,
APPRV_OPERATOR CHAR(10) NOT NULL,RETURN_NUMBER NUMBER(10) NOT NULL,JRNL_BOOK_NBR CHAR(12) NOT NULL,
TAX_POINT CHAR(1) NOT NULL,OBJ_ID NUMBER(12) NOT NULL,RECON_DATE DATE NOT NULL,
POD_PRINTED NUMBER(1) NOT NULL,MATCH_REF_NBR CHAR(22) NOT NULL,MATCH_FL CHAR(1) NOT NULL,
TERMS_CD CHAR(5) NOT NULL,RCPT_INV_DATE DATE NOT NULL,RETAIL_AMT NUMBER(15,2) NOT NULL,
MATCH_STATUS NUMBER(1) NOT NULL,REASON_CODE CHAR(4) NOT NULL,HANDLING_CODE CHAR(4) NOT NULL,
MATCH_AMT NUMBER(15,2) NOT NULL,AOC_ALLOW_AMT NUMBER(15,2) NOT NULL,LOCATION CHAR(5) NOT NULL,
MATCH_OBJ_ID NUMBER(12) NOT NULL,CBPRINT_FL CHAR(1) NOT NULL,MATCH_TABLE CHAR(10) NOT NULL,
TAX_CODE_CNTL CHAR(1) NOT NULL,LAST_MATCH_LN NUMBER(4) NOT NULL,MATCH_LEVEL CHAR(3) NOT NULL,
MATCH_DATE DATE NOT NULL,PO_INV_TAX NUMBER(15,2) NOT NULL,BYPASS_MATCH CHAR(1) NOT NULL,
SERVICE_FL CHAR(1) NOT NULL,SERVICE_AMT NUMBER(15,2) NOT NULL,BUYER CHAR(3) NOT NULL,
FINAL_DST_FLAG CHAR(1) NOT NULL,NOTC CHAR(2) NOT NULL,STAT_PROC CHAR(6) NOT NULL,
SHIP_VIA CHAR(12) NOT NULL,UNLOADING_PORT CHAR(5) NOT NULL,INTRASTAT_NBR NUMBER(12) NOT NULL,
DROPSHIP_FL CHAR(1) NOT NULL,FOB_CODE CHAR(3) NOT NULL,JBK_SEQ_NBR NUMBER(10) NOT NULL,
L_INDEX CHAR(4) NOT NULL,APISET11_SS_SW CHAR(1) NOT NULL,APISET14_SS_SW CHAR(1) NOT NULL,
APISET2_SS_SW CHAR(1) NOT NULL,APISET7_SS_SW CHAR(1) NOT NULL,APISET8_SS_SW CHAR(1) NOT NULL,
L_ATAPI_SS_SW CHAR(1) NOT NULL,
CONSTRAINT APISET1 PRIMARY KEY(COMPANY, VENDOR,INVOICE, SUFFIX, CANCEL_SEQ)
)

Tom, should i set the table stats and then regenerate the explain plan ?
It took almost one and half hour yesterday to execute
dbms_stats.gather_table_stats('LAWSON','APINVOICE',estimate_percent=>35,cascade=>true,degree=>4)

Thanks
Sanji

Tom Kyte
December 07, 2006 - 1:11 pm UTC

give me a simple - small - create table, along with create index (something I can cut and paste and run) and use dbms_stats to set appropriate enough stats to reproduce your issue

so we can all see it in our own database.

Table Stats details

Sanji, December 07, 2006 - 10:38 am UTC

In case, if the following information is required.

select table_name, num_rows,blocks,empty_blocks,avg_space,avg_row_len,sample_size,global_stats,user_stats
from dba_tables
where table_name='APINVOICE';

TABLE_NAME NUM_ROWS BLOCKS EMPTY_BLOCKS AVG_SPACE AVG_ROW_LEN SAMPLE_SIZE GLO USE
---------- ---------- ---------- ------------ ---------- ----------- ----------- --- ---
APINVOICE 13474569 2248283 17330 442 589 4716099 YES NO

Thanks
Sanji

Cannot reproduce the problem

Sanji, December 07, 2006 - 2:19 pm UTC

Tried reproducing the scenario with a smaller table structure. Couldn't reproduce.
I then took the export of the table and imported it in a test database (similar configuration). The same scenario could not be reproduced either.

The explain plan of the same query in the test db

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=204)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'APINVOICE' (Cost=4 Card=1 Bytes=204)
2 1 INDEX (UNIQUE SCAN) OF 'APISET1' (UNIQUE) (Cost=3 Card=1)

The problem is occuring only on the production box.

Coming back to the question

1> Does the cardinality in the index (unique scan) suggest that the optimizer would traverse that many rowids to arrive at the result set ?

2> When i exported/imported this table on the test machine, the explain plan of the same query was perfect. Could, the reorg, have done the trick ?
The stats on the test machine were collected as they were on the production box.

Thanks
Sanji

Tom Kyte
December 07, 2006 - 5:47 pm UTC

1) it looks funny, wrong
2) no

Tuning

Sanji, December 08, 2006 - 10:27 am UTC

Tom,

Is there an alternative to approaching this problem.

For academic purposes too i'd want to understand the concept behind the cardinality represented in the INDEX (UNIQUE SCAN) step being so high (comparitively), if at all the cardinality matters here.

I cannot reproduce the problem on the test machine and the query is persistent on the production server.
The db_block_size is 4Kb and all the tablespaces are dictionary managed.

Would appreciate any suggestion.

Thanks
Sanji

Tom Kyte
December 09, 2006 - 12:28 pm UTC

it looks wrong, don't know how else to say that to you - it does not look correct.

why choose hash join

Harry Zhang, December 12, 2006 - 4:22 am UTC

Hi tom,

Why the parser chose hash join without hints while with hints it chose nested loop. Thanks!

SQL> select /*+ INDEX(ID ID_PK)*/ distinct fd.field_id, fd.field_name, fd.field_valid_id, fd.field_dsply_length
  2   from field_def fd, instrument i, instrument_data id
 where i.mod_group_id=109521 and id.ric=i.ric and fd.field_id=id.field_id
  3    4   order by fd.field_name
  5  /

Elapsed: 00:00:00.52

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=7847 Card=1604 Bytes=83408)
   1    0   SORT (UNIQUE) (Cost=7828 Card=1604 Bytes=83408)
   2    1     HASH JOIN (Cost=7808 Card=1604 Bytes=83408)
   3    2       TABLE ACCESS (FULL) OF 'FIELD_DEF' (Cost=7 Card=3611 Bytes=75831)
   4    2       NESTED LOOPS (Cost=7799 Card=11043 Bytes=342333)
   5    4         TABLE ACCESS (BY INDEX ROWID) OF 'INSTRUMENT' (Cost=1123 Card=3338 Bytes=50070)
   6    5           INDEX (RANGE SCAN) OF 'INS_MG_FK_I' (NON-UNIQUE) (Cost=11 Card=3338)
   7    4         INDEX (RANGE SCAN) OF 'ID_PK' (UNIQUE) (Cost=2 Card=3 Bytes=48)




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


SQL> select distinct fd.field_id, fd.field_name, fd.field_valid_id, fd.field_dsply_length
 from field_def fd, instrument i, instrument_data id
 where i.mod_group_id=109521 and id.ric=i.ric and fd.field_id=id.field_id
  2   order by fd.field_name  3    4
  5  /

Elapsed: 00:00:19.27

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3808 Card=1604 Bytes=83408)
   1    0   SORT (UNIQUE) (Cost=3789 Card=1604 Bytes=83408)
   2    1     HASH JOIN (Cost=3769 Card=1604 Bytes=83408)
   3    2       TABLE ACCESS (FULL) OF 'FIELD_DEF' (Cost=7 Card=3611 Bytes=75831)
   4    2       HASH JOIN (Cost=3760 Card=11043 Bytes=342333)
   5    4         TABLE ACCESS (BY INDEX ROWID) OF 'INSTRUMENT' (Cost=1123 Card=3338 Bytes=50070)
   6    5           INDEX (RANGE SCAN) OF 'INS_MG_FK_I' (NON-UNIQUE) (Cost=11 Card=1)
   7    4         INDEX (FAST FULL SCAN) OF 'ID_PK' (UNIQUE) (Cost=2443 Card=6743363 Bytes=107893808)




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

 

Tom Kyte
December 12, 2006 - 7:00 am UTC

are the estimated cardinalities in the query plan anywhere close to "reality"

Harry Zhang, December 13, 2006 - 2:30 am UTC

select distinct fd.field_id, fd.field_name, fd.field_valid_id, fd.field_dsply_length
from field_def fd, instrument i, instrument_data id
where i.mod_group_id=109521 and id.ric=i.ric and fd.field_id=id.field_id
order by fd.field_name

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.03 0.02 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 6.66 6.87 0 25679 0 3
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 6.69 6.89 0 25679 0 3

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

Rows Row Source Operation
------- ---------------------------------------------------
3 SORT UNIQUE
682 HASH JOIN
3611 TABLE ACCESS FULL FIELD_DEF
682 HASH JOIN
340 TABLE ACCESS BY INDEX ROWID INSTRUMENT
340 INDEX RANGE SCAN INS_MG_FK_I (object id 29597)
6743363 INDEX FAST FULL SCAN ID_PK (object id 29608)

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

select /*+ INDEX(ID ID_PK)*/ distinct fd.field_id, fd.field_name, fd.field_valid_id, fd.field_dsply_length
from field_def fd, instrument i, instrument_data id
where i.mod_group_id=109521 and id.ric=i.ric and fd.field_id=id.field_id
order by fd.field_name

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.00 0 0 0 0
Execute 1 0.01 0.00 0 0 0 0
Fetch 2 0.04 0.03 0 888 0 3
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.06 0.04 0 888 0 3

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

Rows Row Source Operation
------- ---------------------------------------------------
3 SORT UNIQUE
682 HASH JOIN
3611 TABLE ACCESS FULL FIELD_DEF
682 NESTED LOOPS
340 TABLE ACCESS BY INDEX ROWID INSTRUMENT
340 INDEX RANGE SCAN INS_MG_FK_I (object id 29597)
682 INDEX RANGE SCAN ID_PK (object id 29608)

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

select /*+ USE_NL (i,id)*/ distinct fd.field_id, fd.field_name, fd.field_valid_id, fd.field_dsply_length
from field_def fd, instrument i, instrument_data id
where i.mod_group_id=109521 and id.ric=i.ric and fd.field_id=id.field_id
order by fd.field_name

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.02 0.01 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.04 0.03 0 888 0 3
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.06 0.05 0 888 0 3

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

Rows Row Source Operation
------- ---------------------------------------------------
3 SORT UNIQUE
682 HASH JOIN
3611 TABLE ACCESS FULL FIELD_DEF
682 NESTED LOOPS
340 TABLE ACCESS BY INDEX ROWID INSTRUMENT
340 INDEX RANGE SCAN INS_MG_FK_I (object id 29597)
682 INDEX RANGE SCAN ID_PK (object id 29608)

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


Tom Kyte
December 13, 2006 - 7:46 am UTC

and so I don't have to - did you compare the ESTIMATED to the ACTUAL and what did you see - how about you point it out to us.

Harry Zhang, December 14, 2006 - 2:00 am UTC

I think the instrment table estimated cardinalities is wrong.
5 4 TABLE ACCESS (BY INDEX ROWID) F 'INSTRUMENT' (Cost=1123 Card=3338 Bytes=50070)

Actual card=340, so the optimizer chose hash join.

Currently we use
DBMS_STATS.GATHER_TABLE_STATS ( ownname => 'OPS$GLOBAL',tabname => 'INSTRUMENT',estimate_percent => 25, method_opt => 'FOR ALL INDEXED COLUMNS SIZE 254',cascade => TRUE,degree => 4)

for analyze, need to change the way we analyze?

Thanks


Tom Kyte
December 15, 2006 - 8:06 am UTC

are you sure you meant to gather histograms

Raj, December 18, 2006 - 11:15 am UTC

Hi Tom,
I have the following query, which takes a long time to execute for this caseid alone ('515157'). I have tried analyze command but there was no improvement in response time. The response time for all other caseids is just the blink of an eye and there are not much difference in the number of records. I have also noticed that this uses only indexes. Apart from this I don't have any clue from the explain plan. The explain plan is the same for all case ids. Can you please help me out. I'm new to performance tuning.
SELECT pt.plcysymbl, pt.plcyid
FROM view_dbl_bllgrp v,
exprncgrp_bllgrp_assoc eba,
pega_exprncgrp_assoc pexa,
case_commission_plan ccp,
policy_translation pt
WHERE EXISTS (
SELECT 'X'
FROM view_dbl_bllgrp v2,
exprncgrp_bllgrp_assoc eba2,
pega_exprncgrp_assoc pexa2,
case_commission_plan ccp2,
policy_translation pt2
WHERE ( NVL (ccp2.fltamt, 0) != NVL (ccp.fltamt, 0)
OR NVL (ccp2.frqncycd, ' ') != NVL (ccp.frqncycd, ' ')
OR NVL (ccp2.cmmssnschdlid, 0) !=
NVL (ccp.cmmssnschdlid, 0)
)
AND NVL (ccp2.cmmssnplntrmdt, v2.frstbllduedt + 1) >
v2.frstbllduedt
AND v2.bllgrpid = eba2.bllgrpid
AND v2.caseid = eba2.caseid
AND NVL (eba2.exprncgrpbllgrptrmdt,
pexa2.pegaexprncgrpeffctvdt + 1
) > pexa2.pegaexprncgrpeffctvdt
AND eba2.exprncgrpbllgrpeffctvdt <
NVL (pexa2.pegaexprncgrptrmdt,
eba2.exprncgrpbllgrpeffctvdt + 1
)
AND eba2.exprncgrpid = pexa2.exprncgrpid
AND eba2.caseid = pexa2.caseid
AND pexa2.fndngmthdplnnbr = pt2.fndngmthdplnnbr
AND pexa2.cvrgplnnbr = pt2.cvrgplnnbr
AND pexa2.fndngmthdcd = pt2.fndngmthdcd
AND pexa2.cvrgtypcd = pt2.cvrgtypcd
AND pexa2.cvrgctgrycd = pt2.cvrgctgrycd
AND pexa2.caseid = pt2.caseid
AND NVL (ccp2.cmmssnplntrmdt, ccp.cmmssnplneffctvdt + 1) >
ccp.cmmssnplneffctvdt
AND ccp2.cmmssnplneffctvdt <
NVL (ccp.cmmssnplntrmdt, ccp2.cmmssnplneffctvdt + 1)
AND ccp2.plnnbr =
DECODE (ccp2.fndngmthdcd,
'N/A', pt2.cvrgplnnbr,
pt2.fndngmthdplnnbr
)
AND ccp2.fndngmthdcd IN ('N/A', pt2.fndngmthdcd)
AND ccp2.cvrgtypcd IN ('N/A', pt2.cvrgtypcd)
AND ccp2.cvrgctgrycd IN ('N/A', pt2.cvrgctgrycd)
AND ccp2.caseid = pt2.caseid
AND ( pt2.fndngmthdplnnbr != pt.fndngmthdplnnbr
OR pt2.cvrgplnnbr != pt.cvrgplnnbr
OR pt2.fndngmthdcd != pt.fndngmthdcd
OR pt2.cvrgtypcd != pt.cvrgtypcd
OR pt2.cvrgctgrycd != pt.cvrgctgrycd
)
AND pt2.plcysymbl = pt.plcysymbl
AND pt2.plcyid = pt.plcyid
AND pt2.caseid = pt.caseid)
AND NVL (ccp.cmmssnplntrmdt, v.frstbllduedt + 1) > v.frstbllduedt
AND v.bllgrpid = eba.bllgrpid
AND v.caseid = eba.caseid
AND NVL (eba.exprncgrpbllgrptrmdt, pexa.pegaexprncgrpeffctvdt + 1) >
pexa.pegaexprncgrpeffctvdt
AND eba.exprncgrpbllgrpeffctvdt <
NVL (pexa.pegaexprncgrptrmdt, eba.exprncgrpbllgrpeffctvdt + 1)
AND eba.exprncgrpid = pexa.exprncgrpid
AND eba.caseid = pexa.caseid
AND pexa.fndngmthdplnnbr = pt.fndngmthdplnnbr
AND pexa.cvrgplnnbr = pt.cvrgplnnbr
AND pexa.fndngmthdcd = pt.fndngmthdcd
AND pexa.cvrgtypcd = pt.cvrgtypcd
AND pexa.cvrgctgrycd = pt.cvrgctgrycd
AND pexa.caseid = pt.caseid
AND ccp.plnnbr =
DECODE (ccp.fndngmthdcd,
'N/A', pt.cvrgplnnbr,
pt.fndngmthdplnnbr
)
AND ccp.fndngmthdcd IN ('N/A', pt.fndngmthdcd)
AND ccp.cvrgtypcd IN ('N/A', pt.cvrgtypcd)
AND ccp.cvrgctgrycd IN ('N/A', pt.cvrgctgrycd)
AND ccp.caseid = pt.caseid
AND (pt.caseid, pt.plcyid, pt.plcysymbl, 'MORE') IN (
SELECT caseid, plcyid, plcysymbl,
DECODE (COUNT (DISTINCT cvrgctgrycd
|| '/'
|| cvrgtypcd
|| '/'
|| fndngmthdcd
|| '/'
|| cvrgplnnbr
|| '/'
|| fndngmthdplnnbr
),
1, 'ONE',
'MORE'
)
FROM policy_translation
WHERE caseid = '515157'
GROUP BY caseid, plcyid, plcysymbl)
ORDER BY pt.plcysymbl, pt.plcyid

Elapsed: 00:20:15.08

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=86 Card=1 Bytes=181)
1 0 SORT (ORDER BY) (Cost=20 Card=1 Bytes=181)
2 1 FILTER
3 2 TABLE ACCESS (BY INDEX ROWID) OF 'CASE_COMMISSION_PLAN
' (Cost=2 Card=1 Bytes=38)

4 3 NESTED LOOPS (Cost=14 Card=1 Bytes=181)
5 4 NESTED LOOPS (Cost=12 Card=1 Bytes=143)
6 5 NESTED LOOPS (Cost=9 Card=1 Bytes=106)
7 6 NESTED LOOPS (Cost=6 Card=1 Bytes=67)
8 7 NESTED LOOPS (Cost=4 Card=1 Bytes=47)
9 8 NESTED LOOPS (Cost=3 Card=1 Bytes=28)
10 9 INDEX (UNIQUE SCAN) OF 'UICASE' (UNIQUE) (Cost=1 Card=1 Bytes=8)

11 9 TABLE ACCESS (BY INDEX ROWID) OF 'CASE_ADMIN_PROVISION' (Cost=2 Card=1 Bytes=20)

12 11 INDEX (RANGE SCAN) OF 'UICASEADMINPROV' (UNIQUE) (Cost=1 Card=3)

13 8 TABLE ACCESS (BY INDEX ROWID) OF 'BILL_GROUP' (Cost=1 Card=1 Bytes=19)

14 13 INDEX (UNIQUE SCAN) OF 'UIBILLGROUP' (UNIQUE)

15 7 TABLE ACCESS (BY INDEX ROWID) OF 'EXPRNCGRP_BLLGRP_ASSOC' (Cost=2 Card=1 Bytes=20)

16 15 INDEX (RANGE SCAN) OF 'IEXPBLLGRPASSOC' (NON-UNIQUE) (Cost=1 Card=1)

17 6 TABLE ACCESS (BY INDEX ROWID) OF 'PEGA_EXPRNCGRP_ASSOC' (Cost=3 Card=1 Bytes=39)

18 17 INDEX (RANGE SCAN) OF 'IPEGAEXPGRPASSOC' (NON-UNIQUE) (Cost=2 Card=1)

19 5 TABLE ACCESS (BY INDEX ROWID) OF 'POLICY_TRANSLATION' (Cost=3 Card=1 Bytes=37)

20 19 INDEX (RANGE SCAN) OF 'IPOLICYTRNSLTN1' (NON-UNIQUE) (Cost=2 Card=1)

21 4 INDEX (RANGE SCAN) OF 'UICASECOMMPLN' (UNIQUE) (Cost=1 Card=1)

22 2 TABLE ACCESS (BY INDEX ROWID) OF 'PEGA_EXPRNCGRP_ASSOC' (Cost=3 Card=1 Bytes=39)

23 22 NESTED LOOPS (Cost=14 Card=1 Bytes=181)
24 23 NESTED LOOPS (Cost=11 Card=1 Bytes=142)
25 24 NESTED LOOPS (Cost=9 Card=1 Bytes=122)
26 25 NESTED LOOPS (Cost=6 Card=1 Bytes=85)
27 26 NESTED LOOPS (Cost=4 Card=1 Bytes=47)
28 27 NESTED LOOPS (Cost=3 Card=1 Bytes=28)
29 28 INDEX (UNIQUE SCAN) OF 'UICASE' (UNIQUE)(Cost=1 Card=1 Bytes=8)

30 28 TABLE ACCESS (BY INDEX ROWID) OF 'CASE_ADMIN_PROVISION' (Cost=2 Card=1 Bytes=20)

31 30 INDEX (RANGE SCAN) OF 'UICASEADMINPROV' (UNIQUE) (Cost=1 Card=3)

32 27 TABLE ACCESS (BY INDEX ROWID) OF 'BILL_GROUP' (Cost=1 Card=1 Bytes=19)

33 32 INDEX (UNIQUE SCAN) OF 'UIBILLGROUP' (UNIQUE)

34 26 TABLE ACCESS (BY INDEX ROWID) OF 'CASE_COMMISSION_PLAN' (Cost=2 Card=1 Bytes=38)

35 34 INDEX (RANGE SCAN) OF 'UICASECOMMPLN' (UNIQUE) (Cost=1 Card=1)

36 25 TABLE ACCES