Unable to use sub-partitioned column in query plan (oracle) - oracle

I have a 20 GB table which for some requirement, have to be range partitioned on DATE1 field and list sub-partitioned on DATE2 field.
Created a virtual column (VC) on that table to extract numeric month value from DATE2 field and use this VC as the sub-partition key.
Per requirement, we'll have 30 partitions on DATE1 and each of them will have 12 sub-partitions on VC.
The max size of any sub-partition can be up to 5 GB.
N.B. I could not implement Multi-column partitioning as our inbuilt partition manager does not support them.
Also, I could not implement a RANGE-RANGE partition-sub-partitioning as the two Date fields (DATE1 and DATE2) have no-sync in dates coming in them, causing INSERT operations to fail.
Next, I've a simple view created on top of this table. All Date fields including VC are exposed in this view. While querying SELECT * FROM vw; Plan shows PARTITION RANGE SINGLE as expected.
Now, I've a web front end through which I can click on the DATE2 to open some more details. It basically passed DATE2 as filter to query on another table and displays huge records (approx. 3 million).
The PROBLEM is, on clicking the DATE2 field, I'm not able to hit the sub-partition as its based on MONTH value (VC) and not the date.
Thus, I want a PARTITION LIST SINGLE instead of the current PARTITION LIST ALL in plan.
The QUESTION is, how to write a select query to select the SUB partition. I know I have to use the VC in the filter to achieve it, but, the irony is the web-application cannot pass VC in the backend especially when we display only DATE values (not VC).
Also, if we CANNOT hit the sub-partition, is there any way we can better the performance by using INDEXES or PARALLELISM?
Please help.
--***********************************************
--TABLE CREATION STATEMENT
--***********************************************
CREATE TABLE M_DTX
(
R_ID NUMBER(3),
R_AMT NUMBER(5),
DATE1 DATE,
DATE2 DATE,
VC NUMBER(2) GENERATE ALWAYS AS (EXTRACT(MONTH FROM DATE2))
)
PARTITION BY RANGE (DATE1)
SUBPARTITION BY LIST (VC)
SUBPARTITION TEMPLATE (
SUBPARTITION M1 VALUES (1),
SUBPARTITION M2 VALUES (2),
SUBPARTITION M3 VALUES (3),
SUBPARTITION M4 VALUES (4),
SUBPARTITION M5 VALUES (5),
SUBPARTITION M6 VALUES (6),
SUBPARTITION M7 VALUES (7),
SUBPARTITION M8 VALUES (8),
SUBPARTITION M9 VALUES (9),
SUBPARTITION M10 VALUES (10),
SUBPARTITION M11 VALUES (11),
SUBPARTITION M12 VALUES (12)
TABLESPACE M_DATA
)
(
PARTITION M_DTX_2015060100
VALUES LESS THAN (
TO_DATE(' 2015-06-01 00:00:01', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')
) SEGMENT CREATION DEFERRED
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS NOLOGGING
STORAGE( INITIAL 1048576 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT
) TABLESPACE M_DATA);
--******************************************
--VIEW ON TOP OF M_DTX:
--******************************************
CREATE OR REPLACE VIEW v_dtx AS
SELECT r_id, TRUNC(date2) date2_dd, vc, SUM(r_amt) amt
FROM m_dtx WHERE date1 = TRUNC(sysdate)
GROUP BY r_id, TRUNC(date2), vc;
--******************************************
--QUERY FIRED FROM WEB-APPLICATION (AFTER CLICKING ON date2_DD):
--******************************************
SELECT * FROM m_dtx WHERE date1 = trunc(sysdate) AND date2 = ''date2_dd'';
--this is where its bypassing the sub-partition as I could not substitute month or VC ...

The only way I found to hit the subpartition is to write the query this way:
SELECT *
FROM m_dtx
WHERE date1 = trunc(sysdate)
AND date2 = ''date2_dd''
and vc = EXTRACT(MONTH FROM ''date2_dd'');
So, you don't need to get the vc from the previous screen, because you have the date2.
I've tested with:
SELECT *
FROM m_dtx
WHERE date1 = trunc(sysdate)
AND date2 = to_date('10-dec-2014','dd-mon-yyyy')
and vc = EXTRACT(MONTH FROM to_date('10-dec-2014','dd-mon-yyyy'));

Related

Problem with alter table big_table modify partition

I create table:
create table big_table(
bt_id number primary key,
bt_date date,
bt_value varchar2(20)
)
Then I wnat partition this table (code abbreviated):
alter table big_table modify
partition by range (bt_date)
interval(numtoyminterval(1, 'MONTH'))
subpartition by hash (bt_id)
(
partition nn_st_p1 values less than (to_date(' 2019-05-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
subpartitions 4
store in (ipr_tbl),
)online
Error message:
17:20:39 line 1: ORA-14006: invalid partition name
I can't understand what is wrong with my partition name?
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
Try something like this
CREATE TABLE big_table
(bt_id NUMBER PRIMARY KEY
, bt_date DATE
, bt_value VARCHAR2(20)
)
PARTITION BY RANGE (bt_date) INTERVAL (NUMTOYMINTERVAL(1,'MONTH'))
SUBPARTITION BY HASH (bt_id) SUBPARTITIONS 4
(PARTITION nn_st_p1 VALUES LESS THAN (TO_DATE('01-MAY-2019','dd-MON-yyyy'))
)
PARALLEL;

Alternative to VALUES LESS THAN in interval partitioing creation query

As we know that Interval partitioning is an extension of Range partitioning and it takes much of its syntax from
range partitioning.
From various sources on the net, I assume that interval partitioning creation query do have mandatory following clause:
VALUES LESS THAN (XXX)
But when we go for interval partitioning, is there any simpler way where we do not provide any VALUE LESS THAN CLAUSE.
I was searching for something similar like EQUAL TO (012019) where 012019 corresponds to the January month Interval
of 2019 year.
I have gone through following links for the help/understanding but couldn't find useful for my concern.
http://www.dba-oracle.com/t_interval_partitioning.htm
https://docs.oracle.com/database/121/VLDBG/GUID-C121EA1B-2725-4464-B2C9-EEDE0C3C95AB.htm
The code used by me is like as follows:
create table
pos_data (
start_date DATE,
store_id NUMBER,
inventory_id NUMBER(6),
qty_sold NUMBER(3)
)
PARTITION BY RANGE (start_date)
INTERVAL(NUMTOYMINTERVAL(1, 'MONTH'))
(
PARTITION pos_data_p2 VALUES LESS THAN (TO_DATE('1-7-2007', 'DD-MM-YYYY')),
PARTITION pos_data_p3 VALUES LESS THAN (TO_DATE('1-8-2007', 'DD-MM-YYYY'))
);
From my search It looks like there is no other way apart from the one VALUE LESS THAN.
Please share if anyone have some understanding about someother approach for creating interval based partitioning.
Remainder: my concern is in BOLD above
I think what you are looking for is the partition extended name
PARTITION FOR(DATE'2019-01-01')
Actually the LESS THAN definition plays in interval partitioning close to zero role.
You use it only once while creating the table to define some lower bound of the data.
Here is an example to define a table containing data starting from the year 2019
create table pos_data (
start_date DATE,
store_id NUMBER
)
SEGMENT CREATION DEFERRED
PARTITION BY RANGE (start_date)
INTERVAL(NUMTOYMINTERVAL(1, 'MONTH'))
(
PARTITION pos_data_init VALUES LESS THAN (TO_DATE('1-1-2019', 'DD-MM-YYYY'))
);
Note that the first partition is not an interval partition (INTERVAL = NO) and doesn't physically exists due to SEGMENT CREATION DEFERRED. It will also never contain any data, as you start with 2019 content.
select PARTITION_POSITION,PARTITION_NAME,INTERVAL,MIN_EXTENT, HIGH_VALUE
from user_tab_partitions where table_name = 'POS_DATA'
order by PARTITION_POSITION;
PARTITION_POSITION PARTITION_NAME INTERVAL MIN_EXTENT HIGH_VALUE
------------------ -------------- -------- ---------- -------------------------------
1 POS_DATA_INIT NO TO_DATE(' 2019-01-01 00:00:00',
New partitions are created on the fly e.g. while inserting new data, you don't need to specify LESS THAN
insert into pos_data(start_date,store_id) values(DATE'2019-01-01',1);
PARTITION_POSITION PARTITION_NAME INTERVAL MIN_EXTENT HIGH_VALUE
------------------ -------------- -------- ---------- -------------------------------
1 POS_DATA_INIT NO TO_DATE(' 2019-01-01 00:00:00',
2 SYS_P16713 YES 1 TO_DATE(' 2019-02-01 00:00:00',
While accessing the table you use the partition_extended_name, you may choose any date within the month to reference the partition.
select * from pos_data
partition for (date'2019-01-15');
Same syntax may be used for partition maintainance
alter table pos_data move partition for (date'2019-01-30') compress;

Create partition in an indexed table

I have a table which holds data for 12 hours. Every 5 minutes, it keeps deleting data which is more than 12 hours old and adds new data. It has almost 15-20 million rows. I want to create partition by hour and also index the table on column(time_stamp), to make the row fetching faster.
I will obviously do interval or range partitioning, but found that interval partitioning doesn't work on indexed table. So please help me with the syntax so that oracle creates 12 partitions and automatically adds new one when new time_stamp data is added which is after first 12 hours. I have already got a procedure to delete oldest partition which i will use so that there is always 12 hours of data.
I am stating the columns below.
CustomerId,ApplicationId,Time_Stamp,Service
I have tried to come up with this, but don't know how it will create new partitions
CREATE TABLE local_table
(customerid VARCHAR2(30),
applicationid VARCHAR2(30),
time_stamp TIMESTAMP,
service VARCHAR2(30))
PARTITION BY RANGE(time_stamp)
(
PARTITION t1 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 00:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t2 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 01:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t3 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 02:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t4 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 03:00:00.0','YYYY-MM- DD HH24:MI:SS.ff')),
PARTITION t5 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 04:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t6 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 05:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t7 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 06:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t8 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 07:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t9 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 08:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t10 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 09:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t11 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 10:00:00.0','YYYY-MM-DD HH24:MI:SS.ff')),
PARTITION t12 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 11:00:00.0','YYYY-MM-DD HH24:MI:SS.ff'))
);
create index index_time_stamp on local_table(TIME_STAMP);
I am using- Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit
Create table with autopartitiong and LOCAL (partitioning) index.
The local_partitioned_index clauses let you specify that the index is partitioned on the same columns, with the same number of partitions and the same partition bounds as table. Oracle Database automatically maintains local index partitioning as the underlying table is repartitioned.
CREATE TABLE local_table
(customerid VARCHAR2(30),
applicationid VARCHAR2(30),
time_stamp TIMESTAMP,
service VARCHAR2(30))
PARTITION BY RANGE(time_stamp)
INTERVAL(NUMTODSINTERVAL(1, 'HOUR'))
(PARTITION t1 VALUES LESS THAN(TO_TIMESTAMP('2015-02-25 00:00:00.0','YYYY-MM-DD HH24:MI:SS.ff'))
);
CREATE INDEX index_time_stamp on local_table(TIME_STAMP) LOCAL;
SELECT *
FROM user_tab_partitions;
INSERT INTO local_table VALUES('1', 'a', sysdate, 'b');
SELECT *
FROM user_tab_partitions;
INSERT INTO local_table VALUES('2', 'c', sysdate + 1/1440, 'd');
SELECT *
FROM user_tab_partitions;
INSERT INTO local_table VALUES('3', 'e', sysdate + 1/24, 'f');
SELECT *
FROM user_tab_partitions;
The INTERVAL clause of the CREATE TABLE statement establishes interval
partitioning for the table. You must specify at least one range
partition using the PARTITION clause. The range partitioning key value
determines the high value of the range partitions, which is called the
transition point, and the database automatically creates interval
partitions for data beyond that transition point. The lower boundary
of every interval partition is the non-inclusive upper boundary of the
previous range or interval partition.
For example, if you create an interval partitioned table with monthly
intervals and the transition point at January 1, 2010, then the lower
boundary for the January 2010 interval is January 1, 2010. The lower
boundary for the July 2010 interval is July 1, 2010, regardless of
whether the June 2010 partition was previously created. Note, however,
that using a date where the high or low bound of the partition would
be out of the range set for storage causes an error. For example,
TO_DATE('9999-12-01', 'YYYY-MM-DD') causes the high bound to be
10000-01-01, which would not be storable if 10000 is out of the legal
range.
Some quick DRAFT for your second question about DROP PARTITION. Examine and debug before uncomment ALTER TABLE. You can create scheduler job for run this block of code every hour.
DECLARE
l_pt_cnt NUMBER;
l_pt_name VARCHAR2(100);
l_minrowid ROWID;
l_mindate TIMESTAMP;
BEGIN
-- get partition count
SELECT count(*)
INTO l_pt_cnt
FROM user_tab_partitions
WHERE table_name = 'LOCAL_TABLE';
IF l_pt_cnt > 12 THEN
SELECT min(time_stamp)
INTO l_mindate
FROM LOCAL_TABLE;
-- get ROWID with min date
SELECT min(rowid)
INTO l_minrowid
FROM LOCAL_TABLE
WHERE time_stamp = l_mindate;
-- get name of partition with row with min date
SELECT subobject_name
INTO l_pt_name
FROM LOCAL_TABLE
JOIN user_objects
ON dbms_rowid.rowid_object(LOCAL_TABLE.rowid) = user_objects.object_id
WHERE LOCAL_TABLE.rowid = l_minrowid;
DBMS_OUTPUT.put_line('ALTER TABLE LOCAL_TABLE DROP PARTITION ' || l_pt_name );
--EXECUTE IMMEDIATE 'ALTER TABLE LOCAL_TABLE DROP PARTITION ' || l_pt_name;
END IF;
END;

How to create Day and Hourly partitioning in Oracle 11g?

I am using Oracle-11G database.
I am trying to create day wise partition and again sub-partition on hourly level.
Below is the script I am using
CREATE TABLE test_shipments
( order_date DATE NOT NULL
, delivery_date DATE NOT NULL
)
PARTITION BY RANGE (order_date)
INTERVAL (NUMTODSINTERVAL(1, 'day'))
SUBPARTITION BY RANGE (order_date)
( PARTITION p_2006_jul VALUES LESS THAN (TO_DATE('2006-08-01','YYYY-MM-DD'))
( SUBPARTITION p06_jul_e1 VALUES LESS THAN (TO_DATE('2006-08-01 12:00:00','YYYY-MM-DD hh24:mi:ss'))
,SUBPARTITION p06_jul_e2 VALUES LESS THAN (TO_DATE('2006-08-01 13:00:00','YYYY-MM-DD hh24:mi:ss'))
,SUBPARTITION p06_jul_e3 VALUES LESS THAN (TO_DATE('2006-08-01 14:00:00','YYYY-MM-DD hh24:mi:ss'))
)
);
I am expecting to have partition for every day and again sub-partition for every hour in that day. The above script creates partition per day only. Hourly sub-partition is not getting created. Can anyone please guide?
Why do you not create simple partition by Hourly range? Example: PARTITION BY RANGE (order_date) INTERVAL (INTERVAL '1' HOUR)
Anyway, this one works. However, you should use TIMESTAMP data type instead of DATE. For hour value you have to create a virtual column.
CREATE TABLE test_shipments (
order_date TIMESTAMP(0) NOT NULL,
order_date_hour NUMBER GENERATED ALWAYS AS (EXTRACT(HOUR FROM order_date)),
delivery_date DATE NOT NULL)
PARTITION BY RANGE (order_date)
INTERVAL (INTERVAL '1' DAY)
SUBPARTITION BY LIST (order_date_hour)
SUBPARTITION TEMPLATE
(SUBPARTITION HOUR00 VALUES (0),
SUBPARTITION HOUR01 VALUES (1),
SUBPARTITION HOUR02 VALUES (2),
SUBPARTITION HOUR03 VALUES (3),
...
SUBPARTITION HOUR22 VALUES (22),
SUBPARTITION HOUR23 VALUES (23)
)
(
PARTITION P_20150101 VALUES LESS THAN (TIMESTAMP'2015-01-01 00:00:00')
);
If you are enforced to use DATE data type you can do it like this:
CREATE TABLE test_shipments (
order_date DATE NOT NULL,
order_date_hour NUMBER GENERATED ALWAYS AS (TO_CHAR(order_date, 'HH24')),
delivery_date DATE NOT NULL)
PARTITION BY RANGE (order_date)
INTERVAL (INTERVAL '1' DAY)
SUBPARTITION BY LIST (order_date_hour)
SUBPARTITION TEMPLATE
(SUBPARTITION HOUR00 VALUES (0),
SUBPARTITION HOUR01 VALUES (1),
SUBPARTITION HOUR02 VALUES (2),
SUBPARTITION HOUR03 VALUES (3),
...
SUBPARTITION HOUR22 VALUES (22),
SUBPARTITION HOUR23 VALUES (23)
)
(
PARTITION P_20150101 VALUES LESS THAN (DATE '2015-01-01')
);
Maybe you want to group night time hours:
(
SUBPARTITION HOUR_NIGHT VALUES (0,1,2,3,4,5,6, 19,20,21,22,23),
SUBPARTITION HOUR07 VALUES (7),
SUBPARTITION HOUR08 VALUES (8),
SUBPARTITION HOUR11 VALUES (11),
...
SUBPARTITION HOUR17 VALUES (17),
SUBPARTITION HOUR18 VALUES (18)
)

Oracle Partition Interval by day - wrong high value?

I created a table with the following partition interval:
create table
pos_data_two (
start_date TIMESTAMP,
store_id NUMBER,
inventory_id NUMBER(6),
qty_sold NUMBER(3)
)
PARTITION BY RANGE (start_date)
INTERVAL(NUMTODSINTERVAL (1, 'DAY'))
(
PARTITION pos_data_p2 VALUES LESS THAN (TO_DATE('30.10.2013', 'DD.MM.YYYY'))
);
When I insert a a row with the timestamp value
'31.10.2013 00:00:00'
The high value of the new created partition is:
TIMESTAMP' 2013-11-01 00:00:00'
Is that correct? Shouldn't it be 2013-10-31 00:00:00 ??
(Disclaimer: I'm just guessing here)
You're partitioning by days, so values for a given date fall into the same partition.
The row you're inserting has a start_date that's exactly at midnight, so Oracle has to decide whether to put it onto the previous day or onto the next day.
Apparently, Oracle is using the rule
lower_bound <= value < upper_bound
to decide which interval a value should go into, so your value
2013-10-31 00:00:00
goes into the interval
[2013-10-31 00:00:00; 2013-11-01 00:00:00 [

Resources