ORACLE - Partitioning with changing values - oracle

Assuming following table:
create table INVOICE(
INVOICE_ID NUMBER
,INVOICE_SK NUMBER
,INVOICE_AMOUNT NUMBER
,INVOICE_TEXT VARCHAR2(4000 Char)
,B2B_FLAG NUMBER -- 0 or 1
,ACTIVE NUMBER(1) -- 0 or 1
)
PARTITION BY LIST (ACTIVE)
SUBPARTITION BY LIST (B2B_FLAG)
( PARTITION p_active_1 values (1)
( SUBPARTITION sp_b2b_flag_11 VALUES (1)
, SUBPARTITION sp_b2b_flag_10 VALUES (0)
)
,
PARTITION p_active_0 values (0)
( SUBPARTITION sp_b2b_flag_01 VALUES (1)
, SUBPARTITION sp_b2b_flag_00 VALUES (0)
)
)
For perfomance reasons the table should get a "Composite List-List" partitioning, see http://docs.oracle.com/cd/E18283_01/server.112/e16541/part_admin001.htm#i1006565.
The problematic point is, that the ACTIVE-Flag will change requently for a huge amount of records and sometimes also the B2B_FLAG. Will Oracle automatically recognize the records, for which the partitioning value has changed and move them to the appropriate partion or do I have to call some kind of maintenance function, in order to reorganize the partitions?

You need to enable row movement on the table or the update statement will fail with ORA-14402: updating partition key column would cause a partition change.
See the following testcase:
create table T_TESTPART
(
pk number(10),
part_key number(10)
)
partition by list (part_key) (
partition p01 values (1),
partition p02 values (2),
partition pdef values (default)
);
alter table T_TESTPART
add constraint pk_pk primary key (PK);
Now insert a row and try to update the partitioning value:
insert into t_testpart values (1,1);
update t_testpart set part_key = 2 where pk = 1;
You will now get the Error mentioned above.
If you enable row movement, the same statement will work and oracle will move the row to the other partition:
alter table t_testpart enable row movement;
update t_testpart set part_key = 2 where pk = 1;
I did not do any performance tests, but Oracle will probably delete the row from the first partition and insert it to the second partition. Consider this when using it in large scale.
In my own databases, I usually only use partitioning on columns that do not change.
Further reading:
http://www.dba-oracle.com/t_callan_oracle_row_movement.htm

Related

partition in oracle

CREATE TABLE temp_stud as select * from STUD_MAST
PARTITION BY RANGE(ADM_DT)
(
PARTITION temp_stud1 VALUES LESS THAN(TO_DATE('02/01/2000','MM/DD/YYYY')),
PARTITION temp_stud2 VALUES LESS THAN(TO_DATE('03/01/2000','MM/DD/YYYY')),
PARTITION temp_stud3 VALUES LESS THAN(TO_DATE('04/01/2000','MM/DD/YYYY')),
PARTITION temp_stud4 VALUES LESS THAN(TO_DATE('05/01/2000','MM/DD/YYYY'))
);
I am getting a missing left parenthesis error for above table creation can anyone tell me what is the issue in above creation
Note: ADM_DT is a date column with data type char(8) and storing format YYMMDD
Please use below SQL. The Creation of Partition has be part of Create table.
CREATE TABLE temp_stud
PARTITION BY RANGE(ADM_DT)
(
PARTITION temp_stud1 VALUES LESS THAN(TO_DATE('02/01/2000','MM/DD/YYYY')),
PARTITION temp_stud2 VALUES LESS THAN(TO_DATE('03/01/2000','MM/DD/YYYY')),
PARTITION temp_stud3 VALUES LESS THAN(TO_DATE('04/01/2000','MM/DD/YYYY')),
PARTITION temp_stud4 VALUES LESS THAN(TO_DATE('05/01/2000','MM/DD/YYYY'))
)
as select * from STUD_MAST;

Can I create a growing Interval partitioned table with a default/maxvalue partition?

