Automatic list partitioning - oracle

I'm using Oracle 11g.
I have large table that I want to add partitions. My partition (and subpartition) key column is of VARCHAR2 type.
I know that there is an INTERVAL RANGE PARTITION that can be auto-created but I need to do the same thing with list partition. Is there a way to do that with VARCHAR2 type (which doesn't represent any date).
Example:
create table t1 (name varchar2(30), company varchar2(10), value number);
I want that for every new name there will be a new partition. Is there a way to do that without writing specific PL/SQL code that will check if a new partition is needed and will do the creation? Maybe with some pattern or something...

Automatic list partitioning was added in 12.2. So you need to upgrade to do this.
In the meantime, you can create a catch-all, default partition. Then split new values out as needed:
create table t (
c1 int
) partition by list ( c1 ) (
partition pdef values ( default )
);
select partition_name, high_value
from user_tab_partitions
where table_name = 'T';
PARTITION_NAME HIGH_VALUE
PDEF default
insert into t values ( 1 );
insert into t values ( 2 );
alter table t
split partition pdef
values ( 1 ) into (
partition p1, partition pdef
);
alter table t
split partition pdef
values ( 2 ) into (
partition p2, partition pdef
);
select partition_name, high_value
from user_tab_partitions
where table_name = 'T';
PARTITION_NAME HIGH_VALUE
P1 1
P2 2
PDEF default
When you get to 12.2, you can switch it to auto-list. But you need to drop the default partition first!
alter table t set partitioning automatic;
ORA-14852: SET [SUB]PARTITIONING AUTOMATIC is not legal on this table.
alter table t drop partition pdef;
alter table t set partitioning automatic;
insert into t values ( 3 );
select partition_name, high_value
from user_tab_partitions
where table_name = 'T';
PARTITION_NAME HIGH_VALUE
P1 1
P2 2
SYS_P5201 3

Related

ORA-14074: partition bound must collate higher than that of the last partition 14074

I am getting this error.
ORA-14074: partition bound must collate higher than that of the last
partition
14074.
I am unable to solve this error.
I am trying to adding partition to the table.
The limit value for the partition you're adding is lower that the highest limit already in the table:
create table t (
c1 int
) partition by range ( c1 ) (
partition p1 values less than ( 9 ),
partition p2 values less than ( 99 )
);
alter table t
add partition p3 values less than ( 0 );
ORA-14074: partition bound must collate higher than that of the last partition
0 < 99 => you can't add the partition. If you want a partition with this upper bound you'll need to split an existing partition.
You can find the current highest value by querying *tab_partitions and getting the high_value for the partition with the max partition_position
with rws as (
select table_name, partition_position, high_value,
row_number () over (
partition by table_name
order by partition_position desc
) rn
from user_tab_partitions
)
select table_name, high_value
from rws
where rn = 1;
TABLE_NAME HIGH_VALUE
---------- ----------
T 99

Using case for indexes in oracle

I am having the below index defined on one of the tables in Oracle.
CASE WHEN C IS NOT NULL THEN A||','||B||','||C END
Initially, I thought the combination of A,B,C separated by commas should be unique when C is not null, but seems like I'm wrong. I did a bit of a research on this as well but could not find a good explanation.
Could someone kindly help me with this?
Thanks in Advance!!
Yes, it can be unique.
But don't forget - uniqueness is an additional cost.
There will be little benefit from such an index, since there will be duplicates of rows for fields A & B when C is NULL.
I suppose it might be worth changing the index function as :
a||','||b||case when c is not null then ','||c end
This will control duplicates if only columns A and B are used.
Of course, unless your table has other unique indexes using columns A and B.
For demonstration - a short example, with a unique functional index.
N.B. Please, when describing the problem /task, provide a description of the relevant objects (tables,indexes, procedures...etc.).)
For a test,try:
--ddl
drop table test_tab;
create table test_tab
( a varchar2(3) not null,
b varchar2(3) not null,
c varchar2(3),
idx varchar2(12) generated always as (case when c is not null then a||','||b||','||c end) virtual
);
--
create unique index test_tab_i1 on test_tab(idx);
--dml
insert into test_tab(a,b,c) values('a1','b1','c1');
insert into test_tab(a,b,c) values('a1','b2','c2');
insert into test_tab(a,b,c) values('a1','b1',null);
commit;
select * from test_tab;
ANALYZE TABLE test_tab COMPUTE STATISTICS FOR ALL INDEXES;
--get data of idx
select INDEX_NAME,NUM_ROWS,DISTINCT_KEYS from user_indexes i where i.table_name=upper('test_tab');
--dubt
insert into test_tab(a,b,c) values('a1','b1',null);
commit;
--
ANALYZE TABLE test_tab COMPUTE STATISTICS FOR ALL INDEXES;
--pass2
select * from test_tab;
--get data of idx
select INDEX_NAME,NUM_ROWS,DISTINCT_KEYS from user_indexes i where i.table_name=upper('test_tab');
--pass3
--dubt
insert into test_tab(a,b,c) values('a1','b1','c1');
Result:
Table created.
Index created.
1 row(s) inserted.
1 row(s) inserted.
1 row(s) inserted.
Statement processed.
Result Set 31
A B C IDX
a1 b1 c1 a1,b1,c1
a1 b2 c2 a1,b2,c2
a1 b1 - -
Download CSV
3 rows selected.
Statement processed.
Result Set 32
INDEX_NAME NUM_ROWS DISTINCT_KEYS
TEST_TAB_I1 2 2
Download CSV
1 row(s) inserted.
Statement processed.
Statement processed.
Result Set 33
A B C IDX
a1 b1 c1 a1,b1,c1
a1 b2 c2 a1,b2,c2
a1 b1 - -
a1 b1 - -
Download CSV
4 rows selected.
Result Set 34
INDEX_NAME NUM_ROWS DISTINCT_KEYS
TEST_TAB_I1 2 2
Download CSV
ORA-00001: unique constraint (SQL_XKMHFTSRXNSKMGVEDOYSVXZGL.TEST_TAB_I1) violated ORA-06512: at "SYS.DBMS_SQL", line 1721
Even if your question is vague, it is interesting. I will try to provide you a possible explanation.
A||','||B is unique if you can guarantee that for 2 distinct values of (A,B), the function A||','||B is not returning the same value.
You need to guarantee that TO_CHAR(A) ||','|| TO_CHAR(B) is unique.
I am considering that A and B are NOT NULL.
A||','||B is unique if A and B are VARCHAR but it is not the case if you have DATE or TIMESTAMP datatypes. This is not TRUE because TO_CHAR function has default parameters that depend on nls_parameters from your session.
Testcase - executing the same queries but changing the nls_date_format and try to create a unique index
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10));
alter session set nls_date_format= 'dd/mm/yyyy hh24:mi:ss';
drop index test;
truncate table test;
insert into test(a,b,c) values(sysdate+(1/60),'1','c1');
insert into test(a,b,c) values(sysdate+(1/160),'1','c1');
insert into test(a,b,c) values(sysdate+(1/80),'2','c2');
insert into test(a,b,c) values(sysdate+(1/180),'2','c2');
commit;
create unique index test on test (case when c is not null then a||','||b||','||c end);
select (case when c is not null then a||','||b||','||c end) valfn from test order by B;
alter session set nls_date_format= 'dd/mm/yyyy';
truncate table test;
drop index test;
insert into test(a,b,c) values(sysdate+(1/60),'1','c1');
insert into test(a,b,c) values(sysdate+(1/160),'1','c1');
insert into test(a,b,c) values(sysdate+(1/80),'2','c2');
insert into test(a,b,c) values(sysdate+(1/180),'2','c2');
create unique index test on test (case when c is not null then a||','||b||','||c end);
select (case when c is not null then a||','||b||','||c end) valfn from test order by B;
output with nls_date_format= 'dd/mm/yyyy hh24:mi:ss'
Index TEST created.
VALFN
------------------------
15/05/2021 08:40:35,1,c1
15/05/2021 08:25:35,1,c1
15/05/2021 08:34:35,2,c2
15/05/2021 08:24:35,2,c2
output with nls_date_format= 'dd/mm/yyyy'
ORA-01452: cannot CREATE UNIQUE INDEX; duplicate keys found
VALFN
---------------
15/05/2021,1,c1
15/05/2021,1,c1
15/05/2021,2,c2
15/05/2021,2,c2
Then we can see what happened when the indexes were created. We will query the index definition to explain this behavior.
output with nls_date_format= 'dd/mm/yyyy hh24:mi:ss'
alter session set nls_date_format= 'dd/mm/yyyy hh24:mi:ss';
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10));
create unique index test on test (case when c is not null then a||','||b||','||c end);
select index_name, column_expression from all_ind_expressions where table_name = 'TEST'
INDEX_NAME COLUMN_EXPRESSION
---------- ---------------------------------------------------------------------------------------------
TEST CASE WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy hh24:mi:ss')||','||"B"||','||"C" END
output with nls_date_format= 'dd/mm/yyyy'
alter session set nls_date_format= 'dd/mm/yyyy';
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10));
create unique index test on test (case when c is not null then a||','||b||','||c end);
select index_name, column_expression from all_ind_expressions where table_name = 'TEST'
INDEX_NAME COLUMN_EXPRESSION
---------- ----------------------------------------------------------------------------------
TEST CASE WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy')||','||"B"||','||"C" END
As you can see, the index is not the same because it depends on your nls_date_format when the index was created. I always suggest to validate what was the expression used by Oracle when creating the object.
This is the same when creating virtual columns
alter session set nls_date_format= 'dd/mm/yyyy hh24:mi:ss';
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10)
,idx varchar2(50) generated always as (case when c is not null then a||','||b||','||c end) virtual
);
select DATA_DEFAULT from all_tab_cols where table_name = 'TEST' and column_name = 'IDX';
DATA_DEFAULT
---------------------------------------------------------------------------------------------
CASE WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy hh24:mi:ss')||','||"B"||','||"C" END
alter session set nls_date_format= 'dd/mm/yyyy';
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10)
,idx varchar2(50) generated always as (case when c is not null then a||','||b||','||c end) virtual
);
select DATA_DEFAULT from all_tab_cols where table_name = 'TEST' and column_name = 'IDX';
DATA_DEFAULT
----------------------------------------------------------------------------------
CASE WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy')||','||"B"||','||"C" END
Now, you can add C to this equation and validate why in your case : CASE WHEN C IS NOT NULL THEN A||','||B||','||C END is not unique
Most of the time, by default the nls_date_format is not including the time.

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); ...

