2

I have a set of tables that look similar to this:

Time_Table (relatively small):

Time      (TIMESTAMP)
timeId    (NUMBER)
Data...   (NUMBER)

Table2 (large, about 30 rows per time_table row):

timeId    (NUMBER)
table2Id  (NUMBER)
Data...   (NUMBER)

Table3 (very large, around 10 rows per table2 row, currently 1.4 billion rows after a couple of hundred days):

timeId    (NUMBER)
table2Id  (NUMBER)
table3Id  (NUMBER)
Data...   (NUMBER)

My queries ALWAYS join on timeId at the very least, and each query is broken up into days (10 day read will result in 10 smaller queries). New data is written to all tables every day. We need to store (and query) years of data from these tables.

How do I partition these tables into daily chunks when the Time information is only known through a JOIN? Should I be looking at partitioning in ways not reliant on Time? Can this be done automatically, or does it have to be a manual process?

Oracle version 11.2

Samuel O'Malley
  • 3,471
  • 1
  • 23
  • 41

2 Answers2

2

Reference partitioning may help here. It allows a child table's partitioning scheme to be determined by the parent table.

Schema

--drop table table3;
--drop table table2;
--drop table time_table;

drop table time_table;
create table Time_Table
(
    time   TIMESTAMP,
    timeId NUMBER,
    Data01 NUMBER,
    constraint time_table_pk primary key (timeId)
)
partition by range (time)
(
    partition p1 values less than (date '2000-01-02'),
    partition p2 values less than (date '2000-01-03'),
    partition p3 values less than (date '2000-01-04')
);

create table table2
(
    timeId   number,
    table2Id number,
    Data01   number,
    constraint table2_pk primary key (table2ID),
    constraint table2_fk foreign key (timeId) references time_table(timeId)
);


create table table3
(
    timeId   number not null,
    table2Id number,
    table3Id number,
    Data01   number,
    constraint table3_pk primary key (table3ID),
    constraint table3_fk1 foreign key (timeId) references time_table(timeId),
    constraint table3_fk2 foreign key (table2ID) references table2(table2ID)
) partition by reference (table3_fk1);

Execution Plans

The Pstart and Pstop show that the huge child table is correctly pruned even though the partition predicate is only set on the small parent table.

explain plan for
select *
from table3
join time_table using (timeId)
where time = date '2000-01-02';

select * from table(dbms_xplan.display);

Plan hash value: 832465087

-----------------------------------------------------------------------------------------------------
| Id  | Operation              | Name       | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |            |     1 |    91 |     3   (0)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE SINGLE|            |     1 |    91 |     3   (0)| 00:00:01 |     2 |     2 |
|   2 |   NESTED LOOPS         |            |     1 |    91 |     3   (0)| 00:00:01 |       |       |
|*  3 |    TABLE ACCESS FULL   | TIME_TABLE |     1 |    39 |     2   (0)| 00:00:01 |     2 |     2 |
|*  4 |    TABLE ACCESS FULL   | TABLE3     |     1 |    52 |     1   (0)| 00:00:01 |     2 |     2 |
-----------------------------------------------------------------------------------------------------

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

   3 - filter("TIME_TABLE"."TIME"=TIMESTAMP' 2000-01-02 00:00:00')
   4 - filter("TABLE3"."TIMEID"="TIME_TABLE"."TIMEID")

Note
-----
   - dynamic sampling used for this statement (level=2)
   - automatic DOP: skipped because of IO calibrate statistics are missing

Warnings

Reference partitioning has a few quirks. It doesn't work with interval partitioning in 11g, so you have to manually define every partition for the parent table. The foreign keys are also impossible to disable which may require modifying some scripts. And like any rarely used feature it has a few bugs.

