Had discussed
about the importance of NOT NULL constraints in-terms of query rewrite
capabilities in the past.
The following example appeared as part of real production
scenario, where a simple predicate against the sub-partition key columns
doesn’t perform partition elimination.
rajesh@ORA11G> create table t
  2  partition by range( end_dt )
  3  subpartition by range( start_dt )
  4  (
  5    partition p_2014 values less than
  6             (
to_date('01-Jan-2015','dd-mon-yyyy') )
  7     (
  8                subpartition sp_2014_q1 values
less than
  9                             (
to_date('01-apr-2014','dd-mon-yyyy') ) ,
 10                subpartition sp_2014_q2 values
less than
 11                             (
to_date('01-jul-2014','dd-mon-yyyy') ) ,
 12                subpartition sp_2014_q3 values
less than
 13                             (
to_date('01-oct-2014','dd-mon-yyyy') ) ,
 14                subpartition sp_2014_q4 values less than
 15                             (
to_date('01-jan-2015','dd-mon-yyyy') )
 16     ) ,
 17    partition p_2015 values less than
 18             (
to_date('01-Jan-2016','dd-mon-yyyy') )
 19     (
 20               subpartition sp_2015_q1 values
less than
 21                             (
to_date('01-apr-2015','dd-mon-yyyy') ) ,
 22               subpartition sp_2015_q2 values
less than
 23                             (
to_date('01-jul-2015','dd-mon-yyyy') ) ,
 24               subpartition sp_2015_q3 values less than
 25                             (
to_date('01-oct-2015','dd-mon-yyyy') ) ,
 26               subpartition sp_2015_q4 values
less than
 27                             (
to_date('01-jan-2016','dd-mon-yyyy') )
 28     )
 29  )
 30  as
 31  select a.* ,
 32     to_date('01-Jan-2014','dd-mon-yyyy') +
mod(rownum,730)-1 as start_dt,
 33     to_date('01-Jan-2014','dd-mon-yyyy') +
mod(rownum,730) as end_dt
 34  from all_objects a;
Table created.
rajesh@ORA11G> alter table t add constraint t_chk check(
start_dt < end_dt );
Table altered.
Given the fact that, table is partitioned by END_DT and sub partitioned
by START_DT and a check constraint to enforce that START_DT is always less than
END_DT.
rajesh@ORA11G> set autotrace traceonly explain
rajesh@ORA11G> select *
  2  from t
  3  where start_dt =