How can I import a partition from one table into another in Oracle?

I would like to know if the following steps are possible and how fast this is:
Create a partition named part1 in Table A
Drop partition part1 in Table B
Import the Table A partition part1 into Table B
Can you provide me with an example if it is possible indeed? Or any resources I can look at?
Note that the tables would have the exact same structure.
You can do something similar with the ALTER TABLE ... EXCHANGE PARTITION command. This would exchange a single partition with a table that has the same structure.
A little example:
/* Partitionned Table Creation */
SQL> CREATE TABLE table_a (
2 ID NUMBER PRIMARY KEY,
3 DATA VARCHAR2(200)
4 )
5 PARTITION BY RANGE (ID) (
6 PARTITION part100 VALUES LESS THAN (100),
7 PARTITION part200 VALUES LESS THAN (200)
8 );
Table created
/* Swap table creation */
SQL> CREATE TABLE swap_table (
2 ID NUMBER PRIMARY KEY,
3 DATA VARCHAR2(200)
4 );
Table created
SQL> INSERT INTO swap_table SELECT ROWNUM, 'a' FROM dual CONNECT BY LEVEL <= 99;
99 rows inserted
SQL> select count(*) from table_a partition (part100);
COUNT(*)
----------
0
This will exchange the partition part100 with the transition table swap_table:
SQL> ALTER TABLE table_a EXCHANGE PARTITION part100 WITH TABLE swap_table;
Table altered
SQL> select count(*) from table_a partition (part100);
COUNT(*)
----------
99

Resources