Jon Heller
  • 34,999
  • 6
  • 74
  • 132
  • I would not recommend that solution as it requires GLOBAL indexes and supports only 2 level of master-detail. It also requires FK which for warehouses could be a problem (APPEND insert will not work). Instead of that I can recommend redesign. What is TimeID? Is it value from sequence? Why? Time is natural unique value - use it instead of meaningless generated numbers. – Rusty Feb 13 '15 at 16:47
  • @Rusty You're right there are some definite downsides to reference partitioning. Although [APPEND does work](http://stackoverflow.com/q/23330143/409172) even when there's a foreign key. Denormalizing might be a better solution if it just adds one column and the table is already "fat". – Jon Heller Feb 13 '15 at 17:22
  • We used to use the timestamp as the PK, but there can actually be duplicate times and that is valid data. Can I still keep the "TimeId" but duplicate Time into the other tables? – Samuel O'Malley Feb 15 '15 at 05:35
  • @SamuelO'Malley Sure, that might make sense. It makes queries and partition pruning easier for the simple case, where only the timestamp is used. And it also allows other time dimension values to be used in other queries. It does requires about 10GB of storage for that one extra column though. – Jon Heller Feb 15 '15 at 07:59
  • @Jon Heller - according to Oracle spec http://docs.oracle.com/cd/E11882_01/server.112/e41084/statements_9014.htm : The target table cannot have any triggers or referential integrity constraints defined on it. So append hint will be ignored. – Rusty Feb 17 '15 at 09:30
  • @Rusty It is not documented but the referential integrity rule does not apply to reference partitioned tables. See the link in my first comment for a test case demonstrating direct-path inserts on a reference partitioned table. – Jon Heller Feb 17 '15 at 09:56
  • @Jon Heller. Good spot. So you considered this as well and found out that it works. Good to know. – Rusty Feb 17 '15 at 10:05
2
drop table time_table;
create table Time_Table
(
    time   TIMESTAMP,
--    timeId NUMBER, Why you need ID when you have timestamp?????
    Data01 NUMBER,
    constraint time_table_pk primary key (time) -- not timeID!!!
)
partition by range (time)
(
    partition p1 values less than (date '2000-01-02'),
    partition p2 values less than (date '2000-01-03'),
    partition p3 values less than (date '2000-01-04')
);

create table table2
(
    time     timestamp not null,
    table2ID number,
    Data01   number
)
partition by range (time)
(
    partition p1 values less than (date '2000-01-02'),
    partition p2 values less than (date '2000-01-03'),
    partition p3 values less than (date '2000-01-04')
);


create table table3
(
    time     timestamp not null,
    table2Id number,
    table3Id number,
    Data01   number
) 
partition by range (time)
(
    partition p1 values less than (date '2000-01-02'),
    partition p2 values less than (date '2000-01-03'),
    partition p3 values less than (date '2000-01-04')
);
Rusty
  • 1,988
  • 10
  • 12
  • Would this work while still keeping TimeID as the PK? We actually do get rows with the same timestamp but different Ids. – Samuel O'Malley Feb 15 '15 at 05:31
  • I would not use value generated from sequence at all. It makes data not transportable between environments. You need to use lock mechanism to prevent duplicates. Waiting time would be less than 1 microsecond. – Rusty Feb 17 '15 at 09:27
  • One more tip how to get always unique timestamp value. Return systimestamp value - normally it has precision of 6 on Unix or 3 on Windows. Trim it to 3 digits. Then you can use value from session level global variable-counter or rownum in insert as select operations. Get MOD(rownum, 1000000) and add this value to the systimestamp. So you can have 1 million unique values generated during 1 millisecond. That should be enough. Example: l_log_rec.log_time := l_log_rec.log_time + to_dsinterval('PT0.'||to_char(g_counter, 'fm000000009')||'S') – Rusty Feb 17 '15 at 09:35
  • Its a good tip, but unfortunately the Times (and Ids) I am writing come from diagnostic data in a system I can't modify. Events can happen at exactly the same time (not by accident, but actually dual events). – Samuel O'Malley Feb 18 '15 at 10:19