Skip to Main Content
  • Questions
  • explain plan on range-partitioned table

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Markus.

Asked: January 14, 2002 - 9:20 am UTC

Last updated: April 20, 2006 - 8:11 am UTC

Version: 8.1.7

Viewed 10K+ times! This question is

You Asked

Hi Tom!

I recenty dicovered some (in my eyes) very strange behaviour on our 8.1.7 installation (AIX 4.3):

We have a very large table ("src_trn_det") that is range-partitioned by a date column ("cob_dt"):

CREATE TABLE SRC_TRN_DET (
SEQ_NO NUMBER (10) NOT NULL,
CRES_SYS_ID NUMBER (3) NOT NULL,
COB_DT DATE NOT NULL,
TRN_SRC_CD VARCHAR2 (40) NOT NULL,
TRN_CD VARCHAR2 (30) NOT NULL,
RUN_ID NUMBER (15) NOT NULL,
MANUAL_IND CHAR (1) DEFAULT 'N' NOT NULL,
TRN_LINK_CD VARCHAR2 (30),
FAC_LIMIT_SRC_CD VARCHAR2 (40),
FAC_LIMIT_CD VARCHAR2 (30),
FAC_SRC_CD VARCHAR2 (40),
FAC_CD VARCHAR2 (30),
BRANCH_ID NUMBER (10),
ENTITY_CCDB_ID NUMBER (11),
RT_TYPE_CD CHAR (1),
RT_CB_CCDB_ID NUMBER (11),
RT_COV_TYPE_CD VARCHAR2 (2),
RT_COV_SRC_CD NUMBER (28,2),
RT_COV_CD VARCHAR2 (30),
RT_BUS_AREA_UBR_ID NUMBER (7),
CPTY_TYPE_CD VARCHAR2 (40),
CPTY_CD VARCHAR2 (50),
CPTY_LONG_NAME VARCHAR2 (120),
CPTY_CCDB_ID NUMBER (11),
ORIG_TYPE_CD VARCHAR2 (40),
ORIG_CD VARCHAR2 (50),
ORIG_CCDB_ID NUMBER (11),
INTERCO_IND CHAR (1),
MA_SRC_CD VARCHAR2 (40),
MA_CD VARCHAR2 (30),
MA_TYPE_CD VARCHAR2 (30),
MA_SPL_NOTE VARCHAR2 (255),
MASTER_MA_CD VARCHAR2 (30),
CA_CD VARCHAR2 (30),
SRC_PROD_TYPE_CD VARCHAR2 (40),
GRC_PROD_TYPE_ID NUMBER (5),
TRADE_ENTITY_CCDB_ID NUMBER (11),
SRC_PROXY_UBR_CD VARCHAR2 (40),
LOWEST_LEVEL_UBR_ID NUMBER (7),
DIVISION_UBR_ID NUMBER (7),
BUS_AREA_UBR_ID NUMBER (7),
PROD_AREA_UBR_ID NUMBER (7),
BOOK_TYPE_CD CHAR (1),
TRN_TYPE_CD CHAR (1),
DEAL_TYPE_CD CHAR (1),
XCHG_CTRY_CD VARCHAR2 (4),
ORIG_NOTIONAL_AMT NUMBER (27,2),
ORIG_NOTIONAL_CCY_CD CHAR (3),
OTHER_ORIG_NOTIONAL_AMT NUMBER (27,2),
OTHER_ORIG_NOTIONAL_CCY_CD CHAR (3),
CUR_NOTIONAL_AMT NUMBER (27,2),
CUR_NOTIONAL_CCY_CD CHAR (3),
OTHER_CUR_NOTIONAL_AMT NUMBER (27,2),
OTHER_CUR_NOTIONAL_CCY_CD CHAR (3),
ACCRUED_INT_AMT NUMBER (27,2),
ACCRUED_INT_CCY_CD CHAR (3),
TRADE_DT DATE,
START_DT DATE,
MATURITY_DT DATE,
UL_MATURITY_DT DATE,
AMORT_IND CHAR (1),
AMORT_ID NUMBER (3),
SETTLE_STATUS_ID NUMBER (1),
DELIVERY_DT DATE,
UL_DELIVERY_DT DATE,
CASH_SETTLE_IND CHAR (1),
CONTRACT_AMT NUMBER (27,2),
CONTRACT_COUNT NUMBER (15),
PAY_CONTRACT_AMT NUMBER (27,2),
PAY_CONTRACT_CCY_CD CHAR (3),
PAY_FWD_AMT NUMBER (27,2),
PAY_FWD_CCY_CD CHAR (3),
PAY_UL_SEC_TYPE_CD VARCHAR2 (40),
PAY_UL_SEC_CD VARCHAR2 (50),
PAY_UL_SEC_CCY_CD CHAR (3),
PAY_UL_ISS_CCDB_ID NUMBER (11),
PAY_INT_RATE_TYPE_CD VARCHAR2 (20),
PAY_INT_RATE NUMBER (11,7),
PAY_NEXT_FIX_CPN_DT DATE,
PAY_INT_INTERVAL NUMBER (3),
RCV_CONTRACT_AMT NUMBER (27,2),
RCV_CONTRACT_CCY_CD CHAR (3),
RCV_FWD_AMT NUMBER (27,2),
RCV_FWD_CCY_CD CHAR (3),
RCV_UL_SEC_TYPE_CD VARCHAR2 (40),
RCV_UL_SEC_CD VARCHAR2 (50),
RCV_UL_SEC_CCY_CD CHAR (3),
RCV_UL_ISS_CCDB_ID NUMBER (11),
RCV_INT_RATE_TYPE_CD VARCHAR2 (20),
RCV_INT_RATE NUMBER (11,7),
RCV_NEXT_FIX_CPN_DT DATE,
RCV_INT_INTERVAL NUMBER (3),
AVG_MTH_DUE_AMT NUMBER (27,2),
AVG_MTH_DUE_CCY_CD CHAR (3),
AVG_YTD_DUE_AMT NUMBER (27,2),
AVG_YTD_DUE_CCY_CD CHAR (3),
OPT_TYPE_CD CHAR (1),
STRIKE_FWD_FUT_PRICE_AMT NUMBER (27,2),
STRIKE_FWD_FUT_PRICE_CCY_CD CHAR (3),
VALUATION_DT DATE,
MTM_AMT NUMBER (27,2),
MTM_CCY_CD CHAR (3),
PAY_UL_MTM_AMT NUMBER (27,2),
PAY_UL_MTM_CCY_CD CHAR (3),
RCV_UL_MTM_AMT NUMBER (27,2),
RCV_UL_MTM_CCY_CD CHAR (3),
BAL_SHEET_EXP_AMT NUMBER (27,2),
BAL_SHEET_EXP_CCY_CD CHAR (3),
CAD_RISK_WEIGHT_PCT NUMBER (6,3),
TRADE_RELATED_IND CHAR (1),
RISK_PROVISION_BAL_AMT NUMBER (27,2),
NEW_RISK_PROVISION_AMT NUMBER (27,2),
REL_RISK_PROVISION_AMT NUMBER (27,2),
RECOV_AMT NUMBER (27,2),
RISK_WRITE_OFF_AMT NUMBER (27,2),
RISK_PROVISION_CCY_CD CHAR (3),
FIRST_RISK_PROVISION_DT DATE,
RESTRUCT_IND CHAR (1),
NON_PERF_IND CHAR (1),
NON_PERF_RCV_INT_AMT NUMBER (27,2),
OVERDUE_DAY_COUNT NUMBER (6),
CTRY_RISK_PROVISION_BAL_AMT NUMBER (27,2),
ABS_TRN_CD VARCHAR2 (30),
RISK_MITIGATING_PCT NUMBER (6,3),
PAY_INT_AMT NUMBER (27,2),
RCV_INT_AMT NUMBER (27,2),
INT_CCY_CD CHAR (3),
MARGIN_PCT NUMBER (6,3),
MKT_MARGIN_PCT NUMBER (6,3),
STRATEGIC_LEND_IND CHAR (1),
YEAR00_NOTIONAL_AMT NUMBER (27,2),
YEAR01_NOTIONAL_AMT NUMBER (27,2),
YEAR02_NOTIONAL_AMT NUMBER (27,2),
YEAR03_NOTIONAL_AMT NUMBER (27,2),
YEAR04_NOTIONAL_AMT NUMBER (27,2),
YEAR05_NOTIONAL_AMT NUMBER (27,2),
YEAR06_NOTIONAL_AMT NUMBER (27,2),
YEAR07_NOTIONAL_AMT NUMBER (27,2),
YEAR08_NOTIONAL_AMT NUMBER (27,2),
YEAR09_NOTIONAL_AMT NUMBER (27,2),
YEAR10_NOTIONAL_AMT NUMBER (27,2),
GEN_RISK_PROVISION_BAL_AMT NUMBER (27,2),
ADJ_DESC VARCHAR2 (255),
CREATE_USER_CD VARCHAR2 (30) DEFAULT USER NOT NULL,
CREATE_DATETIME DATE DEFAULT SYSDATE NOT NULL,
UPDATE_USER_CD VARCHAR2 (30) DEFAULT USER NOT NULL,
UPDATE_DATETIME DATE DEFAULT SYSDATE NOT NULL,
CREDIT_LINE_NET_IND CHAR (1),
REGULATORY_NET_IND CHAR (1),
OVERDUE_AMT NUMBER (27,2),
OVERDUE_CCY_CD CHAR (3),
LOCAL_GL_CD VARCHAR2 (25),
BAL_SHEET_EXP_EUR_AMT NUMBER (27,2),
BRANCH_LOC_CD VARCHAR2 (15),
RESPONSIBLE_BRANCH_LOC_CD VARCHAR2 (15),
RESPONSIBLE_BRANCH_ID NUMBER (10),
RESPONSIBLE_BRANCH_CCDB_ID NUMBER (15),
TRADE_ENTITY_LOC_CD VARCHAR2 (15),
INTER_IND VARCHAR2 (1),
GLOBAL_GL_CD VARCHAR2 (30),
BS_COMPEN_CD VARCHAR2 (30),
INT_COMPEN_CD VARCHAR2 (30),
IMP_DT DATE,
INT_INCOME_THEOR_IMP_AMT NUMBER (27,2),
INT_INCOME_ACT_IMP_AMT NUMBER (27,2),
INT_INCOME_IMP_CCY_CD VARCHAR2 (3),
UNEARNED_INCOME_DIS_LOAN_AMT NUMBER (27,2),
UNEARNED_INCOME_CCY_CD VARCHAR2 (3),
ORIG_FEE_INCOME_AMT NUMBER (27,2),
ORIG_FEE_EXPENSE_AMT NUMBER (27,2),
OTHER_FEE_INCOME_AMT NUMBER (27,2),
OTHER_EXPENSES_AMT NUMBER (27,2),
OTHER_INCOME_EXP_CCY_CD VARCHAR2 (3),
BOOK_VAL_AMT NUMBER (27,2),
BOOK_VAL_CCY_CD VARCHAR2 (3),
THIRD_PARTY_SRC_CD VARCHAR2 (40),
THIRD_PARTY_CD VARCHAR2 (50),
THIRD_PARTY_CCDB_ID NUMBER (15),
ORIG_FEE_UNREALISED_AMT NUMBER (27,2),
EXTRACT_IND VARCHAR2 (1) DEFAULT 'N')
TABLESPACE CRES_SMALL_DATA
PCTFREE 10
PCTUSED 40
INITRANS 1
MAXTRANS 255
PARTITION BY RANGE (COB_DT)
(
PARTITION OCT2001 VALUES LESS THAN (TO_DATE(' 2001-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')) TABLESPACE CRES_LARGE_DATA
PCTFREE 40
PCTUSED 40
INITRANS 1
MAXTRANS 255
STORAGE (
INITIAL 4194304
NEXT 134217728
PCTINCREASE 0
MINEXTENTS 1
MAXEXTENTS 2147483645
),
PARTITION NOV2001 VALUES LESS THAN (TO_DATE(' 2001-12-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')) TABLESPACE CRES_LARGE_DATA
PCTFREE 40
PCTUSED 40
INITRANS 1
MAXTRANS 255
STORAGE (
INITIAL 134217728
NEXT 134217728
PCTINCREASE 0
MINEXTENTS 1
MAXEXTENTS 2147483645
),
PARTITION DEC2001 VALUES LESS THAN (TO_DATE(' 2002-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')) TABLESPACE CRES_LARGE_DATA
PCTFREE 40
PCTUSED 40
INITRANS 1
MAXTRANS 255
STORAGE (
INITIAL 134217728
NEXT 134217728
PCTINCREASE 0
MINEXTENTS 1
MAXEXTENTS 2147483645
)
);