Summary of the question: To Create table with partitions which are range partitioned. However records which do not know the range value should reside in a different (default) partition and be moved to the correct partition when the value is filled. The default partition would never be dropped while the other partitions would be dropped after a defined retention period via an script.
The whole story:
I have a table where the records have to be placed in a partition based on a date field. This is a growing table and after some time the data from these partitions can be purged. I used to create table with something like the snippet below.
This works fine because we knew the value of the date column based on which we partition (RDATE). However in our new project we do not know this when a record is inserted. The value would eventually be filled in during the course of the application processing.
My initial thought was to create MAXPARTITION (MAXVALUE) which would be a catch-all partition for records which do not have the date filled and enable ROW MOVEMENTS so that when the date is filled it moves into an appropriate partition. However I think it is not possible to have both MAXVALUE partition and interval partitioning together. Is that right?
Also Is there a better way to do this?
PARTITION BY RANGE ("RDATE") INTERVAL (NUMTODSINTERVAL (1,'DAY'))
SUBPARTITION BY HASH ("RKEY")
SUBPARTITION TEMPLATE (
SUBPARTITION "SP01",
SUBPARTITION "SP02",
SUBPARTITION "SP03",
SUBPARTITION "SP04",
SUBPARTITION "SP05",
SUBPARTITION "SP06",
SUBPARTITION "SP07",
SUBPARTITION "SP08",
SUBPARTITION "SP09",
SUBPARTITION "SP10",
SUBPARTITION "SP11",
SUBPARTITION "SP12",
SUBPARTITION "SP13",
SUBPARTITION "SP14",
SUBPARTITION "SP15",
SUBPARTITION "SP16" )
(PARTITION "INITIALPARTITION" VALUES LESS THAN (TO_DATE(' 2016-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'))
I expect a table with default and range partitions and records to move to the range partitions from the default when a column is filled.
The column you use as partition key cannot be NULL but you can use a workaround like this:
CREATE TABLE ... (
...
RDATE DATE,
PARTITION_KEY DATE GENERATED ALWAYS AS (COALESCE(RDATE, DATE '1969-12-31'))
)
PARTITION BY RANGE (PARTITION_KEY) INTERVAL (NUMTODSINTERVAL (1,'DAY'))
...
(PARTITION INITIAL_PARTITION VALUES LESS THAN (DATE '1970-01-01'))
ENABLE ROW MOVEMENT;
If you insert a record with RDATE = NULL then it will be inserted into partition INITIAL_PARTITION. For the initial data (e.g. 1970-01-01) you must select a values whicc will never fall into the "real" date values. You could also use a date in far future, e.g.
CREATE TABLE ... (
...
RDATE DATE,
PARTITION_KEY DATE GENERATED ALWAYS AS (COALESCE(RDATE, DATE '2999-12-31'))
)
PARTITION BY RANGE (PARTITION_KEY) INTERVAL (NUMTODSINTERVAL (1,'DAY'))
...
(PARTITION INITIAL_PARTITION VALUES LESS THAN (DATE '2019-04-01'))
ENABLE ROW MOVEMENT;
-- Create DEFAULT_PARTITION
INSERT INTO ... (RDATE) VALUES (NULL);
ROLLBACK;
ALTER TABLE ... RENAME PARTITION FOR (TIMESTAMP '2999-12-31 00:00:00') TO DEFAULT_PARTITION;

SQL Error: ORA-14006: invalid partition name

I am trying to partition an existing table in Oracle 12C R1 using below SQL statement.
ALTER TABLE TABLE_NAME MODIFY
PARTITION BY RANGE (DATE_COLUMN_NAME)
INTERVAL (NUMTOYMINTERVAL(1,'MONTH'))
(
PARTITION part_01 VALUES LESS THAN (TO_DATE('01-SEP-2017', 'DD-MON-RRRR'))
) ONLINE;
Getting error:
Error report -
SQL Error: ORA-14006: invalid partition name
14006. 00000 - "invalid partition name"
*Cause: a partition name of the form <identifier> is
expected but not present.
*Action: enter an appropriate partition name.
Partition needs to be done on the basis of data datatype column with the interval of one month.
Min value of Date time column in the Table is 01-SEP-2017.
You can't partition an existing table like that. That statement is modifying the partition that hasn't been created yet. I don't know the automatic way to do this operation and I am not sure that you can do it.
Although I have done this thing many times but with manual steps. Do the following if you can't find an automated solution:
Create a partitioned table named table_name_part with your clauses and all your preferences.
Insert into this partitioned table all rows from original table. Pay attention to compression. If you have some compression on table (Basic or HCC) you have to use + APPEND hint.
Create on partitioned table your constrains and indexes from the original table.
Rename the tables and drop the original table. Do not drop it until you make some counts on them.
I saw that your table has the option to auto-create partition if it does not exists. (NUMTOYMINTERVAL(1,'MONTH')) So you have to create your table with first partition only. I assume that you have here a lot of read-only data, so you won't have any problem with consistency instead of last month. Probably there is some read-write data so there you have to be more careful with the moment when you want to insert data in new table and switch tables.
Hope to help you. As far as I know there might be a package named DBMS_REDEFINITION that can help you with an automated version of my steps. If you need more details or need some help on my method, please don't hesitate.
UPDATE:
From Oracle 12c R2 you can convert a table from an unpartitioned to a partitioned one with your method. Find a link below. Now this is a challenge for me and I am trying to convert, but I think there is no way to make this conversion online in 12c R1.
In previous releases you could partition a non-partitioned table using
EXCHANGE PARTITION or DBMS_REDEFINITION in an "almost online" manner,
but both methods require multiple steps. Oracle Database 12c Release 2
makes it easier than ever to convert a non-partitioned table to a
partitioned table, requiring only a single command and no downtime.
https://oracle-base.com/articles/12c/online-conversion-of-a-non-partitioned-table-to-a-partitioned-table-12cr2
Solution
I found a solution for you. Here you will have all of my steps that I run to convert table online. :)
1. Create regular table and populate it.
CREATE TABLE SCOTT.tab_unpartitioned
(
id NUMBER,
description VARCHAR2 ( 50 ),
created_date DATE
);
INSERT INTO tab_unpartitioned
SELECT LEVEL,
'Description for ' || LEVEL,
ADD_MONTHS ( TO_DATE ( '01-JAN-2017', 'DD-MON-YYYY' ),
-TRUNC ( DBMS_RANDOM.VALUE ( 1, 4 ) - 1 ) * 12 )
FROM DUAL
CONNECT BY LEVEL <= 10000;
COMMIT;
2. Create partitioned table with same structure.
--If you are on 11g create table with CREATE TABLE command but with different name. ex: tab_partitioned
CREATE TABLE SCOTT.tab_partitioned
(
id NUMBER,
description VARCHAR2 ( 50 ),
created_date DATE
)
PARTITION BY RANGE (created_date)
INTERVAL( NUMTOYMINTERVAL(1,'YEAR'))
(PARTITION part_2015 VALUES LESS THAN (TO_DATE ( '01-JAN-2016', 'DD-MON-YYYY' )),
PARTITION part_2016 VALUES LESS THAN (TO_DATE ( '01-JAN-2017', 'DD-MON-YYYY' )),
PARTITION part_2017 VALUES LESS THAN (TO_DATE ( '01-JAN-2018', 'DD-MON-YYYY' )));
--this is an alter command that works only in 12c.
ALTER TABLE tab_partitioned
MODIFY
PARTITION BY RANGE (created_date)
(PARTITION part_2015 VALUES LESS THAN (TO_DATE ( '01-JAN-2016', 'DD-MON-YYYY' )),
PARTITION part_2016 VALUES LESS THAN (TO_DATE ( '01-JAN-2017', 'DD-MON-YYYY' )),
PARTITION part_2017 VALUES LESS THAN (TO_DATE ( '01-JAN-2018', 'DD-MON-YYYY' )));
3. Check if the table can be converted. This procedure should run without any error.
Prerequisites: table should have an UNIQUE INDEX and a Primary Key constraint.
EXEC DBMS_REDEFINITION.CAN_REDEF_TABLE('SCOTT','TAB_UNPARTITIONED');
4. Run the following steps like I have done.
EXEC DBMS_REDEFINITION.START_REDEF_TABLE('SCOTT','TAB_UNPARTITIONED','TAB_PARTITIONED');
var num_errors varchar2(2000);
EXEC DBMS_REDEFINITION.COPY_TABLE_DEPENDENTS('SCOTT','TAB_UNPARTITIONED','TAB_PARTITIONED', 1,TRUE,TRUE,TRUE,FALSE,:NUM_ERRORS,FALSE);
SQL> PRINT NUM_ERRORS -- Should return 0
EXEC DBMS_REDEFINITION.SYNC_INTERIM_TABLE('SCOTT','TAB_UNPARTITIONED','TAB_PARTITIONED');
EXEC DBMS_REDEFINITION.FINISH_REDEF_TABLE('SCOTT','TAB_UNPARTITIONED','TAB_PARTITIONED');
At the end of the script you will see that the original table is partitioned.
Try Oracle Live SQL I used to use Oracle 11g EE and got the same error message. So I tried Oracle live SQL and it perfectly worked. It has very simple and easy to understand interface,
For example, I'm creating a sales table and inserting some dummy data and partition it using range partitioning method,
CREATE TABLE sales
(product VARCHAR(300),
country VARCHAR(100),
sales_year DATE);
INSERT INTO sales (product, country, sales_year )
VALUES ('Computer','Kazakhstan',TO_DATE('01/02/2018','DD/MM/YYYY'));
INSERT INTO sales (product, country, sales_year )
VALUES ('Mobile Phone','China',TO_DATE('23/12/2019','DD/MM/YYYY'));
INSERT INTO sales (product, country, sales_year )
VALUES ('Camara','USA',TO_DATE('20/11/2020','DD/MM/YYYY'));
INSERT INTO sales (product, country, sales_year )
VALUES ('Watch','Bangladesh',TO_DATE('19/03/2020','DD/MM/YYYY'));
INSERT INTO sales (product, country, sales_year )
VALUES ('Cake','Sri Lanka',TO_DATE('13/04/2021','DD/MM/YYYY'));
ALTER TABLE sales MODIFY
PARTITION BY RANGE(sales_year)
INTERVAL(INTERVAL '1' YEAR)
(
PARTITION sales_2018 VALUES LESS THAN(TO_DATE('01/01/2019','DD/MM/YYYY')),
PARTITION sales_2019 VALUES LESS THAN(TO_DATE('01/01/2020','DD/MM/YYYY')),
PARTITION sales_2020 VALUES LESS THAN(TO_DATE('01/01/2021','DD/MM/YYYY')),
PARTITION sales_2021 VALUES LESS THAN(TO_DATE('01/01/2022','DD/MM/YYYY'))
)ONLINE;
Finally, I can write SELECT query for partitions to confirm that the partitions are created successfully.
SELECT *
FROM sales PARTITION (sales_2020);
And it gives the expected output,

