Search

OakieTags

Who's online

There are currently 0 users and 35 guests online.

Recent comments

Affiliations

Virtual date partitions

I posted this question on twitter earlier on today (It was a thought that crossed my mind during a (terrible) presentation on partitioning that I had to sit through a few weeks ago – it’s always possible to be prompted to think of some interesting questions no matter how bad the presentation is, though):

Quiz: if you create a virtual column as trunc(date_col,’W') and partition on it – will a query on date_col result in partition elimination?

The answer is yes – on the version of Oracle that I happened to have to hand (12c) the next time I had a few minutes spare. Here’s a quick and dirty demo – with data adjusted to the publication date, so you may need to adjust the code to your current date.

drop table transactions purge;

create table transactions (
	transaction_date	date		not null,
	job_id			varchar2(10)	not null,
	account_id		number(8)	not null,
	transaction_type	varchar2(2)	not null,
	transaction_id		varchar2(10)	not null,
	amount			number(10,2)	not null,
	padding			varchar2(100),
	transaction_week 	generated always as (trunc(transaction_date,'IW'))
)
partition by range (transaction_week) interval (numtodsinterval(7,'day')) (
	partition p0 values less than (to_date('30-Sep-2013','dd-mon-yyyy'))
)
;

insert into transactions( transaction_date, job_id  , account_id , transaction_type, transaction_id , amount  , padding )
values(trunc(sysdate)-7, 1, 1, 'IN', 1, 1, rpad('x',100))
;

insert into transactions( transaction_date, job_id  , account_id , transaction_type, transaction_id , amount  , padding )
values(trunc(sysdate), 2, 2, 'IN', 2, 2, rpad('x',100))
;

insert into transactions( transaction_date, job_id  , account_id , transaction_type, transaction_id , amount  , padding )
values(trunc(sysdate) + 7, 3, 3, 'IN', 3, 3, rpad('x',100))
;

begin
	dbms_stats.gather_table_stats(
                ownname          => user,
                tabname          =>'transactions',
                method_opt       => 'for all columns size 1'
	);
end;
/

So transaction_date is a column in the table, but the partitioning column is transaction_week, which is defined as trunc(transaction_date,’IW’) which gives me Mondays as the partition boundaries. (I always go back to the manuals for truncating on weeks – there are at least two options (W and IW) which have very different effects.)

So here are a couple of simple queries – will the optimizer do partition elimination ?


select * from transactions where transaction_date = sysdate;
select * from transactions where transaction_date = to_date('17-Oct-2013','dd-mon-yyyy');

And the answer is yes. Note the “partition single”, although in both cases the partition start and stop show KEY/KEY – which means Oracle has to deduce which partitions are relevant at run-time.


-------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name         | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |              |     1 |   130 |     2   (0)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE SINGLE|              |     1 |   130 |     2   (0)| 00:00:01 |   KEY |   KEY |
|*  2 |   TABLE ACCESS FULL    | TRANSACTIONS |     1 |   130 |     2   (0)| 00:00:01 |   KEY |   KEY |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("TRANSACTION_DATE"=SYSDATE@! AND "TRANSACTIONS"."TRANSACTION_WEEK"=TRUNC(SYSDATE@!,'fmiw'))

-------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name         | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |              |     1 |   130 |     2   (0)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE SINGLE|              |     1 |   130 |     2   (0)| 00:00:01 |   KEY |   KEY |
|*  2 |   TABLE ACCESS FULL    | TRANSACTIONS |     1 |   130 |     2   (0)| 00:00:01 |   KEY |   KEY |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("TRANSACTION_DATE"=TO_DATE(' 2013-10-17 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
              "TRANSACTIONS"."TRANSACTION_WEEK"=TRUNC(TO_DATE(' 2013-10-17 00:00:00',
              'syyyy-mm-dd hh24:mi:ss'),'fmiw'))

Pretty clever !

I find it slightly strange that the predicate section reports the derived predicate on the virtual column – it’s clearly not necessary for this example because we can see that the optimizer has already decided that the query will visit just one partition, so and every row in that partition will have the same value for the virtual column. Possibly its presence is just a side effect of the generic strategy used for more complex predicates and the special-case code hasn’t been included, but maybe there’s something I’ve overlooked that makes it necessary.