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