Now when I query

select * from src_trn_det where cob_dt = to_date('31.12.01','DD.MM.YY')

I get the following explain plan:

SELECT STATEMENT Optimizer=CHOOSE (Cost=72202 Card=6566399 Bytes=3112473126)
PARTITION RANGE (SINGLE)
TABLE ACCESS (FULL) OF SRC_TRN_DET (Cost=72202 Card=6566399
Bytes=3112473126)

But when I query:

select * from src_trn_det where cob_dt = to_date('31.12.2001','DD.MM.YYYY')

I get

SELECT STATEMENT Optimizer=CHOOSE (Cost=71661 Card=6533165 Bytes=3090187045)
TABLE ACCESS (FULL) OF SRC_TRN_DET (Cost=71661 Card=6533165 Bytes=3090187045)

So my question is: Why do I get a different execution plan if I specify the year with 4 digits (Oracle even seems to do a full table scan in this case - no elimination of partitions seems to happen).

Even more strange is that if you query

select * from src_trn_det where cob_dt = to_date('31.12.2001','DD.MM.YYYY','FOO')

You get the first plan again (it does not seem to matter what you supply as third argument to to_date, it just has to be a non-empty string).

We have nls_language = AMERICAN, nls_territory = AMERICA, no other nls parameters are set.

Could you please explain what is happening here?
Many thanks.

and Tom said...

figured it out (sorry so long, I was traveling lots this week).

Ok, BOTH are doing partition elimination. The first is doing it at run time, the other at query compile time.

The first one -- according to the optimizer -- will eliminate a partition but until it actually evaluates the to_date - it doesn't know which one. Consider this first query to be like:

select * from src_trn_det where cob_dt = :bind_variable;

we know we can eliminate but not until we RUN the query do we know which one will be eliminated. The YY is the cause of this. Run this in 1999 and you get a different answer then if you run in 2000!

The second query -- the optimizer KNOWS exactly what partition it will eliminate. It matters not when the query is run -- the date is a constant.

Autotrace doesn't show us this (well, not CLEARLY). Here is how You can see this fact. I created this table:


ops$tkyte@ORA817DEV.US.ORACLE.COM> CREATE TABLE SRC_TRN_DET (
2 SEQ_NO NUMBER (10) NOT NULL,
3 CRES_SYS_ID NUMBER (3) NOT NULL,
4 COB_DT DATE NOT NULL,
5 EXTRACT_IND VARCHAR2 (1) DEFAULT 'N')
6 PARTITION BY RANGE (COB_DT)
7 ( PARTITION OCT2001 VALUES LESS THAN
8 (TO_DATE(' 2001-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
9 PARTITION NOV2001 VALUES LESS THAN
10 (TO_DATE(' 2001-12-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
11 PARTITION DEC2001 VALUES LESS THAN
12 (TO_DATE(' 2002-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'))
13 )
14 /

Table created.


And then taking the CARD from your full scans -- I though the table had 6533165
(but apparently that is the number of rows in a PARTITION of your table!). So I used dbms_stats:

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_stats.set_table_stats( user, 'SRC_TRN_DET', numrows => 6533165, numblks => 500000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_stats.set_table_stats( user, 'SRC_TRN_DET', 'OCT2001', numrows => 2188799, numblks => 167000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_stats.set_table_stats( user, 'SRC_TRN_DET', 'NOV2001', numrows => 2188799, numblks => 167000 );

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> exec dbms_stats.set_table_stats( user, 'SRC_TRN_DET', 'DEC2001', numrows => 2188799, numblks => 167000 );

PL/SQL procedure successfully completed.

Now, when I run your queries -- I see something similar to you:

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from src_trn_det where cob_dt = to_date('31.12.01','DD.MM.YY')
2 /

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4799 Card=65332 Bytes=2417284)
1 0 PARTITION RANGE (SINGLE)
2 1 TABLE ACCESS (FULL) OF 'SRC_TRN_DET' (Cost=4799 Card=65332 Bytes=2417284)



ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from src_trn_det where cob_dt = to_date('31.12.2001','DD.MM.YYYY')
2 /

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4808 Card=21888 Bytes=809856)
1 0 TABLE ACCESS (FULL) OF 'SRC_TRN_DET' (Cost=4808 Card=21888 Bytes=809856)

But the kicker is -- look at the CARD= in my example! The "worse" looking plan is "better".... Why? Well, its because the second plan is really doing a fixed partition elimination and has actually more info then the first query. We'll drop down to an old fashioned explain plan to see this:

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> explain plan set statement_id = 'DD_MM_YY' for
2 select * from src_trn_det where cob_dt = to_date('31.12.01','DD.MM.YY')
3 /

Explained.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @/export/home/ora817/rdbms/admin/utlxpls.sql

Plan Table
--------------------------------------------------------------------------------
| Operation | Name | Rows | Bytes| Cost | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT | | 65K| 2M| 4799 | | |
| PARTITION RANGE SINGLE | | | | | KEY | KEY |
| TABLE ACCESS FULL |SRC_TRN_D | 65K| 2M| 4799 | KEY | KEY |
--------------------------------------------------------------------------------

6 rows selected.

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from plan_table;

13 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> explain plan set statement_id = 'DD_MM_YYYY' for
2 select * from src_trn_det where cob_dt = to_date('31.12.2001','DD.MM.YYYY')
3 /

Explained.



Plan Table
--------------------------------------------------------------------------------
| Operation | Name | Rows | Bytes| Cost | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT | | 21K| 790K| 4808 | | |
| TABLE ACCESS FULL |SRC_TRN_D | 21K| 790K| 4808 | 3 | 3 |
--------------------------------------------------------------------------------

ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from plan_table;

2 rows deleted.




see the first query knows it'll eliminate partitions, just not which ones. The second one, with a much abbreviated plan, knows that only partition 3 will be accessed and sets it up accordingly.

So -- both plans are the "same" -- they will return different answers if run in different centuries.

Use YYYY !!!



Rating

  (25 ratings)

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

Comments

what is the difference between run time and query compile time

A reader, January 19, 2002 - 4:17 pm UTC

As subject

These technical words are killing me...

Very nice example, clear and simple!

Thank you

Tom Kyte
January 19, 2002 - 6:00 pm UTC

when you parse -- you compile the query. In this example, when we compiled (parsed)

select * from src_trn_det where cob_dt = to_date('31.12.01','DD.MM.YY')


the exact value of what to_date('31.12.01','DD.MM.YY') would be is indeterminate. If we run it in 1999, it'll be one value, if we run it in 2000, it'll be yet another (wait another 98 years and it'll be yet a third). Hence until we actually EXECUTE the query (actually RUN it), that value is "unbound" -- not known.

The value to_date('31.12.2001','DD.MM.YYYY') is not ambigous, it is very well known. At the time we parse (compile) the query, we KNOWN exactly what value that will be regardless of the date (guess in 8000 years I'll have to modify this answer...)

Thanks Tom

A reader, June 25, 2002 - 12:29 pm UTC

A good, clear example.
Can you tell me why you would use SYYYY in the to_date format string rather than YYYY? My own system uses SYYYY and examples in your book use YYYY. I'm not sure if there was a specific reason for the SYYYY. Does this do anything extra?
Thanks

Tom Kyte
June 25, 2002 - 1:05 pm UTC

They did it:

YYYY
SYYYY
4-digit year. "S" prefixes BC dates with "-".


They were just being as explicit as possible.





explain plan on range-partitioned table

Neena, June 26, 2002 - 3:57 am UTC

sql> @c:\oracle\app\oracle\product\8.1.7\rdbms\admin\utlxplan.sql

sql>delete from plan_table;

sql> explain plan set statement_id='DD' for
2 select * from emp;

Explained.


sql> @c:\oracle\app\oracle\product\8.1.7\rdbms\admin\utlxpls.sql

I am not getting any output .

Can u please tell me , what additional I had to do



Tom Kyte
June 26, 2002 - 8:27 am UTC

show us a true cut and paste


ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from plan_table;

158 rows deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM> explain plan set statement_id = 'DD' for     
  2  select * from emp;

Explained.

ops$tkyte@ORA817DEV.US.ORACLE.COM> @/export/home/ora817/rdbms/admin/utlxpls.sql

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |       |      |        |       |       |
|  TABLE ACCESS FULL        |EMP       |       |      |        |       |       |
--------------------------------------------------------------------------------

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


works for me and I've never seen it "not work" 

why different execution plan under accounts in same database

June, March 19, 2003 - 2:29 pm UTC

Hi Tom,

we had one partition table in one production database, with 7 partitions, each with about 25 mm records. there is one thing we couldn't explain: after analyze on partitions, run explain plan under 3 accounts (one schema owner, one Oracle account, one application account, the other two use the public synonym) with same sql statment (query data from one partition), the execution path is different: from application account, it is used 'nest loop', while the other two uses 'hash loop' which results much longer query time (28 minutes vs 2 minutes).

Do you have any quick answer to this? it is on production database. at least, we should expect same explain plan, shouldn't we?

Tom Kyte
March 19, 2003 - 3:57 pm UTC

lets see the queries and plans -- and also make sure there aren't any hidden "login triggers" or other such magic that change the environments (optimizer_*, *_size, etc settings)

push comes to shove, we'll use a 10053 trace to see whats up.

re: why different execution plan under accounts in same database

June, March 19, 2003 - 5:13 pm UTC

Thanks Tom. what specific setting would you like to see? as it is in production, I can't see them all but have to request it. but what wonders me is that database setting is there which should not change magically, when the explain plan came from 3 accounts in same database.

I will try to update with more detail information. Thanks!

by the way, what is the '10053 trace '? isn't it regular trace utility?

Tom Kyte
March 19, 2003 - 7:40 pm UTC

I'd like you to just verify that the settings are in fact the same (no login triggers, no procedures changing things behind our backs)

search this site for 10053 -- if you think everything is constant we'll have to use that to dump the optimizers thought process and see whats different.

PARTITION RANGE (ALL)?

Sikandar Hayat, June 28, 2003 - 12:25 am UTC

Hi,
I have created a table t with the following statement and then created primary key, global index, local index on idno but plan always shows me "PARTITION RANGE (ALL)" and it seems that it is not using the index. I have also created an index on the second name column which is not partition key and when I query on this field it shows index usage. I want to know that why it is not using index and how to create global primary key index?

create table t(idno varchar2(2), name varchar2(10)
)
partition by range (idno)
(partition p1 values less than (11),
partition p2 values less than (21),
partition p3 values less than (31)
)
/

insert into t values(1, 'One');
insert into t values(2, 'Two');

insert into t values(11, 'Eleven');
insert into t values(12, 'Tweleve');

insert into t values(21, 'Twenty One');
insert into t values(22, 'Twenty Two');

scott > select * from t;

ID NAME
-- ----------
1 One
2 Two
11 Eleven
12 Tweleve
21 Twenty One
22 Twenty Two

6 rows selected.

scott > set autotrace on
scott > select * from t where idno = 12;

ID NAME
-- ----------
12 Tweleve


Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=3 Bytes=30)
1 0 PARTITION RANGE (ALL)
2 1 TABLE ACCESS (FULL) OF 'T' (Cost=1 Card=3 Bytes=30)




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

scott > select * from t where name = 'Two';

ID NAME
-- ----------
2 Two


Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=3 Bytes=30)
1 0 TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'T' (Cost=1 Card=3
2 1 INDEX (RANGE SCAN) OF 'T_GLOBAL2' (NON-UNIQUE) (Cost=1 C




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

scott >

I am reading docs,
</code> http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a76956/partiti.htm#9324 <code>

Tom Kyte
June 28, 2003 - 9:17 am UTC

try comparing numbers to numbers,
strings to strings,
dates to dates

and your life will be a much happier one.

select * from t where idno = 12;

is the same as

select * from t where TO_NUMBER(idno) = 12;


hence you are applying a function to the partition key so it cannot do partition elimination.

try

select * from t where idno = '12';

Primary Key should be Global?

Sikandar Hayat, June 29, 2003 - 4:07 am UTC

Thanks TOM I had not noticed it that I defined the field with varchar. Now it is ok.
I have a table and I have to split it into 12 partitions. There will be a primary key on a filed which is not partition key. I need partition to truncate the data after 3 months so after 3 months I will delete the data of one partition. Now let me know which type of index I should use. Only 2 or 3 users are using this table and generating data with batch processing. Let me know if I go with Global then how to change the primary key to Global?

Tom Kyte
June 29, 2003 - 9:24 am UTC

the primary key will have to be a globally partioned index since it is UNIQUE and uniqueness can only be enforced in locally partitioned indexes that include the partition key.

you will also want it to be global so as to permit index partition elimination when you do a "select * from t where pk = :x" type of query. Otherwise every keyed lookup would involve 12 index range scans (eg: your IO would go up more then an order of magnitude)

you will be rebuilding this index after a truncate, or if using 9i, you can perform many partition level operations while maintaining the global index (don't need a rebuild, but the partition operation will generate redo to protect the index structure)

Primary key is Global by default?

Sikandar Hayat, June 30, 2003 - 5:39 am UTC

I have added primary key on the none partitioned key field. I want to know that,

1> How can I know from the plan that it has not scanned all partitions?
2> Is primary key is Global be default?

scott > alter table t add constraint t_pk primary key(name);

Table altered.

scott > select * from t where name = 'One';

IDNO NAME
------------ ----------
1 One


Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=3 Bytes=60)
1 0 TABLE ACCESS (BY GLOBAL INDEX ROWID) OF 'T' (Cost=1 Card=3
2 1 INDEX (RANGE SCAN) OF 'T_PK' (UNIQUE) (Cost=1 Card=3)




Statistics
----------------------------------------------------------
159 recursive calls
3 db block gets
34 consistent gets
0 physical reads
0 redo size
204 bytes sent via SQL*Net to client
288 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
6 sorts (memory)
0 sorts (disk)
1 rows processed

Tom Kyte
June 30, 2003 - 7:14 am UTC

1) use explain plan and @?/rdbms/admin/utlxpls.sql to query the plan.  That output will show the pstart/pstop

but, in your case, you have but one index partition.  so it wont be very interesting.  As we discussed right above -- in order to be unique and local, it would have to be part of the partition key, name is not, hence it will be a global index and by default that will be a single partition.

ops$tkyte@ORA817DEV> alter table t add constraint t_pk primary key(name);

Table altered.

ops$tkyte@ORA817DEV> create index t_idx on t(idno);

Index created.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> delete from plan_table;

3 rows deleted.

ops$tkyte@ORA817DEV> explain plan for
  2  select * from t where name = 'One';

Explained.

ops$tkyte@ORA817DEV> set echo off
@?/rdbms/admin/utlxpls.sql

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     3 |   30 |      1 |       |       |
|  TABLE ACCESS BY GLOBAL IN|T         |     3 |   30 |      1 | ROWID | ROW L |
|   INDEX RANGE SCAN        |T_PK      |     3 |      |      1 |       |       |
--------------------------------------------------------------------------------

6 rows selected.

ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> delete from plan_table;

3 rows deleted.

ops$tkyte@ORA817DEV> explain plan for
  2  select * from t where idno = '11';

Explained.

ops$tkyte@ORA817DEV> set echo off
@?/rdbms/admin/utlxpls.sql

Plan Table
--------------------------------------------------------------------------------
| Operation                 |  Name    |  Rows | Bytes|  Cost  | Pstart| Pstop |
--------------------------------------------------------------------------------
| SELECT STATEMENT          |          |     1 |   10 |      1 |       |       |
|  TABLE ACCESS BY GLOBAL IN|T         |     1 |   10 |      1 |     2 |     2 |
|   INDEX RANGE SCAN        |T_IDX     |     1 |      |      1 |       |       |
--------------------------------------------------------------------------------

6 rows selected.


<b>see the pstart/pstop columns...</b>


 

partition varchar2 columns

A reader, December 07, 2003 - 3:28 pm UTC

Hi

We have a few quite big tables (around 50 millions rows) in our order entry table. We want to partition these tables by the ID, unfortunately these IDs are varchar2. We have ID ranging from 0 to 650000000. I try to partition and get errors

ORA-14037: partition bound of partition "xxx" is too high

This is because ordering in varchar2 is not same as number

How can we do this?

If I partition less than '10000000' then all sort of data goes there

data less than 10000000 and also bigger such as 12000000...

Can we do

less than to_number(ID) ?

Tom Kyte
December 07, 2003 - 7:13 pm UTC

how do you store "id" -- is zero stored at 000000000000000000 or 0

you chose the wrongenst datatype on the planet to store numbers, unless you made them "sortable" (using fixed width fields), its going to be a hassle.

utlxplan throws exception

BK, December 08, 2003 - 3:47 pm UTC

utlxplan script copied from the admin directory. any ideas why it's throwing an exception?

APPS>explain plan set statement_id = 'XLIKE' for
2 select ps.party_site_id from hz_party_sites ps, hz_parties p
3 where p.party_id = ps.PARTY_ID
4 and p.ORIG_SYSTEM_REFERENCE not like 'PER%';

Explained.

APPS>@c:\bin\utlxpls

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
An uncaught error happened in fetching the records : ORA-00904: "OTHER_TAG": inv
alid identifier

ORA-00904: "OTHER_TAG": invalid identifier

APPS>

Tom Kyte
December 09, 2003 - 5:52 am UTC

you give no version info whatsoever?

but looks like 9ir2 -- so, can you just

select * from table( dbms_xplan.display( 'PLAN_TABLE', 'XLIKE' ) )

Still the same error

BK, December 09, 2003 - 10:48 am UTC

What am I missing ?

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production
With the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

APPS>begin
2 sa_session.set_access_profile('ORGS','AA_USER');
3 end;
4 /

PL/SQL procedure successfully completed.

APPS>explain plan set statement_id = 'XLIKE' for
2 select count(*),
3 count( case when orig_system_reference < 'A%' then 1 end )
4 from hz_parties;

Explained.

APPS>select * from table( dbms_xplan.display( 'PLAN_TABLE', 'XLIKE' ) ) ;

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
An uncaught error happened in fetching the records : ORA-00904: "OTHER_TAG": inv
alid identifier

ORA-00904: "OTHER_TAG": invalid identifier

APPS>

Tom Kyte
December 09, 2003 - 2:21 pm UTC

describe your plan_table -- is it the 9i plan table?

Plan Table

BK, December 09, 2003 - 6:06 pm UTC

Here it is. Thanks

APPS>desc plan_table
Name Null? Type
---------- -------- ----------------------------
STATEMENT_ID VARCHAR2(30)
TIMESTAMP DATE
REMARKS VARCHAR2(80)
OPERATION VARCHAR2(30)
OPTIONS VARCHAR2(30)
OBJECT_NODE VARCHAR2(128)
OBJECT_OWNER VARCHAR2(30)
OBJECT_NAME VARCHAR2(30)
OBJECT_INSTANCE NUMBER(38)
OBJECT_TYPE VARCHAR2(30)
OPTIMIZER VARCHAR2(255)
SEARCH_COLUMNS NUMBER(38)
ID NUMBER(38)
PARENT_ID NUMBER(38)
POSITION NUMBER(38)
OTHER LONG

APPS>



Tom Kyte
December 10, 2003 - 2:50 am UTC

that would not be the 9i plan_table.

rerun utlxplan.sql to get the right one created, that one is really old.  OTHER_TAG is a column in that table these days.

ops$tkyte@ORA9IR2> desc plan_table
 Name                          Null?    Type
 ----------------------------- -------- --------------------
 STATEMENT_ID                           VARCHAR2(30)
 TIMESTAMP                              DATE
 REMARKS                                VARCHAR2(80)
 OPERATION                              VARCHAR2(30)
 OPTIONS                                VARCHAR2(255)
 OBJECT_NODE                            VARCHAR2(128)
 OBJECT_OWNER                           VARCHAR2(30)
 OBJECT_NAME                            VARCHAR2(30)
 OBJECT_INSTANCE                        NUMBER(38)
 OBJECT_TYPE                            VARCHAR2(30)
 OPTIMIZER                              VARCHAR2(255)
 SEARCH_COLUMNS                         NUMBER
 ID                                     NUMBER(38)
 PARENT_ID                              NUMBER(38)
 POSITION                               NUMBER(38)
 COST                                   NUMBER(38)
 CARDINALITY                            NUMBER(38)
 BYTES                                  NUMBER(38)
 OTHER_TAG                              VARCHAR2(255)
 PARTITION_START                        VARCHAR2(255)
 PARTITION_STOP                         VARCHAR2(255)
 PARTITION_ID                           NUMBER(38)
 OTHER                                  LONG
 DISTRIBUTION                           VARCHAR2(30)
 CPU_COST                               NUMBER(38)
 IO_COST                                NUMBER(38)
 TEMP_SPACE                             NUMBER(38)
 ACCESS_PREDICATES                      VARCHAR2(4000)
 FILTER_PREDICATES                      VARCHAR2(4000)
 
 

9i Plan Table is different and that was the problem

BK, December 10, 2003 - 6:28 pm UTC

Great ! Thanks a lot. It works like a charm.

We have a 9i data base, it's not a fresh install but patched up from 8.1.7, some body must have used an old script to create the plan table. Now it works and I'm sure I'm going to make use of it a lot.

Plan with and without binds

phil, December 30, 2003 - 11:45 am UTC

HI Tom,

I have a date range partitioned table. When the application runs a specific query against this table, despite the key of the partition being in the query, the plan indicates a partition range all. This query is being run many times, by many different users.

SELECT COUNT(*)

FROM

(SELECT 1 FROM LogicalTransaction Trans, UserAccount UA WHERE UA.ACCOUNT_FK=

Trans.ACCOUNT_FK AND UA.RTNUSER_FK=:1 AND UA.REVOKED=0 AND

Trans.ISOCURRENCY_FK IN (:2) AND (Trans.VALUE_DATE_TIME >= :3 AND

Trans.VALUE_DATE_TIME < :4) AND Trans.TRANSACTIONSTATUSTYPE_FK IN (:5,:6,:7,

:8,:9,:10,:11,:12) AND rownum < :13) p





Rows Row Source Operation

------- ---------------------------------------------------

1 SORT AGGREGATE

1650 VIEW

1650 COUNT STOPKEY

1650 TABLE ACCESS BY LOCAL INDEX ROWID LOGICALTRANSACTION

18277 NESTED LOOPS

451 INDEX RANGE SCAN USERACCOUNT_IX3 (object id 32621)

17825 PARTITION RANGE ALL PARTITION: 1 6

17825 INDEX RANGE SCAN LTXN_AC_VDT PARTITION: 1 6 (object id 32997)

however when I hard code values the plan indicates partition elimination

SELECT COUNT(*)
FROM
(SELECT 1 FROM LogicalTransaction Trans, UserAccount UA WHERE UA.ACCOUNT_FK=
Trans.ACCOUNT_FK AND UA.RTNUSER_FK=3244 AND UA.REVOKED=0 AND
Trans.ISOCURRENCY_FK IN (4900) AND (Trans.VALUE_DATE_TIME >= '09/Dec/2003'
AND Trans.VALUE_DATE_TIME < '10/Dec/2003' )
AND Trans.TRANSACTIONSTATUSTYPE_FK IN (100,200,300,400,500,600,900,1000)
AND rownum < 5000) p

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

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

Rows Row Source Operation
------- ---------------------------------------------------
1 SORT AGGREGATE
3300 VIEW
3300 COUNT STOPKEY
3300 NESTED LOOPS
11000 PARTITION RANGE ITERATOR PARTITION: KEY KEY
11000 TABLE ACCESS BY LOCAL INDEX ROWID LOGICALTRANSACTION PARTITION: KEY KEY
143459 INDEX RANGE SCAN LTXN_VDT_LOCAL PARTITION: KEY KEY (object id 32712)
3300 INDEX RANGE SCAN USERACCOUNT_IX3 (object id 32621)

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

Can the database not eliminate partitions when using binds, or am I not looking at the real plan?

thanks


Tom Kyte
December 30, 2003 - 12:16 pm UTC

the plans are totally different -- using totally different driving tables.

the first:

18277 NESTED LOOPS
451 INDEX RANGE SCAN USERACCOUNT_IX3 (object id 32621)
17825 PARTITION RANGE ALL PARTITION: 1 6
17825 INDEX RANGE SCAN LTXN_AC_VDT PARTITION: 1 6 (object id 32997)

is doing the index scan on useraccount_ix3 first and then doing 6 index probes into the partitioned index ltxn_ac_vdt. that index is probably (see, very little data so I'm guessing on lots of things here) not partitioned by the join key -- but by date so we inspect all 6 partions and then access the table. so, given the access plan it came up with (the best overall general plan for this generic query where the inputs could be anything), it cannot do the index partition elimination.

the other plan uses the partitioned table as the driving table and hence does it very differently.


it is not that binds precluded elimitation, it is that the plan used with binds didn't have an opportunity to partition prune.


Anything I can do ?

phil, December 31, 2003 - 5:34 am UTC

Thanks Tom,

is there anything I can do in this example to help pruning - dare I say it ,should I be binding, on a heavliy queried and partitioned table ??

the index LTXN_VDT_LOCAL is VALUE_DATE_TIME, ACCOUNT_FK local and the index LTXN_AC_VDT is ACCOUNT_FK, VALUE_DATE_TIME, TRANSACTIONSTATUSTYPE_FK local.





Tom Kyte
December 31, 2003 - 9:48 am UTC

question for you -- we see the tkprof indicating the runtime performance of the non-bound query.

how's about for the query with binds (using the same values you did for the non-bind query) -- the rowsource numbers look pretty good

binds

Phil, January 02, 2004 - 9:01 am UTC

Hi Tom,

I did some futher investigation and found that if I run the sql statement with binds through a simple plsql procedure and pass in the values and then compare the plan with the query with hard coded values they are identical !

The statement plan below ( and the one I was comparing above ) is created from the sql and values that are passed by the web application. The values passed to the below query are "supposed" to be the same as I hard coded.....

(I have renamed a table below and appended _P it is the same as above.)

SELECT COUNT(*)
FROM
(SELECT 1 FROM LogicalTransaction Trans, UserAccount UA WHERE UA.ACCOUNT_FK=
Trans.ACCOUNT_FK AND UA.RTNUSER_FK=:1 AND UA.REVOKED=0 AND
Trans.ISOCURRENCY_FK IN (:2) AND (Trans.VALUE_DATE_TIME >= :3 AND
Trans.VALUE_DATE_TIME < :4) AND Trans.TRANSACTIONSTATUSTYPE_FK IN (:5,:6,:7,
:8,:9,:10,:11,:12) AND rownum < :13) p


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 1 2.18 2.13 4225 13889 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3 2.18 2.14 4225 13889 0 1

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

Rows Row Source Operation
------- ---------------------------------------------------
1 SORT AGGREGATE
3300 VIEW
3300 COUNT STOPKEY
3300 TABLE ACCESS BY LOCAL INDEX ROWID LOGICALTRANSACTION_P
43453 NESTED LOOPS
451 INDEX RANGE SCAN USERACCOUNT_IX3 (object id 32621)
43001 PARTITION RANGE ALL PARTITION: 1 6
43001 INDEX RANGE SCAN LTXN_AC_VDT PARTITION: 1 6 (object id 33014)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 6 0.00 0.00
SQL*Net message from client 6 0.16 0.17
db file sequential read 4225 0.00 0.17
********************************************************************************


The one here compared to the one in my previous post with hard coded values ( which I also ran in plsql with binds ) are clearly different, although I thought the values were the same.

I have looked at the raw trace file to discover the actual bind values, this is what I found for this statement :

PARSING IN CURSOR #1 len=336 dep=0 uid=68 oct=3 lid=68 tim=7363261661 hv=1233797181 ad='be7c13f0'
SELECT COUNT(*) FROM (SELECT 1 FROM LogicalTransaction Trans, UserAccount UA WHERE UA.ACCOUNT_FK=Trans.ACCOUNT_FK AND UA.RTNUSER_FK=:1 AND UA.REVOKED=0 AND Trans.ISOCURRENCY_FK IN (:2) AND (Trans.VALUE_DATE_TIME >= :3 AND Trans.VALUE_DATE_TIME < :4) AND Trans.TRANSACTIONSTATUSTYPE_FK IN (:5,:6,:7,:8,:9,:10,:11,:12) AND rownum < :13) p
END OF STMT
PARSE #1:c=0,e=2244,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=0,tim=7363261651
BINDS #1:
bind 0: dty=2 mxl=22(22) mal=00 scl=00 pre=00 oacflg=01 oacfl2=0 size=24 offset=0
bfp=ffffffff7b084fa0 bln=22 avl=03 flg=05
value=3244
bind 1: dty=1 mxl=4000(4000) mal=00 scl=00 pre=00 oacflg=01 oacfl2=0 size=4000 offset=0
bfp=ffffffff7b070480 bln=4000 avl=04 flg=05
value="4900"
bind 2: dty=180 mxl=11(11) mal=00 scl=00 pre=00 oacflg=01 oacfl2=0 size=32 offset=0
bfp=ffffffff7b070448 bln=11 avl=07 flg=05
value=
Dump of memory from 0xFFFFFFFF7B070448 to 0xFFFFFFFF7B07044F
FFFFFFFF7B070440 78670C09 01010100 [xg......]
bind 3: dty=180 mxl=11(11) mal=00 scl=00 pre=00 oacflg=01 oacfl2=0 size=0 offset=16
bfp=ffffffff7b070458 bln=11 avl=07 flg=01
value=
Dump of memory from 0xFFFFFFFF7B070458 to 0xFFFFFFFF7B07045F
FFFFFFFF7B070450 78670C09 183C0100 [xg...<..]
......
......

I noticed 2 things, one it is passing bind 1 as a string and it should be a number (app issue) also I would expect in bind 2 and bind 3 to see the date value that is being passed, I do when I run from plsqpl ...


bind 2: dty=12 mxl=07(07) mal=00 scl=00 pre=00 oacflg=03 oacfl2=1 size=0 offset=48
bfp=ffffffff7b066680 bln=07 avl=07 flg=01
value="12/9/2003 0:0:0"

so my question is what is the dump from memory for bind 2 and bind 3 and also what is type 180 ? Is it this that is not assisting in my partition elimination

thanks
phil

Tom Kyte
January 02, 2004 - 9:43 am UTC

from the data dictionary view user_tab_cols:

180, 'TIMESTAMP(' ||c.scale|| ')',


180 is the timestamp datatype (must be a java piece of code there, at least they are using binds)

The EXPLAIN plan for a query with binds can be very misleading as EP doesn't understand what datatype are/should be bound to the :bv's.

You need to use to_number, to_date and the like to get the accurate plan (i only rely on tkprof/sql_trace outputs like this)

Phil, January 02, 2004 - 9:50 am UTC

Many thanks

phil, January 02, 2004 - 10:07 am UTC

sorry .. dumb question

could not find such an entry in the view user_tabs_cols, where in this view are the datatypes? When I select from here i do not see anything that is related to 180 ?

thanks


Tom Kyte
January 02, 2004 - 10:12 am UTC

it is not an "entry", it is the view definition itself.

ops$tkyte@ORA9IR2> set long 5000
ops$tkyte@ORA9IR2> select text from all_views where view_name = 'USER_TAB_COLS'; 
TEXT
--------------------------------------------------------------------------------select o.name,
       c.name,
       decode(c.type#, 1, decode(c.charsetform, 2, 'NVARCHAR2', 'VARCHAR2'),
                       2, decode(c.scale, null,
                                 decode(c.precision#, null, 'NUMBER', 'FLOAT'),
                                 'NUMBER'),
                       8, 'LONG',
                       9, decode(c.charsetform, 2, 'NCHAR VARYING', 'VARCHAR'),
                       12, 'DATE', 23, 'RAW', 24, 'LONG RAW',
                       58, nvl2(ac.synobj#, (select o.name from obj$ o
                                where o.obj#=ac.synobj#), ot.name),
                       69, 'ROWID',
                       96, decode(c.charsetform, 2, 'NCHAR', 'CHAR'),
                       105, 'MLSLABEL',
                       106, 'MLSLABEL',
                       111, nvl2(ac.synobj#, (select o.name from obj$ o
                                 where o.obj#=ac.synobj#), ot.name),
                       112, decode(c.charsetform, 2, 'NCLOB', 'CLOB'),
                       113, 'BLOB', 114, 'BFILE', 115, 'CFILE',
                       121, nvl2(ac.synobj#, (select o.name from obj$ o
                                 where o.obj#=ac.synobj#), ot.name),
                       122, nvl2(ac.synobj#, (select o.name from obj$ o
                                 where o.obj#=ac.synobj#), ot.name),
                       123, nvl2(ac.synobj#, (select o.name from obj$ o
                                 where o.obj#=ac.synobj#), ot.name),
                       178, 'TIME(' ||c.scale|| ')',
                       179, 'TIME(' ||c.scale|| ')' || ' WITH TIME ZONE',<b>
                       180, 'TIMESTAMP(' ||c.scale|| ')',</b>
                       181, 'TIMESTAMP(' ||c.scale|| ')' || ' WITH TIME ZONE',
                       231, 'TIMESTAMP(' ||c.scale|| ')' || ' WITH LOCAL TIME ZONE',
                       182, 'INTERVAL YEAR(' ||c.precision#||') TO MONTH',
                       183, 'INTERVAL DAY(' ||c.precision#||') TO SECOND(' ||
                             c.scale || ')',
                       208, 'UROWID',
                       'UNDEFINED'),
 

partition plan

phil, January 09, 2004 - 8:53 am UTC

Can you help me understand this plan. I do no understand why Oracle might use 2 indexes from the same table to get the information...

could explain how this plan is working..

LTXN_VDT_ISO_IX3 & LOGICALTRANSACTION are local indexes on the logicaltransaction table.

Any thoughts on optimising?


SELECT COUNT(*)
FROM
(SELECT 1 FROM LogicalTransaction Trans, UserAccount UA WHERE UA.ACCOUNT_FK=
Trans.ACCOUNT_FK AND UA.RTNUSER_FK= :b15 AND UA.REVOKED=0 AND
Trans.ISOCURRENCY_FK IN (:b14,:b13,:b12) AND (Trans.VALUE_DATE_TIME >= :b11 AND
Trans.VALUE_DATE_TIME < :b10 ) AND Trans.TRANSACTIONSTATUSTYPE_FK IN (:b9,:b8,
:b7,:b6,:b5,:b4,:b3,:b2) AND rownum < :b1) p

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.01 0.00 0 0 0 0
Fetch 1 2.79 2.72 0 93131 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3 2.80 2.73 0 93131 0 1

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

Rows Row Source Operation
------- ---------------------------------------------------
1 SORT AGGREGATE
8996 VIEW
8996 COUNT STOPKEY
8996 NESTED LOOPS
44784 VIEW
44784 HASH JOIN
473899 PARTITION RANGE ITERATOR PARTITION: KEY KEY
473899 INDEX RANGE SCAN LOGICALTRANSACTION_IX3 PARTITION: KEY KEY (object id 32705)
71500 PARTITION RANGE ITERATOR PARTITION: KEY KEY
71500 INLIST ITERATOR
71500 INDEX RANGE SCAN LTXN_VDT_ISO_IX3 PARTITION: KEY KEY (object id 33081)
8996 INDEX RANGE SCAN USERACCOUNT_IX3 (object id 32621)

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

Tom Kyte
January 09, 2004 - 9:13 am UTC

without KNOWING what the indexes are -- it is really hard "to say".

one would need to see the tables and the indexes (as SMALL as possible -- no need for the entire partitioning clauses, tablespaces and so on -- just need to see columns, partition keys, indexes)

as requested

phil, January 09, 2004 - 9:29 am UTC

the index LTXN_VDT_ISO_IX3 is one I am trying/experimenting with for performance, I was just suprised and well basically did not fully understand the reasoning behind the above plan.


I hope this is sufficient.


CREATE TABLE LOGICALTRANSACTION (
PK NUMBER(15) NOT NULL,
VALUE_DATE_TIME DATE NOT NULL,
ACCOUNT_FK NUMBER(15) NOT NULL,
ISOCURRENCY_FK NUMBER(5) NOT NULL,
SERVICER_ORG_FK NUMBER(15) NOT NULL,
TRANSACTIONSTATUSTYPE_FK NUMBER(4) NOT NULL,
TRANSACTIONTYPE_FK NUMBER(15) NOT NULL,
LASTINST_HUB_ARRIVAL_DATE_TIME DATE NOT NULL,
ENTRY_DATE_TIME DATE NOT NULL,
ORIGINAL_AMOUNT NUMBER(28,4) NOT NULL,
AMOUNT NUMBER(28,4) NOT NULL,
PREV_TRANSACTIONSTATUSTYPE_FK NUMBER(15),
OWNER_ORG_FK NUMBER(15),
ACCOUNT_OWNER_REF VARCHAR2(100),
TRANSACTION_REFERENCE VARCHAR2(100),
TRANSACTION_STATUS_DATE_TIME DATE,
STATEMENT_FK NUMBER(15),
STATEMENT_ORDER NUMBER(8),
STATEMENT_SEQ NUMBER(5),
IS_UNMATCHABLE NUMBER(1),
BENEFICIARY VARCHAR2(160),
ORIGINATOR VARCHAR2(160),
RELATED_REF VARCHAR2(100),
SERVICING_INSTITUTION_REF VARCHAR2(100),
SUPPLEMENTARY_INFO VARCHAR2(55),
INFORMATION_TO_ACCOUNT_OWNER VARCHAR2(420),
CONSTRAINT LOGICALTRANSACTION_PK PRIMARY KEY (PK,VALUE_DATE_TIME) USING INDEX TABLESPACE &&indx_ltxn01 STORAGE (PCTINCREASE 0)
)
PARTITION BY RANGE (VALUE_DATE_TIME)
(
PARTITION logicaltransaction_0311 VALUES LESS THAN (to_date('01-DEC-2003','dd-mon-yyyy')),
PARTITION logicaltransaction_0312 VALUES LESS THAN (to_date('01-JAN-2004','dd-mon-yyyy')),
PARTITION logicaltransaction_0401 VALUES LESS THAN (to_date('01-FEB-2004','dd-mon-yyyy')),
PARTITION logicaltransaction_0402 VALUES LESS THAN (to_date('01-MAR-2004','dd-mon-yyyy')),
PARTITION logicaltransaction_0403 VALUES LESS THAN (to_date('01-APR-2004','dd-mon-yyyy')),
PARTITION logicaltransaction_0404 VALUES LESS THAN (to_date('01-MAY-2004','dd-mon-yyyy'))
)
TABLESPACE &&data_ltxn01 PCTFREE 10 PCTUSED 40 STORAGE (PCTINCREASE 0);


CREATE INDEX LOGICALTRANSACTION_IX3 ON
LOGICALTRANSACTION_P(VALUE_DATE_TIME, ACCOUNT_FK, TRANSACTIONSTATUSTYPE_FK) LOCAL;


CREATE INDEX LTXN_VDT_ISO_IX3 ON
LOGICALTRANSACTION_P(ISOCURRENCY_FK, VALUE_DATE_TIME, ACCOUNT_FK) LOCAL;

regards
phil

Tom Kyte
January 09, 2004 - 9:44 am UTC

Ok, this plan starts by taking the where clause:


WHERE UA.ACCOUNT_FK= Trans.ACCOUNT_FK
AND UA.RTNUSER_FK= :b15
AND UA.REVOKED=0
AND Trans.ISOCURRENCY_FK IN (:b14,:b13,:b12)
AND (Trans.VALUE_DATE_TIME >= :b11 AND Trans.VALUE_DATE_TIME < :b10 )
AND Trans.TRANSACTIONSTATUSTYPE_FK IN (:b9,:b8,:b7,:b6,:b5,:b4,:b3,:b2)
AND rownum < :b1

and breaking it into (conceptually)


WHERE UA.ACCOUNT_FK= Trans.ACCOUNT_FK
AND UA.RTNUSER_FK= :b15
AND UA.REVOKED=0

AND Trans.ISOCURRENCY_FK IN (:b14,:b13,:b12)
AND (Trans.VALUE_DATE_TIME >= :b11 AND Trans.VALUE_DATE_TIME < :b10 )

AND Trans.TRANSACTIONSTATUSTYPE_FK IN (:b9,:b8,:b7,:b6,:b5,:b4,:b3,:b2)
AND (Trans.VALUE_DATE_TIME >= :b11 AND Trans.VALUE_DATE_TIME < :b10 )

AND rownum < :b1


Now, the predicate on isocurrency_fk/value_date_time can use LTXN_VDT_ISO_IX3 nicely and the predicate on transactionstatustype_fk/value_date_time can use logicaltransaction (range scan on the value_date_time and just check the trans status type fk column as it goes along in that index)


So, that is what it did -- developing two "smallish" result sets. It then "hash joined" these two subsets together (that AND's them) and then took this sub result and nexted loops joined it to UA.


With the rownum contraint in there, I would suggest "/*+ FIRST_ROWS */" be added to the inline view. Seems you want first rows first in a query like that.

phil, January 09, 2004 - 10:28 am UTC

thanks for that.

if I drop the index logicaltransaction_ix3 run the query again the plan is as follows :

SELECT COUNT(*)
FROM
(SELECT 1 FROM LogicalTransaction Trans, UserAccount UA WHERE UA.ACCOUNT_FK=
Trans.ACCOUNT_FK AND UA.RTNUSER_FK= :b15 AND UA.REVOKED=0 AND
Trans.ISOCURRENCY_FK IN (:b14,:b13,:b12) AND (Trans.VALUE_DATE_TIME >= :b11 AND
Trans.VALUE_DATE_TIME < :b10 ) AND Trans.TRANSACTIONSTATUSTYPE_FK IN (:b9,:b8,
:b7,:b6,:b5,:b4,:b3,:b2) AND rownum < :b1) p

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.01 0.00 0 0 0 0
Fetch 1 0.86 0.83 0 96108 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3 0.87 0.84 0 96108 0 1

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

Rows Row Source Operation
------- ---------------------------------------------------
1 SORT AGGREGATE
8996 VIEW
8996 COUNT STOPKEY
8996 NESTED LOOPS
44784 PARTITION RANGE ITERATOR PARTITION: KEY KEY
44784 INLIST ITERATOR
44784 TABLE ACCESS BY LOCAL INDEX ROWID LOGICALTRANSACTION_P PARTITION: KEY KEY
58000 INDEX RANGE SCAN LTXN_VDT_ISO_IX3 PARTITION: KEY KEY (object id 33081)
8996 INDEX RANGE SCAN USERACCOUNT_IX3 (object id 32621)

this is quite obviously far more efficient than the previous. So why might oracle not choose this plan ( how long is a pice of string?) in the first place ? Is this just a case of having too many of the "wrong" indexes available to oracle. I guess i sort of assumed that the CBO would be able to take this into account. An index strategy and lots of testing/benchmarking is obviously what is needed


regards
phil


Tom Kyte
January 09, 2004 - 1:45 pm UTC

hard to say without an autotrace traceonly explain output (or explain plan PLUS dbms_xplan.display output)

RE : PSTART and PSTOP

A reader, August 25, 2005 - 12:12 pm UTC

Tom,

Could you please explain the difference between the following :

KEY in both PSTART and PSTOP
ROWID and ROW L in PSTART and PSTOP

When will I see KEY in PSTART and PSTOP and ROWID and ROW L
in the explain plan.

Also my understanding on PSTART and PSTOP is as follows :

1. PSTART = PSTOP and PSTART = Number => implies that partition elimination is happening at compile time and partition statistics will be used.
2. PSTART = PSTOP and PSTART = KEY => implies that partition elimination is happening at runtime and table statistics will be used
3. PSTART != PSTOP and PSTART = Number => implies that partition elimination is not happening and table statistics will be used.

Please confirm if my understanding on the above is correct.

Also, please clarify the following and explain when we can see them in the plan and what statistics will be used in each case.
1. PSTART != PSTOP and PSTART = KEY
2. PSTART != PSTOP and PSTART = ROWID and PSTOP = ROW L

I tried to understand from Oracle documentation but getting more confused. Your help is highly appreciated. If possible with an example.

Thanks



Tom Kyte
August 25, 2005 - 6:21 pm UTC

KEY KEY -> you have something like 

where partition_key between :x and :y;

at runtime, it knows "I will have the keys to get the low partitoin and the high partition" -- hence "key key"


ROWID (ROW L) -> rowid to rowid last (one way of reading it).  Means I'll get a rowid for the first partition we'll hit from the global index and then get more global rowids and finally get a last global rowid -- so we really have no idea how many or what partitions we'll hit until we actually range scan the global index.


1) yes

2) usually (bind variable peeking can change this)

3) not really, pstart = 2 pstop = 3 and there are 8 partitions.  we have eliminated 6 partitions but we will use global stats to optimize.


ops$tkyte@ORA9IR2> CREATE TABLE t
  2  (
  3    dt  date,
  4    x   int,
  5    y   varchar2(25)
  6  )
  7  PARTITION BY RANGE (dt)
  8  (
  9    PARTITION part1 VALUES LESS THAN (to_date('13-mar-2003','dd-mon-yyyy')) ,
 10    PARTITION part2 VALUES LESS THAN (to_date('14-mar-2003','dd-mon-yyyy')) ,
 11    PARTITION junk VALUES LESS THAN (MAXVALUE)
 12  )
 13  /
Table created.

ops$tkyte@ORA9IR2> create index tidx1 on t(x) global;
Index created.

ops$tkyte@ORA9IR2> variable x varchar2(20)
ops$tkyte@ORA9IR2> @plan "select * from t where dt = to_date(:x)"
PLAN_TABLE_OUTPUT
----------------------------------------

-----------------------------------------------------------------------
| Id  | Operation              |  Name| Bytes | Cost  | Pstart| Pstop |
-----------------------------------------------------------------------
|   0 | SELECT STATEMENT       |      |    36 |     2 |       |       |
|   1 |  PARTITION RANGE SINGLE|      |       |       |   KEY |   KEY |
|*  2 |   TABLE ACCESS FULL    | T    |    36 |     2 |   KEY |   KEY |
-----------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("T"."DT"=TO_DATE(:Z))
Note: cpu costing is off
15 rows selected.

ops$tkyte@ORA9IR2> @plan "select * from t where x = to_Number(:x)"
PLAN_TABLE_OUTPUT
-------------------------------------------------------

-----------------------------------------------------------------------
| Id  | Operation                          |  Name       |Pstart| Pstop |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |             |      |       |
|   1 |  TABLE ACCESS BY GLOBAL INDEX ROWID| T           |ROWID | ROW L |
|*  2 |   INDEX RANGE SCAN                 | TIDX1       |      |       |
-------------------------------------------------------------------------


Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("T"."X"=TO_NUMBER(:Z))
Note: cpu costing is off
15 rows selected.

ops$tkyte@ORA9IR2> @plan "select * from t where dt >= to_date(:x) and dt < to_date( '14-mar-2003' )"

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

-----------------------------------------------------------------
| Id  | Operation                 |  Name       | Pstart| Pstop |
-----------------------------------------------------------------
|   0 | SELECT STATEMENT          |             |       |       |
|*  1 |  FILTER                   |             |       |       |
|   2 |   PARTITION RANGE ITERATOR|             |   KEY |     2 |
|*  3 |    TABLE ACCESS FULL      | T           |   KEY |     2 |
-----------------------------------------------------------------

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

   1 - filter(TO_DATE(:Z)<TO_DATE('2003-03-14 00:00:00', 'yyyy-mm-dd hh24:mi:ss'))
   3 - filter("T"."DT">=TO_DATE(:Z))
Note: cpu costing is off
17 rows selected.
 

In list and local indexes - Is this a bug?

Pratap, April 20, 2006 - 5:09 am UTC

drop table t_test;

create table t_test
(
a number,
b number
)
partition by list ( b )
(
partition p1 values ( 1 ),
partition p2 values ( 2 ) tablespace example
);

create index t_test_ind on t_test (a) local
;

insert into t_test values ( 1, 1 );

insert into t_test values ( 2, 2 );

drop table driver;

create table driver
(
a number,
b number
);

insert into driver values ( 1, 1 );

commit;

explain plan for
select /*+ use_nl( driver t_test) ordered index(t_test t_test_ind) */ *
from driver, t_test
where driver.a = t_test.a
and driver.b = t_test.b
and driver.b in ( 1, 2 );

select * from table(dbms_xplan.display);



-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Pstart| Pstop |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 52 | 8 | | |
|* 1 | TABLE ACCESS BY LOCAL INDEX ROWID| T_TEST | 1 | 26 | 3 | | |
| 2 | NESTED LOOPS | | 1 | 52 | 8 | | |
|* 3 | TABLE ACCESS FULL | DRIVER | 2 | 52 | 2 | | |
| 4 | PARTITION LIST INLIST | | | | |KEY(I) |KEY(I) |
|* 5 | INDEX RANGE SCAN | T_TEST_IND | 82 | | 2 |KEY(I) |KEY(I) |
-------------------------------------------------------------------------------------------------

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

1 - filter("DRIVER"."B"="T_TEST"."B" AND ("T_TEST"."B"=1 OR "T_TEST"."B"=2))
3 - filter("DRIVER"."B"=1 OR "DRIVER"."B"=2)
5 - access("DRIVER"."A"="T_TEST"."A")

Note: cpu costing is off


-- Both the local index partitions are probed, even though the "and driver.b = t_test.b" join on partition key is there
-- The proof -


alter tablespace example offline;

ERROR:
ORA-00376: file 5 cannot be read at this time
ORA-01110: data file 5: '/pool3/acbsdw/oradata/data/example01.dbf'

-- Now use trim on the driver table filter, so that the transitivity is not applied

explain plan for
select /*+ use_nl( driver t_test) ordered index(t_test t_test_ind) */ *
from driver, t_test
where driver.b = t_test.b
and trim(driver.b) in ( 1, 2 );

select * from table(dbms_xplan.display);


----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Pstart| Pstop |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 364 | 5 | | |
| 1 | NESTED LOOPS | | 7 | 364 | 5 | | |
|* 2 | TABLE ACCESS FULL | DRIVER | 3 | 78 | 2 | | |
| 3 | PARTITION LIST ITERATOR| | | | | KEY | KEY |
|* 4 | TABLE ACCESS FULL | T_TEST | 2 | 52 | 1 | KEY | KEY |
----------------------------------------------------------------------------------------

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

2 - filter(TO_NUMBER(TRIM(TO_CHAR("DRIVER"."B")))=1 OR
TO_NUMBER(TRIM(TO_CHAR("DRIVER"."B")))=2)
4 - filter("DRIVER"."B"="T_TEST"."B")

Note: cpu costing is off


-- This query runs fine, the transitivity is not applied


Tom Kyte
April 20, 2006 - 8:11 am UTC

you seem to have answered your own question in the end?

Version

Pratap, April 20, 2006 - 5:15 am UTC

The version for above test case is 9.2.0.5.0

Expected behavior or bug?

Pratap, April 20, 2006 - 8:31 am UTC

:-) Yes but there is always a confusion between feature (restriction) and a bug.


More to Explore

VLDB

If you are new to partitioning, check out Connor McDonald's introduction series here.

VLDB

Documentation set on VLDB and Partitioning.