to_date('13-Jan-2015','dd-mon-yyyy') ;
Execution Plan
----------------------------------------------------------
Plan hash value: 2183203583
-------------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)| Pstart| Pstop |
-------------------------------------------------------------------------------------
|   0 | SELECT
STATEMENT        |     
|   136 | 23936 |    58  
(6)|       |       |
|   1 |  PARTITION RANGE
ALL    |      |  
136 | 23936 |    58   (6)|    
1 |     2 |
|   2 |   PARTITION RANGE SINGLE|      |  
136 | 23936 |    58   (6)|      
|       |
|*  3 |    TABLE ACCESS FULL    | T   
|   136 | 23936 |    58  
(6)|       |       |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - filter("START_DT"=TO_DATE('
2015-01-13 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
The above sql has a filter against the START_DT column, but why should
it hit both the partitions, can’t the optimizer do something like this using
metadata and do partition elimination?
So given the filter start_dt = 13-Jan-2015, and the
presence of check constraint can produce the additional transitive filter like
this (end_dt
> 13-Jan-2015)
o  
13-Jan-2015 less than 01-Jan-2015 – condition
false, so skip scanning first partition.
o  
13-Jan-2015 less than 01-Jan-2016 – condition
true, so scan this partition and its relevant sub-partition to produce results.
But the optimizer doesn’t perform that. Instead it hit both the
partitions.
Peeking into the 10053 trace file, produced some clue on this (highlighted
below).
**************************
PM:     PM bypassed: Outer
query contains no views.
PM:     PM bypassed: Outer
query contains no views.
query block SEL$1 (#0) unchanged
FPD: Considering simple filter push in query block
SEL$1 (#0)
"T"."START_DT"=TO_DATE('
2015-01-13 00:00:00', 'syyyy-mm-dd hh24:mi:ss')
try to generate transitive predicate from check
constraints for query block SEL$1 (#0)
constraint:
"T"."START_DT"<"T"."END_DT"
finally: "T"."START_DT"=TO_DATE('
2015-01-13 00:00:00', 'syyyy-mm-dd hh24:mi:ss')
apadrv-start sqlid=18016176355183581983
  :
    call(in-use=2136,
alloc=16344), compile(in-use=67952, alloc=72680), execution(in-use=109984,
alloc=113960)
*******************************************
Peeked values of the binds in SQL statement
*******************************************
Final query after transformations:******* UNPARSED QUERY IS
*******
SELECT "T"."OWNER" "OWNER",
 
"T"."OBJECT_NAME" "OBJECT_NAME",
 
"T"."SUBOBJECT_NAME" "SUBOBJECT_NAME",
 
"T"."OBJECT_ID" "OBJECT_ID",
 
"T"."DATA_OBJECT_ID" "DATA_OBJECT_ID",
 
"T"."OBJECT_TYPE" "OBJECT_TYPE",
 
"T"."CREATED" "CREATED",
 
"T"."LAST_DDL_TIME" "LAST_DDL_TIME",
 
"T"."TIMESTAMP" "TIMESTAMP",
 
"T"."STATUS" "STATUS",
 
"T"."TEMPORARY" "TEMPORARY",
 
"T"."GENERATED" "GENERATED",
 
"T"."SECONDARY" "SECONDARY",
 
"T"."NAMESPACE" "NAMESPACE",
 
"T"."EDITION_NAME" "EDITION_NAME",
 
"T"."START_DT" "START_DT",
 
"T"."END_DT" "END_DT"
FROM "RAJESH"."T" "T"
WHERE "T"."START_DT"=TO_DATE('
2015-01-13 00:00:00', 'syyyy-mm-dd hh24:mi:ss')
Now let’s add a NOT NULL constraints on the partition key columns and
rerun our sql to see the plan it picks.
rajesh@ORA11G> alter table t modify END_DT NOT NULL;
Table altered.
rajesh@ORA11G> set autotrace traceonly explain
rajesh@ORA11G> select *
  2  from t
  3  where start_dt =
to_date('13-Jan-2015','dd-mon-yyyy') ;
Execution Plan
----------------------------------------------------------
Plan hash value: 3874588989
-------------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)| Pstart| Pstop |
-------------------------------------------------------------------------------------
|   0 | SELECT
STATEMENT        |      |  
136 | 23936 |    55   (0)|      
|       |
|   1 |  PARTITION RANGE
SINGLE |      |   136 | 23936 |    55  
(0)|     2 |     2 |
|   2 |   PARTITION RANGE SINGLE|      |  
136 | 23936 |    55   (0)|    
1 |     1 |
|*  3 |    TABLE ACCESS FULL    | T   
|   136 | 23936 |    55  
(0)|     5 |     5 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   3 -
filter("START_DT"=TO_DATE(' 2015-01-13 00:00:00', 'syyyy-mm-dd
hh24:mi:ss') AND
             
"END_DT">TO_DATE(' 2015-01-13 00:00:00', 'syyyy-mm-dd
hh24:mi:ss'))
So given the NOT NULL constraint on the END_DT column and the presence
of check constraint, helped the optimizer to transparently rewrite the given
condition 
start_dt = to_date('13-Jan-2015','dd-mon-yyyy')
as
start_dt = to_date('13-Jan-2015','dd-mon-yyyy') and end_dt > to_date('13-Jan-2015','dd-mon-yyyy')
Since the end_dt > to_date('13-Jan-2015','dd-mon-yyyy'), it applied
this criteria to eliminate the first partition from scanning and hit straight
into the Second partition, and within that it only scan the relevant
sub-partition required for this SQL.
And the 10053 trace file confirms the same.
FPD: Considering simple filter push in query block SEL$1 (#0)
"T"."START_DT"=TO_DATE(' 2015-01-13
00:00:00', 'syyyy-mm-dd hh24:mi:ss')
try to generate transitive predicate from check constraints for
query block SEL$1 (#0)
constraint:
"T"."START_DT"<"T"."END_DT"
finally:
"T"."START_DT"=TO_DATE(' 2015-01-13 00:00:00', 'syyyy-mm-dd
hh24:mi:ss') AND "T"."END_DT">TO_DATE(' 2015-01-13
00:00:00', 'syyyy-mm-dd hh24:mi:ss')
FPD:   transitive
predicates are generated in query block SEL$1 (#0)
"T"."START_DT"=TO_DATE(' 2015-01-13
00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
"T"."END_DT">TO_DATE(' 2015-01-13 00:00:00',
'syyyy-mm-dd hh24:mi:ss')
apadrv-start sqlid=18016176355183581983
Final query AFTER transformations:******* UNPARSED QUERY
IS
  *******
  SELECT
"T"."OWNER" "OWNER",
   
"T"."OBJECT_NAME" "OBJECT_NAME",
   
"T"."SUBOBJECT_NAME" "SUBOBJECT_NAME",
   
"T"."OBJECT_ID" "OBJECT_ID",
   
"T"."DATA_OBJECT_ID" "DATA_OBJECT_ID",
   
"T"."OBJECT_TYPE" "OBJECT_TYPE",
   
"T"."CREATED" "CREATED",
   
"T"."LAST_DDL_TIME" "LAST_DDL_TIME",
   
"T"."TIMESTAMP" "TIMESTAMP",
   
"T"."STATUS" "STATUS",
   
"T"."TEMPORARY" "TEMPORARY",
   
"T"."GENERATED" "GENERATED",
   
"T"."SECONDARY" "SECONDARY",
   
"T"."NAMESPACE" "NAMESPACE",
   
"T"."EDITION_NAME" "EDITION_NAME",
   
"T"."START_DT" "START_DT",
   
"T"."END_DT" "END_DT"
  FROM
"RAJESH"."T" "T"
  WHERE "T"."START_DT"=TO_DATE(' 2015-01-13
00:00:00', 'syyyy-mm-dd hh24:mi:ss')
  AND
"T"."END_DT"   
>TO_DATE(' 2015-01-13 00:00:00', 'syyyy-mm-dd hh24:mi:ss')
All these sort of partition elimination (pruning) is possible only with
NOT NULL constraint in place.
The dreaded NOT NULL constraint strikes again!
No comments:
Post a Comment