Oracle database partition manager using PL/SQL

22 Dec 2016

Maintaining partitions is one of the common (and mundane) tasks in a database. Since most data warehouses have time-series data, a lot of these cases involve date-range partitions. I wrote the below script/framework that automates some of those boring parts, based on scenarios/requirements I have seen in real-life.

These are some of the high level requiremetns that I wanted to be handled.

  1. Have a configuration table that has the rules for the partitions
  2. Allow partition name and format to be configured using a template (only dates for now). For example P_{YYYY}_{MM} would get translated to P_2016_04
  3. Allow Table space name and format to be configured (to allow for year wise table spaces, for example) provide procedures to generate initial set of partitions or just the missing partitions.

Here’s the metadata table that holds the partition metadata and configuration.

CREATE TABLE partition_metadata(
  OWNER                  VARCHAR2(30) NOT NULL,
  TABLE_NAME             VARCHAR2(30) NOT NULL,
  PARTITION_FREQUENCY    VARCHAR2(30) NOT NULL,
  PARTITION_NAME_FORMAT  VARCHAR2(30) NOT NULL,
  PARTITION_TS_FORMAT    VARCHAR2(30) NOT NULL,
  FIRST_PARTITION_NAME   VARCHAR2(30) NOT NULL,
  MAXVAL_PARTITION_NAME  VARCHAR2(30) NOT NULL,
  MAXVAL_TS_NAME_FORMAT  VARCHAR2(30) NOT NULL,
  NUM_ADVANCE_PARTITIONS NUMBER       DEFAULT 1 NOT NULL,       
  ACTIVE_RECORD_FLAG     VARCHAR2(1)  DEFAULT 'Y' NOT NULL,
  DW_UPDATED_BY          VARCHAR2(30) NOT NULL,
  DW_LAST_UPDATED        DATE         NOT NULL
);

Next, we look at some test data. The following examples add daily and monthly configuration.

INSERT INTO PARTITION_METADATA (OWNER,TABLE_NAME,PARTITION_FREQUENCY,PARTITION_NAME_FORMAT,PARTITION_TS_FORMAT,FIRST_PARTITION_NAME,MAXVAL_PARTITION_NAME,MAXVAL_TS_NAME_FORMAT,NUM_ADVANCE_PARTITIONS,ACTIVE_RECORD_FLAG,DW_UPDATED_BY,DW_LAST_UPDATED)
 VALUES ('DWH','SALES','DAILY','P{yyyy}{mm}{dd}','DWH_SALES_DAT','P20100101','PMAX','DWH_SALES_DATA',10,'Y','DBMAINTUSER',sysdate);
 
INSERT INTO PARTITION_METADATA (OWNER,TABLE_NAME,PARTITION_FREQUENCY,PARTITION_NAME_FORMAT,PARTITION_TS_FORMAT,FIRST_PARTITION_NAME,MAXVAL_PARTITION_NAME,MAXVAL_TS_NAME_FORMAT,NUM_ADVANCE_PARTITIONS,ACTIVE_RECORD_FLAG,DW_UPDATED_BY,DW_LAST_UPDATED)
 VALUES ('DWH','SALES_MONTHLY','MONTHLY','P{yyyy}{mm}','DWH_SALES_DATA','P20100101','PMAX','DWH_SALES_DATA',2,'Y','DBMAINTUSER',sysdate);
 
commit;

The final package has the following stub. The complete packge body (including the scripts) are on github.

CREATE OR REPLACE package pkg_partition_manager
AS
 PROCEDURE p_create_missing_partitions(
    p_owner      IN partition_metadata.owner%TYPE,
    p_table_name IN partition_metadata.table_name%TYPE
  );
END;
/

Couple of things to notice.

1) it uses the Oracle partition metadata to check for already existing partitions, stored in dba_partititions.

2) connect by clause to generate the partitions, based on the configuration present in the partition_metadata table. The partial end result is a query such as this, which does a minus between the expected and existing partitions to get the missing partitions.

