Oracle interval partitions are not created automatically with alter sub-partition template - oracle

I am using oracle 12c Interval partitioning. I have created the range partition with 1 month interval and list sub-partition using a unique identifier (lets say LOGIN_INTFID).
In table DDL I have added the list of sub-partitions that were known to me at the time of table creation. Here is extract of table DDL:
CREATE TABLE TEST
(
UNIQUE_ID NUMBER(9) NOT NULL,
LOGIN_INTFID VARCHAR2(20) NOT NULL,
LOGIN_SEQNO NUMBER(15) NOT NULL,
LOGIN_DATE DATE DEFAULT SYSDATE NOT NULL
)
PARTITION BY RANGE (LOGIN_DATE)
INTERVAL (NUMTOYMINTERVAL(1,'MONTH'))
SUBPARTITION BY LIST (LOGIN_INTFID) SUBPARTITION TEMPLATE (
SUBPARTITION SP1 VALUES ('ABC'),
SUBPARTITION SP2 VALUES ('DEF'),
)
(PARTITION TEST_Y2018M7D1 VALUES LESS
THAN (TO_DATE('2018-07-01 23:59:59', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')))
;
With this, new partition and sub partitions are created successfully. Later on, I had added one more sub-partition using the following alter command:
ALTER TABLE TEST modify partition SYS_P7068 add subpartition SP3 values ('XYZ');
I have also altered table TEST with the intention that next time when a new partition gets created, this new sub-partition is included in the table automatically:
ALTER TABLE TEST SET SUBPARTITION TEMPLATE (SUBPARTITION SP3 VALUES('XYZ'));
However, this last part is not working as expected. New partitions are not getting created when I am trying to insert data containing XYZ sub partition value. New partitions are getting created only when sub-partition values ABC/DEF are inserted.
What am I doing wrong?

You can't add a new subpartition to a template.
As the documetation states:
You can modify a subpartition template of a composite partitioned table by replacing it with a new subpartition template.
You have to define a new template consisting of the old and new subpartitions.
This will be valid for the partitions not yet created, for existing partition you have to add the subpartitions manually.
Example - after creation of your table you get one partition with two subpartitions
select PARTITION_NAME, SUBPARTITION_NAME,HIGH_VALUE
from user_tab_subpartitions where table_name = 'TEST';
PARTITION_NAME SUBPARTITION_NAME HIGH_VALUE
-------------- ----------------- ----------
TEST_Y2018 TEST_Y2018M7D1_ 'ABC'
TEST_Y2018 TEST_Y2018M7D1_ 'DEF'
Inserting a row adds an other partition with the same two subpartitions:
insert into test (UNIQUE_ID,LOGIN_INTFID,LOGIN_SEQNO,LOGIN_DATE)
values(1,'ABC',1,DATE'2018-08-02');
PARTITION_NAME SUBPARTITION_NAME HIGH_VALUE
-------------- -------------------- ----------
TEST_Y2018 TEST_Y2018M7D1_SP1 'ABC'
TEST_Y2018 TEST_Y2018M7D1_SP2 'DEF'
SYS_P14654 SYS_SUBP14652 'ABC'
SYS_P14654 SYS_SUBP14653 'DEF'
Now you change the subpartition template - by defining all the new subspartitions
ALTER TABLE TEST SET SUBPARTITION TEMPLATE (
SUBPARTITION SP1 VALUES ('ABC'),
SUBPARTITION SP2 VALUES ('DEF'),
SUBPARTITION SP3 VALUES('XYZ'));
and adds an other row
insert into test (UNIQUE_ID,LOGIN_INTFID,LOGIN_SEQNO,LOGIN_DATE)
values(1,'ABC',1,DATE'2018-09-02');
The new partition has now as expected three subpartitions
PARTITION_NAME SUBPARTITION_NAME HIGH_VALUE
-------------- -------------------- ----------
TEST_Y2018 TEST_Y2018M7D1_SP1 'ABC'
TEST_Y2018 TEST_Y2018M7D1_SP2 'DEF'
SYS_P14654 SYS_SUBP14652 'ABC'
SYS_P14654 SYS_SUBP14653 'DEF'
SYS_P14658 SYS_SUBP14655 'ABC'
SYS_P14658 SYS_SUBP14656 'DEF'
SYS_P14658 SYS_SUBP14657 'XYZ'

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;

Oracle: Range-Range Interval Partition/Subpartition

I need to create a range-partitioned table:
i.e.
create table table1(item_id number(22), sys_entry_date timestamp default sysdate)
partition by range(sys_entry_date) interval(NUMTOYMINTERVAL(1,'YEAR'))
(partition p01 values less than (to_date('31-DEC-2016','DD-MON-YYYY')));
A few insert for demo purpose:
---Should lie in the main partition's main subpartition (crnt_part) since the it's part of the latest records received.
insert into table1 values(1, sysdatetime);
---Should lie in the main partition's subpartition of default section (prev_part) since the it's 2 days older
insert into table1 values(1, sysdatetime-3);
insert into table1 values(1, sysdatetime-4);
---Would help us identify the yearly partitions (suggestive)
insert into table1 values(2, sysdatetime-1500);
insert into table1 values(3, sysdatetime-1200);
insert into table1 values(4, sysdatetime-800);
insert into table1 values(1, sysdatetime-400);
I want to achieve following through it:
An Yearly partition;
Within the yearly, a sub-partition based on sys_entry_date which
2.a. latest 2 days held in crnt_part
2.b. remaining held in default partition, maybe in prev_part
Appreciate if someone could help in this particular context.

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;

Oracle Forced dropping of Foreign Key during purge on Interval Partitioned Tables

We have several tables which are interval partitioned by day. While working on a purge job to drop a day of partitioning, our DBA has informed us that we will have to drop all foreign keys to any table, before performing the purge. This seems like an unnecessary step, and thus, I am turning to the wisdom of stackoverflow.
parentTable childTable
ID (PK) ID (PK)(FK)
date (PK) date (PK)(FK)
otherKey(PK)
parentTable childTable
ID date ID date otherKey
1 10/23 1 10/23 a
2 10/23 2 10/23 a
3 10/23 3 10/23 a
1 10/24 1 10/24 a
2 10/24 2 10/24 a
2 10/24 b
The question is, if we were to drop the 10/23 partition from childTable (first), then parentTable, would we have to drop/disable the Foreign Key constraint before the purge, and create it again afterwards? Is there a data situation where this would have to occur (maybe not as shown in my example above).
Seems that the DBA was right, test case scenario:
CREATE TABLE parent_tab (
id NUMBER PRIMARY KEY,
start_date DATE
)
PARTITION BY RANGE (start_date)
INTERVAL(NUMTODSINTERVAL(1, 'DAY'))
(
PARTITION pos_data_p2 VALUES LESS THAN (TO_DATE('01-01-2013', 'DD-MM-YYYY'))
);
INSERT INTO parent_tab VALUES (1, DATE '2012-01-01');
INSERT INTO parent_tab VALUES (2, DATE '2013-01-02');
INSERT INTO parent_tab VALUES (3, DATE '2013-01-03');
CREATE TABLE child_tab (
start_date DATE,
parent_tab_id NUMBER REFERENCES parent_tab(id)
)
PARTITION BY RANGE (start_date)
INTERVAL(NUMTODSINTERVAL(1, 'DAY'))
(
PARTITION pos_data_p2 VALUES LESS THAN (TO_DATE('01-01-2013', 'DD-MM-YYYY'))
);
INSERT INTO child_tab VALUES (DATE '2012-01-01', 1);
INSERT INTO child_tab VALUES (DATE '2013-01-02', 2);
INSERT INTO child_tab VALUES (DATE '2013-01-03', 3);
COMMIT;
SELECT table_name, partition_name FROM user_tab_partitions WHERE table_name IN ('PARENT_TAB', 'CHILD_TAB');
TABLE_NAME PARTITION_NAME
------------------------------ ------------------------------
CHILD_TAB POS_DATA_P2
CHILD_TAB SYS_P69
CHILD_TAB SYS_P70
PARENT_TAB POS_DATA_P2
PARENT_TAB SYS_P67
PARENT_TAB SYS_P68
ALTER TABLE child_tab DROP PARTITION SYS_P69;
> table CHILD_TAB altered.
ALTER TABLE parent_tab DROP PARTITION SYS_P67;
ALTER TABLE parent_tab DROP PARTITION SYS_P67
Error report:
SQL Error: ORA-02266 - "unique/primary keys in table referenced by enabled foreign keys"
*Cause: An attempt was made to truncate a table with unique or
primary keys referenced by foreign keys enabled in another table.
Other operations not allowed are dropping/truncating a partition of a
partitioned table or an ALTER TABLE EXCHANGE PARTITION.
*Action: Before performing the above operations the table, disable the
foreign key constraints in other tables. You can see what
constraints are referencing a table by issuing the following
command:
SELECT * FROM USER_CONSTRAINTS WHERE TABLE_NAME = "tabnam";
Edit
As the author pointed out, disabling the constraint works:
SELECT table_name, constraint_name, constraint_type FROM user_constraints WHERE table_name = 'CHILD_TAB';
TABLE_NAME CONSTRAINT_NAME CONSTRAINT_TYPE
------------------------------ ------------------------------ ---------------
CHILD_TAB SYS_C0033723 R
ALTER TABLE child_tab DISABLE CONSTRAINT SYS_C0033723;
ALTER TABLE parent_tab DROP PARTITION SYS_P67;
> table PARENT_TAB altered.
ALTER TABLE child_tab ENABLE CONSTRAINT SYS_C0033723;
Some day i will learn to manage there my code, So..
CREATE OR REPLACE PROCEDURE manage_constraints (i_status IN varchar2)
IS
CURSOR ref_cons
IS
SELECT constraint_name, table_name, status
FROM user_constraints
WHERE constraint_type in ( 'R') ; -- YOu can disable more constraints type
v_status VARCHAR2 (10);
v_sql VARCHAR2 (300);
BEGIN
FOR e_cons IN ref_cons
LOOP
v_sql :=
'ALTER TABLE '
|| e_cons.table_name
|| ' '
|| i_status
|| ' CONSTRAINT '
|| e_cons.constraint_name;
--DBMS_OUTPUT.put_line (v_sql);
EXECUTE IMMEDIATE v_sql;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
RAISE;
END;
--exec manage_constraints('DISABLE');
--exec manage_constraints('ENABLE');
There you can just DISABLE all you constraints and later ENABLE them.
select * from user_constraints
check constraint types... hope this helps.
Not a direct answer, but it sounds like what you really want here is reference partitioning, which would:
cascade partition maintenance operations
Allow more simple queries
Provide metadata that the two tables' partitions are logically associated
Possibly add a small overhead on the inserts into the child tables.
http://docs.oracle.com/cd/B28359_01/server.111/b32024/partition.htm#CACIHDII

Oracle: how to drop a subpartition of a specific partition

I am using an oracle 11 table with interval partitioning and list subpartitioning like this (simplified):
CREATE TABLE LOG
(
ID NUMBER(15, 0) NOT NULL PRIMARY KEY
, MSG_TIME DATE NOT NULL
, MSG_NR VARCHAR2(16 BYTE)
) PARTITION BY RANGE (MSG_TIME) INTERVAL (NUMTOYMINTERVAL (1,'MONTH'))
SUBPARTITION BY LIST (MSG_NR)
SUBPARTITION TEMPLATE (
SUBPARTITION login VALUES ('FOO')
, SUBPARTITION others VALUES (DEFAULT)
)
(PARTITION oldvalues VALUES LESS THAN (TO_DATE('01-01-2010','DD-MM-YYYY')));
How do I drop a specific subpartitition for a specific month without knowing the (system generated) name of the subpartition? There is a syntax "alter table ... drop subpartition for (subpartition_key_value , ...)" but I don't see a way to specify the month for which I am deleting the subpartition. The partition administration guide does not give any examples, either. 8-}
You can use the metadata tables to get the specific subpartition name:
SQL> insert into log values (1, sysdate, 'FOO');
1 row(s) inserted.
SQL> SELECT p.partition_name, s.subpartition_name, p.high_value, s.high_value
2 FROM user_tab_partitions p
3 JOIN
4 user_tab_subpartitions s
5 ON s.table_name = p.table_name
6 AND s.partition_name = p.partition_name
7 AND p.table_name = 'LOG';
PARTITION_NAME SUBPARTITION_NAME HIGH_VALUE HIGH_VALUE
--------------- ------------------ ------------ ----------
OLDVALUES OLDVALUES_OTHERS 2010-01-01 DEFAULT
OLDVALUES OLDVALUES_LOGIN 2010-01-01 'FOO'
SYS_P469754 SYS_SUBP469753 2012-10-01 DEFAULT
SYS_P469754 SYS_SUBP469752 2012-10-01 'FOO'
SQL> alter table log drop subpartition SYS_SUBP469752;
Table altered.
If you want to drop a partition dynamically, it can be tricky to find it with the ALL_TAB_SUBPARTITIONS view because the HIGH_VALUE column may not be simple to query. In that case you could use DBMS_ROWID to find the subpartition object_id of a given row:
SQL> insert into log values (4, sysdate, 'FOO');
1 row(s) inserted.
SQL> DECLARE
2 l_rowid_in ROWID;
3 l_rowid_type NUMBER;
4 l_object_number NUMBER;
5 l_relative_fno NUMBER;
6 l_block_number NUMBER;
7 l_row_number NUMBER;
8 BEGIN
9 SELECT rowid INTO l_rowid_in FROM log WHERE id = 4;
10 dbms_rowid.rowid_info(rowid_in =>l_rowid_in ,
11 rowid_type =>l_rowid_type ,
12 object_number =>l_object_number,
13 relative_fno =>l_relative_fno ,
14 block_number =>l_block_number ,
15 row_number =>l_row_number );
16 dbms_output.put_line('object_number ='||l_object_number);
17 END;
18 /
object_number =15838049
SQL> select object_name, subobject_name, object_type
2 from all_objects where object_id = '15838049';
OBJECT_NAME SUBOBJECT_NAME OBJECT_TYPE
--------------- --------------- ------------------
LOG SYS_SUBP469757 TABLE SUBPARTITION
As it turns out, the "subpartition for" syntax does indeed work, though that seems to be a secret Oracle does not want to tell you about. :-)
ALTER TABLE TB_LOG_MESSAGE DROP SUBPARTITION FOR
(TO_DATE('01.02.2010','DD.MM.YYYY'), 'FOO')
This deletes the subpartition that would contain MSG_TIME 2010/02/01 and MSG_NR FOO. (It is not necessary that there is an actual row with this exact MSG_TIME and MSG_NR. It throws an error if there is no such subpartition, though.)
Thanks for the post - it was very useful for me.
One observation though on the above script to identify the partition and delete it:
The object_id returned by dbms_rowid.rowid_info is not the object_id of the all_objects table. It is actually the data_object_id. It is observed that usually these ids match. However, after truncating the partitioned table several times, these ids diverged in my database. Hence it might be reasonable to instead use the data_object_id to find out the name of the partition:
select object_name, subobject_name, object_type
from all_objects where data_object_id = '15838049';
From the table description of ALL_OBJECTS:
OBJECT_ID Object number of the object
DATA_OBJECT_ID Object number of the segment which contains the object
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_rowid.htm
In the sample code provided in the above link, DBMS_ROWID.ROWID_OBJECT(row_id) is used instead to derive the same information that is given by dbms_rowid.rowid_info. However, the documentation around this sample mentions that it is a data object number from the ROWID.
Examples
This example returns the ROWID for a row in the EMP table, extracts
the data object number from the ROWID, using the ROWID_OBJECT function
in the DBMS_ROWID package, then displays the object number:
DECLARE object_no INTEGER; row_id ROWID; ... BEGIN
SELECT ROWID INTO row_id FROM emp
WHERE empno = 7499; object_no := DBMS_ROWID.ROWID_OBJECT(row_id); DBMS_OUTPUT.PUT_LINE('The obj. # is
'|| object_no); ...

Resources