Yearly Partitioning a Integer column which stores Date as ID - oracle

I have a table which has a DATE_ID column (Integer data type). This column basically stores dates in ID format. Input data:
DATE_ID
19961210
19991001
20051212
20090108
I need to partition this table (YEARly) based on date_id column. Please note that this is an existing process and we are migrating our tables from database to another. These column datatypes cannot be changed as they will be referred by downstream process in that fashion only.
I tried below interval partitioning but somehow didn't work. Can someone pls help?
CREATE TABLE test (date_id INT NOT NULL, text VARCHAR2(500))
PARTITION BY RANGE (DATE_ID) INTERVAL (365)
(
PARTITION P0 VALUES LESS THAN (19961231),
PARTITION P1 VALUES LESS THAN (19991231),
PARTITION P2 VALUES LESS THAN (20091231)
);

I got the solution:
CREATE TABLE test (
DATE_ID INTEGER
)
partition BY RANGE (DATE_ID )
interval(10000)
(partition p0 values less than(19961231),
partition p1 values less than(19991231),
partition p1 values less than(20091231)
);

You can add a virtual column in order to add an interval partition along with a decent date type column, and able to cross-check the eligibility of the existing integer values as been considered as date values such as
CREATE TABLE test(
date_id INT NOT NULL,
text VARCHAR2(500)
);
ALTER TABLE test ADD (dt AS ( TO_DATE(date_id,'yyyymmdd') ));
ALTER TABLE test MODIFY
PARTITION BY RANGE(dt) INTERVAL (INTERVAL '1' YEAR)
(
PARTITION P1996 VALUES LESS THAN (date'1997-01-01'),
PARTITION P1999 VALUES LESS THAN (date'2000-01-01'),
PARTITION P2009 VALUES LESS THAN (date'2010-01-01')
);
if your aim is to add yearly partition as the title implies for the year range from 1996 to 2006, then rather you can prefer using a dynamic method in order to generate the desired code block such as
DECLARE
v_ddl CLOB;
BEGIN
FOR year in 1996..2006
LOOP
v_ddl := v_ddl||' PARTITION p'||year||' VALUES LESS THAN (date'''||TO_CHAR(year+1)||'-01-01''),'||CHR(13);
END LOOP;
v_ddl := 'ALTER TABLE test MODIFY PARTITION BY RANGE(dt) INTERVAL (INTERVAL ''1'' YEAR)'||CHR(13)||'('||CHR(13)||RTRIM(v_ddl,','||CHR(13));
v_ddl := v_ddl||CHR(13)||')';
DBMS_OUTPUT.PUT_LINE(v_ddl);
EXECUTE IMMEDIATE v_ddl;
END;
/

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;

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,

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.

ORACLE - Partitioning with changing values

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

dynamically creating the partition name as sysdate?

How to give the name to the partition dynamically. I have a requirement in which i'm creating the interval partitioning on monthly basis. I want to give '1-jan-2014' , '1-Feb-2014' as partition names? Please suggest the approach?
I see some problem here because interval partitioning has system generated partition names...
So you have to change this generated names after creation, this problem is resolved here:
https://dba.stackexchange.com/questions/33225/rename-interval-partitioning-system-generated-partition-names
or here
http://bobjankovsky.org/show.php?seq=90
You could make a job which will periodically change the partition names.
If you're adding a partition to an existing table you'd need to do it dynamically. And since you want the name to have dashes in as date element separators the partition names would have to be quoted identifiers, which will make working with them explicitly a bit more complicated later.
If you have a table that initially has named partitions:
create table t42 (date_col date, other_col varchar2(10))
partition by range (date_col)
(
partition "Pre-2014" values less than (date '2014-01-01'),
partition "1-jan-2014" values less than (date '2014-02-01'),
partition "1-feb-2014" values less than (date '2014-03-01'),
partition "1-mar-2014" values less than (date '2014-04-01'),
partition "1-apr-2014" values less than (date '2014-05-01')
);
Then you can manually add a new named partition:
alter table t42 add partition "1-may-2014" values less than (date '2014-06-01');
alter table t42 add partition "1-jun-2014" values less than (date '2014-07-01');
If you want to add a new partition based on sysdate (or any date) then you'd need dynamic SQL to generate that command; here in an anonymous block but you coudl have a procedure if you want to pass a date in:
begin
execute immediate 'alter table t42 add partition "'
|| to_char(trunc(sysdate, 'MM'), 'FMdd-mon-yyyy')
|| '" values less than (date '''
|| to_char(add_months(trunc(sysdate, 'MM'), 1), 'YYYY-MM-DD')
|| ''')';
end;
/
Today that would generate and execute command:
alter table t42 add partition "1-jul-2014" values less than (date '2014-08-01')
Then you can see the partition is named as you wanted:
select partition_name from user_tab_partitions order by partition_position;
PARTITION_NAME
------------------------------
Pre-2014
1-jan-2014
1-feb-2014
1-mar-2014
1-apr-2014
1-may-2014
1-jun-2014
1-jul-2014

Resources