How to do partitioning and sub partitioning with exchange partition in Oracle?

I am facing issue with exchange partitioning and sub partitioning in Oracle.
Please explain with example.
Please find following example
CREATE TABLE DEPARTMENT
( DEPT_ID NUMBER(30,0) NOT NULL ENABLE,
IS_ACTIVE VARCHAR2(1 BYTE) NOT NULL ,
BUSINESS_DATE DATE NOT NULL ENABLE,
COMPANY_CODE VARCHAR2(4) DEFAULT 'C1' NOT NULL ,
CONSTRAINT PK_DEPARTMENT PRIMARY KEY (BUSINESS_DATE, COMPANY_CODE, DEPT_ID) ENABLE
)
PARTITION BY RANGE
(
BUSINESS_DATE
)
INTERVAL (
NUMTODSINTERVAL(1,'DAY')
)
SUBPARTITION BY LIST(COMPANY_CODE)
SUBPARTITION TEMPLATE
(
SUBPARTITION CMN_01 VALUES ('C1'),
SUBPARTITION CMN_02 VALUES ('C2'),
SUBPARTITION CMN_03 VALUES ('C3')
)
(
PARTITION DEFAULT_PART VALUES LESS THAN ('01-JAN-2012')
)
;
CREATE INDEX IDX1_DEPARTMENT ON DEPARTMENT (DEPT_ID, IS_ACTIVE);
CREATE TABLE TEMP_DEPARTMENT AS SELECT * FROM DEPARTMENT WHERE 1=2;
CREATE TABLE EMPLOYEE
( ID NUMBER(30,0) NOT NULL ENABLE,
DEPT_ID NUMBER(30,0) NOT NULL ENABLE,
BUSINESS_DATE DATE NOT NULL ENABLE,
COMPANY_CODE VARCHAR2(4) DEFAULT 'C1' NOT NULL,
CONSTRAINT PK_EMPLOYEE PRIMARY KEY (BUSINESS_DATE,COMPANY_CODE, ID) ENABLE,
CONSTRAINT FK1_EMPLOYEE_DEPT FOREIGN KEY (BUSINESS_DATE, COMPANY_CODE, DEPT_ID)
REFERENCES DEPARTMENT (BUSINESS_DATE, COMPANY_CODE, DEPT_ID) ENABLE NOVALIDATE
)
PARTITION BY RANGE
(
BUSINESS_DATE
)
INTERVAL (
NUMTODSINTERVAL(1,'DAY')
)
SUBPARTITION BY LIST(COMPANY_CODE)
SUBPARTITION TEMPLATE
(
SUBPARTITION CMN_01 VALUES ('C1'),
SUBPARTITION CMN_02 VALUES ('C2'),
SUBPARTITION CMN_03 VALUES ('C3')
)
(
PARTITION DEFAULT_PART VALUES LESS THAN ('01-JAN-2012')
);
Insert few thousands records into DEPARTMENT and EMPLOYEE tables for few partitions and their all sub partitions
select * from DEPARTMENT where BUSINESS_DATE='19-JAN-15' and COMPANY_CODE='C1';
select* from EMPLOYEE;
select * from all_tab_partitions where TABLE_OWNER='TEST' and
table_name='DEPARTMENT';
select * from ALL_TAB_SUBPARTITIONS where TABLE_OWNER='TEST' and
table_name='DEPARTMENT';
select * from all_tab_partitions where TABLE_OWNER='TEST' and
table_name='EMPLOYEE';
select * from ALL_TAB_SUBPARTITIONS where TABLE_OWNER='TEST' and
table_name='EMPLOYEE';
select * from TEMP_DEPARTMENT;
Exchange and Truncate sub partitions
1)Exchange sub partitioningALTER TABLE DEPARTMENT EXCHANGE SUBPARTITION SYS_SUBP7356 WITH TABLE TEMP_DEPARTMENT WITHOUT VALIDATION UPDATE GLOBAL INDEXES;
2) Truncate temp exchange table truncate table TEMP_DEPARTMENT;
3) Truncate sub partitioning (sub partition name from ALL_TAB_SUBPARTITIONS table) with global index update `ALTER TABLE DEPARTMENT TRUNCATE SUBPARTITION SYS_SUBP7356 DROP STORAGE UPDATE GLOBAL INDEXES;
Here the above 1 to 3 steps are executed for three sub-partitions
4) Get this partition name from all_tab_partitions table with global index update ALTER TABLE DEPARTMENT DROP PARTITION SYS_P7359 UPDATE GLOBAL INDEXES;
5) Truncate sub partitioning (sub partition name from ALL_TAB_SUBPARTITIONS table) with global index update ALTER TABLE EMPLOYEE TRUNCATE SUBPARTITION SYS_SUBP7360 DROP STORAGE UPDATE GLOBAL INDEXES;
Here the above 5 steps are executed for three sub-partitions
6) Get this partition name from all_tab_partitions table with global index update ALTER TABLE EMPLOYEE DROP PARTITION SYS_P7363 UPDATE GLOBAL INDEXES;
7) alter index PK_DEPARTMENT rebuild;
8) alter index IDX1_DEPARTMENT rebuild;
9) alter index PK_EMPLOYEE rebuild;
Steps 7 to 9 is require only when UPDATE GLOBAL INDEXES not used at the time of Altering Table for exchange, truncate and drop partition or sub partition
10)
begin
dbms_stats.gather_table_stats(ownname=>'TEST', tabname=>'DEPARTMENT', granularity=>'ALL' ,no_invalidate=>FALSE);
dbms_stats.gather_table_stats(ownname=>'TEST', tabname=>'EMPLOYEE', granularity=>'ALL' ,no_invalidate=>FALSE);
end;
UPDATE GLOBAL INDEXES is used at the time of Altering Table for exchange, truncate and drop partition or sub partition where global indexes are applied on that table then we can do DML operation on same table in parallel here.
UPDATE GLOBAL INDEXES is used to rebuild the indexes at same time with alter operation.
If UPDATE GLOBAL INDEXES is not used then DML operation is not allowed on same table in parallel. It will throw below ORA Exception for global indexes.
ERROR Message when perform insert operation after drop/exchange/truncate partition/sub partition from table:
SQL Error: ORA-01502: index 'TEST.PK_DEPARTMENT' or partition of such
index is in unusable state
01502. 00000 - "index '%s.%s' or partition of such index is in unusable state"
Cause: An attempt has been made to access an index or index partition that has been marked unusable by a direct load or by a DDL operation
Action: DROP the specified index, or REBUILD the specified index, or REBUILD the unusable index partition
If you don’t want to use UPDATE GLOBAL INDEXES and want parallel DML operation with purging then make indexes Local (must not contain primary key or unique constraint because it is default created global unique index). It’s on partition and sub partition and refers default table space.
With Local indexes you observed slow insertion on that table.

Extending existing partitioning

Below the simplified structure of a table:
create table customer(
incident_id number,
customer_id number,
customer_name varchar2(400),
sla_id number
failure_start_date date,
failure_end_date date,
churn_flag number, -- 0 or 1
active number, -- 0 or 1
constraint pk_incident_id primary key (incident_id))
PARTITION BY LIST (active)
SUBPARTITION BY LIST (churn_flag)
SUBPARTITION TEMPLATE
( SUBPARTITION sp_churn_flag_1 VALUES (1)
, SUBPARTITION sp_churn_flag_0 VALUES (0)
)
(PARTITION sp_active_1 values (1)
, PARTITION sp_active_0 VALUES (0)
)
,
ENABLE ROW MOVEMENT COMPRESS FOR QUERY LOW;
Now I need to add additonally to the existing Composite-List-Partition an Interval-Range-Partitioning, in order to partitionate the data by month (failure_starte_date - YYYYMM). The table contains data from 200701 up to now (201511). Failure_start_date < 2013 should be partitionied into one partition for older data. All newer months should have an dedicated partition, whereas partitions for upcoming months shall be created automatically.
How can this be integrating into the already existing partitoning?
You cannot do it exactly the way you want. Partitioning strategies are limited in two relevant ways: first, composite strategies can only have two levels (you need 3) and second, interval partitioning, when used in a composite strategy must be at the top level.
Here is the closest legal thing to what you want:
CREATE TABLE matt_customer
(
incident_id NUMBER,
customer_id NUMBER,
customer_name VARCHAR2 (400),
sla_id NUMBER,
failure_start_date DATE,
failure_end_date DATE,
churn_flag VARCHAR2 (1), -- 0 or 1
active VARCHAR2 (1), -- 0 or 1
active_churn_flags VARCHAR2 (2) GENERATED ALWAYS AS (active || churn_flag) VIRTUAL,
CONSTRAINT pk_incident_id PRIMARY KEY (incident_id)
)
PARTITION BY RANGE
(failure_start_date)
INTERVAL ( NUMTOYMINTERVAL (1, 'MONTH') )
SUBPARTITION BY LIST
(active_churn_flags)
SUBPARTITION TEMPLATE (
SUBPARTITION sp_ac_00 VALUES ('00'),
SUBPARTITION sp_ac_01 VALUES ('01'),
SUBPARTITION sp_ac_10 VALUES ('10'),
SUBPARTITION sp_ac_11 VALUES ('11'))
(PARTITION customer_old VALUES LESS THAN (TO_DATE ('01-JAN-2013', 'DD-MON-YYYY')))
ENABLE ROW MOVEMENT
--COMPRESS FOR QUERY LOW;
;
This uses interval-list partitioning, and uses a virtual column to combine your active and churn_flag columns into one (I turned those columns into VARCHAR2(1) for simplicity.
To make use of partition pruning, your queries would need to be modified to select active_churn_flags = '01' for example, instead of specifying values for active and churn_flag independently.

Resources