WITH all_partitions AS (
    SELECT REPLACE(REPLACE('P{yyyy}{mm}',
                            '{mm}',
                            to_char(add_months(to_date('01-JAN-2010 00:00:00'),level,'mm')
                          ),'{yyyy}',
                            to_char(add_months(to_date('01-JAN-2010 00:00:00'),level),'yyyy'
                   )  partition_name, 
 add_months(to_date('01-JAN-2010 00:00:00'),level) + 1 partition_maxval, 
 level partition_position 
 FROM dual
 CONNECT BY level <  trunc(months_between(sysdate,to_date('01-JAN-2010 00:00:00')))  + 2
), current_partitions AS (
 SELECT partition_name FROM all_tab_partitions 
 WHERE table_owner = 'DWH'
   AND TABLE_NAME = 'SALES_MONTHLY')
SELECT ap.partition_name, ap.partition_maxval,
       'alter table DWH.SALES_MONTHLY 
        split partition PMAX at ( to_date(''' || ap.partition_maxval || ''')) into          (partition ' || 
        ap.partition_name  || ' ,partition PMAX) update global indexes;'
FROM   current_partitions cp, all_partitions ap
WHERE  ap.partition_name = cp.partition_name(+)
  AND  cp.partition_name IS NULL
 ORDER BY partition_position ASC;

This spits out the split statements necessary to create the partitions. output varies based on whether the partitions are daily or monthly. You could run the final commands using execute immediate if needed.

BEGIN
  pkg_partition_manager.p_create_missing_partitions (
    p_owner       => 'DWH',
    p_table_name  => 'SALES' -- daily partitoned table
  );
END;
/

ALTER TABLE DWH.SALES split partition PMAX at ( to_date('03-JAN-2010 00:00:00')) 
      INTO  (partition P20100102 ,partition PMAX) UPDATE global indexes;
ALTER TABLE DWH.SALES split partition PMAX at ( to_date('04-JAN-2010 00:00:00')) 
      INTO  (partition P20100103 ,partition PMAX) UPDATE global indexes;
ALTER TABLE DWH.SALES split partition PMAX at ( to_date('05-JAN-2010 00:00:00')) 
      INTO  (partition P20100104 ,partition PMAX) UPDATE global indexes;
 
BEGIN
  pkg_partition_manager.p_create_missing_partitions (
    p_owner       => 'DWH',
    p_table_name  => 'SALES_MONTHLY' -- monthly partitioned table.
  );
END;
/
 
ALTER TABLE DWH.SALES_MONTHLY split partition PMAX at ( to_date('01-FEB-2010 00:00:00')) 
      INTO (partition P201002 ,partition PMAX) UPDATE global indexes;
ALTER TABLE DWH.SALES_MONTHLY split partition PMAX at ( to_date('01-MAR-2010 00:00:00')) 
      INTO  (partition P201003 ,partition PMAX) UPDATE global indexes;
ALTER TABLE DWH.SALES_MONTHLY split partition PMAX at ( to_date('01-APR-2010 00:00:00')) 
      INTO  (partition P201004 ,partition PMAX) UPDATE global indexes;

At the moment, this code only takes care of range partitioned tables and only allows daily and monthly partitions. In reality, there are a lot more types (hash, composite partitioning using Range+Hash, Range+List, for eaxmple.). I will add support for them later, if there is ever a need for it.

This was, of course, very specific for my needs. Some of the tables had missing partitions, there were different versions of this query in multiple places and sometimes the logic was inconsistent (fixed 30-day months for example).

However, I think some of the ideas such as using “connect by” to generate the names and template based naming are useful for other use cases and I hope you find it useful.

code on github

who am i

I am a data engineer with interests in databases, data science, algorithms and programming in general.

where am i

linkedin//rchamarthi
stackoverflow/users/237939/rchamarthi
Content available under Creative Commons (BY-NC-SA) unless otherwise noted.
This site is hosted at Github Pages and created with Jekyll. It uses the papyrus theme