8i | 9i | 10g | 11g | 12c | 13c | 18c | Misc | PL/SQL | SQL | RAC | WebLogic | Linux

Home » Articles » 12c » Here

Online SPLIT PARTITION and SPLIT SUBPARTITION in Oracle Database 12c Release 2 (12.2)

In Oracle Database 12c Release 2 (12.2) the SPLIT PARTITION and SPLIT SUBPARTITION operations on heap tables can be performed online so they don't block DML. This is done by the addition of the ONLINE keyword, which also causes local and global indexes to be updated without having to specify the UPDATE INDEXES clause.

Split Partition

Related articles.

Online SPLIT PARTITION

Create a partitioned table with a single partition, along with a global and partitioned index.

DROP TABLE t1 PURGE;

CREATE TABLE t1 (
  id            NUMBER,
  code          VARCHAR2(10),
  description   VARCHAR2(50),
  created_date  DATE,
  CONSTRAINT t1_pk PRIMARY KEY (id)
)
PARTITION BY RANGE (created_date)
(
  PARTITION t1_2017 VALUES LESS THAN (MAXVALUE)
);


CREATE INDEX t1_created_date_idx ON t1 (created_date) LOCAL;
CREATE INDEX t1_code_idx ON t1 (code);


INSERT INTO t1 VALUES ( 1, 'ONE',   'Description for ONE', TO_DATE('01-JUL-2015','DD-MON-YYYY'));
INSERT INTO t1 VALUES ( 2, 'TWO',   'Description for ONE', TO_DATE('01-JUL-2015','DD-MON-YYYY'));
INSERT INTO t1 VALUES ( 3, 'THREE', 'Description for ONE', TO_DATE('01-JUL-2015','DD-MON-YYYY'));
INSERT INTO t1 VALUES ( 4, 'FOUR',  'Description for ONE', TO_DATE('01-JUL-2015','DD-MON-YYYY'));
INSERT INTO t1 VALUES ( 5, 'ONE',   'Description for ONE', TO_DATE('01-JUL-2016','DD-MON-YYYY'));
INSERT INTO t1 VALUES ( 6, 'TWO',   'Description for ONE', TO_DATE('01-JUL-2016','DD-MON-YYYY'));
INSERT INTO t1 VALUES ( 7, 'THREE', 'Description for ONE', TO_DATE('01-JUL-2016','DD-MON-YYYY'));
INSERT INTO t1 VALUES ( 8, 'FOUR',  'Description for ONE', TO_DATE('01-JUL-2016','DD-MON-YYYY'));
INSERT INTO t1 VALUES ( 9, 'ONE',   'Description for ONE', TO_DATE('01-JUL-2017','DD-MON-YYYY'));
INSERT INTO t1 VALUES (10, 'TWO',   'Description for ONE', TO_DATE('01-JUL-2017','DD-MON-YYYY'));
INSERT INTO t1 VALUES (11, 'THREE', 'Description for ONE', TO_DATE('01-JUL-2017','DD-MON-YYYY'));
INSERT INTO t1 VALUES (12, 'FOUR',  'Description for ONE', TO_DATE('01-JUL-2017','DD-MON-YYYY'));
COMMIT;

EXEC DBMS_STATS.gather_table_stats(USER, 't1', cascade => TRUE);

Display the the current table partition and index information.

-- Table partitions.
COLUMN table_name FORMAT A30
COLUMN partition_name FORMAT A30

SELECT table_name,
       partition_name,
       num_rows
FROM   user_tab_partitions
ORDER BY 1, 2;

TABLE_NAME                     PARTITION_NAME                   NUM_ROWS
------------------------------ ------------------------------ ----------
T1                             T1_2017                                12

SQL>


-- Indexes.
COLUMN index_name FORMAT A30
COLUMN partitioned FORMAT A11

SELECT index_name,
       partitioned,
       status
FROM   user_indexes
ORDER BY 1;

INDEX_NAME                     PARTITIONED STATUS
------------------------------ ----------- --------
T1_CODE_IDX                    NO          VALID
T1_CREATED_DATE_IDX            YES         N/A
T1_PK                          NO          VALID

SQL>


-- Index partitions.
SELECT index_name,
       partition_name,
       status
FROM   user_ind_partitions
ORDER BY 1, 2;

INDEX_NAME                     PARTITION_NAME                 STATUS
------------------------------ ------------------------------ --------
T1_CREATED_DATE_IDX            T1_2017                        USABLE

SQL>

Perform an online operation to split the 2017 partition to create a new partition for rows with a CREATED_DATE column value in 2015.

ALTER TABLE t1
  SPLIT PARTITION t1_2017 AT (TO_DATE('31-DEC-2015 23:59:59', 'DD-MON-YYYY HH24:MI:SS'))
  INTO (PARTITION t1_2015,
        PARTITION t1_2017)
  ONLINE;

EXEC DBMS_STATS.gather_table_stats(USER, 't1', cascade => TRUE);

Display the current status of the table partitions and indexes. Notice both the global and local indexes are left in a usable state without having to specify the UPDATE INDEXES clause.

-- Table partitions.
COLUMN table_name FORMAT A30
COLUMN partition_name FORMAT A30

SELECT table_name,
       partition_name,
       num_rows
FROM   user_tab_partitions
ORDER BY 1, 2;

TABLE_NAME                     PARTITION_NAME                   NUM_ROWS
------------------------------ ------------------------------ ----------
T1                             T1_2015                                 4
T1                             T1_2017                                 8

SQL>


-- Indexes.
COLUMN index_name FORMAT A30
COLUMN partitioned FORMAT A11

SELECT index_name,
       partitioned,
       status
FROM   user_indexes
ORDER BY 1;

INDEX_NAME                     PARTITIONED STATUS
------------------------------ ----------- --------
T1_CODE_IDX                    NO          VALID
T1_CREATED_DATE_IDX            YES         N/A
T1_PK                          NO          VALID

SQL>


-- Index partitions.
SELECT index_name,
       partition_name,
       status
FROM   user_ind_partitions
ORDER BY 1, 2;

INDEX_NAME                     PARTITION_NAME                 STATUS
------------------------------ ------------------------------ --------
T1_CREATED_DATE_IDX            T1_2015                        USABLE
T1_CREATED_DATE_IDX            T1_2017                        USABLE

SQL>

For a comparison, perform an offline operation to split the 2017 partition to create a new partition for rows with a CREATED_DATE column value in 2016.

ALTER TABLE t1
  SPLIT PARTITION t1_2017 AT (TO_DATE('31-DEC-2016 23:59:59', 'DD-MON-YYYY HH24:MI:SS'))
  INTO (PARTITION t1_2016,
        PARTITION t1_2017);

EXEC DBMS_STATS.gather_table_stats(USER, 't1', cascade => TRUE);

Display the current status of the table partitions and indexes. Notice both the global and local indexes on the affected partitions are left in a unusable state because we didn't specify the UPDATE INDEXES clause or ONLINE keyword.

-- Table partitions.
COLUMN table_name FORMAT A30
COLUMN partition_name FORMAT A30

SELECT table_name,
       partition_name,
       num_rows
FROM   user_tab_partitions
ORDER BY 1, 2;

TABLE_NAME                     PARTITION_NAME                   NUM_ROWS
------------------------------ ------------------------------ ----------
T1                             T1_2015                                 4
T1                             T1_2016                                 4
T1                             T1_2017                                 4

SQL>


-- Indexes.
COLUMN index_name FORMAT A30
COLUMN partitioned FORMAT A11

SELECT index_name,
       partitioned,
       status
FROM   user_indexes
ORDER BY 1;

INDEX_NAME                     PARTITIONED STATUS
------------------------------ ----------- --------
T1_CODE_IDX                    NO          UNUSABLE
T1_CREATED_DATE_IDX            YES         N/A
T1_PK                          NO          UNUSABLE

SQL>


-- Index partitions.
COLUMN partition_name FORMAT A30

SELECT index_name,
       partition_name,
       status
FROM   user_ind_partitions
ORDER BY 1, 2;

INDEX_NAME                     PARTITION_NAME                 STATUS
------------------------------ ------------------------------ --------
T1_CREATED_DATE_IDX            T1_2015                        USABLE
T1_CREATED_DATE_IDX            T1_2016                        UNUSABLE
T1_CREATED_DATE_IDX            T1_2017                        UNUSABLE

SQL>

Online SPLIT SUBPARTITION

Create a subpartitioned table with two partitions, each with two subpartitions, along with a global and partitioned index.

DROP TABLE t1 PURGE;

CREATE TABLE t1 (
  id            NUMBER,
  code          VARCHAR2(10),
  description   VARCHAR2(50),
  created_date  DATE,
  CONSTRAINT t1_pk PRIMARY KEY (id)
)
PARTITION BY RANGE (created_date)
SUBPARTITION BY LIST (code)
  SUBPARTITION TEMPLATE (
    SUBPARTITION small VALUES ('ONE', 'TWO', 'THREE'),
    SUBPARTITION others VALUES (DEFAULT)
  )
(
  PARTITION year_2016 VALUES LESS THAN ( TO_DATE('01-JAN-2017','DD-MON-YYYY')),
  PARTITION year_2017 VALUES LESS THAN ( TO_DATE('01-JAN-2018','DD-MON-YYYY'))
);

CREATE INDEX t1_created_date_idx ON t1 (created_date) LOCAL;
CREATE INDEX t1_code_idx ON t1 (code);

INSERT INTO t1 VALUES (1, 'ONE',   'Description for ONE', TO_DATE('01-JUL-2016','DD-MON-YYYY'));
INSERT INTO t1 VALUES (2, 'TWO',   'Description for ONE', TO_DATE('01-JUL-2016','DD-MON-YYYY'));
INSERT INTO t1 VALUES (3, 'THREE', 'Description for ONE', TO_DATE('01-JUL-2016','DD-MON-YYYY'));
INSERT INTO t1 VALUES (4, 'FOUR',  'Description for ONE', TO_DATE('01-JUL-2016','DD-MON-YYYY'));
INSERT INTO t1 VALUES (5, 'ONE',   'Description for ONE', TO_DATE('01-JUL-2017','DD-MON-YYYY'));
INSERT INTO t1 VALUES (6, 'TWO',   'Description for ONE', TO_DATE('01-JUL-2017','DD-MON-YYYY'));
INSERT INTO t1 VALUES (7, 'THREE', 'Description for ONE', TO_DATE('01-JUL-2017','DD-MON-YYYY'));
INSERT INTO t1 VALUES (8, 'FOUR',  'Description for ONE', TO_DATE('01-JUL-2017','DD-MON-YYYY'));
COMMIT;

EXEC DBMS_STATS.gather_table_stats(USER, 'T1', cascade => TRUE);

Display the the current table partition and index information.

SET LINESIZE 120

-- Table subpartitions.
SELECT table_name,
       partition_name,
       subpartition_name,
       num_rows
FROM   user_tab_subpartitions
ORDER BY 1,2,3;

TABLE_NAME                     PARTITION_NAME                 SUBPARTITION_NAME                NUM_ROWS
------------------------------ ------------------------------ ------------------------------ ----------
T1                             YEAR_2016                      YEAR_2016_OTHERS                        1
T1                             YEAR_2016                      YEAR_2016_SMALL                         3
T1                             YEAR_2017                      YEAR_2017_OTHERS                        1
T1                             YEAR_2017                      YEAR_2017_SMALL                         3

SQL>


-- Indexes.
SELECT index_name,
       partitioned,
       status
FROM   user_indexes
ORDER BY 1;

INDEX_NAME                     PARTITIONED STATUS
------------------------------ ----------- --------
T1_CODE_IDX                    NO          VALID
T1_CREATED_DATE_IDX            YES         N/A
T1_PK                          NO          VALID

SQL>


-- Index subpartitions.
SELECT index_name,
       partition_name,
       subpartition_name,
       status
FROM   user_ind_subpartitions
ORDER BY 1,2,3;

INDEX_NAME                     PARTITION_NAME                 SUBPARTITION_NAME              STATUS
------------------------------ ------------------------------ ------------------------------ --------
T1_CREATED_DATE_IDX            YEAR_2016                      YEAR_2016_OTHERS               USABLE
T1_CREATED_DATE_IDX            YEAR_2016                      YEAR_2016_SMALL                USABLE
T1_CREATED_DATE_IDX            YEAR_2017                      YEAR_2017_OTHERS               USABLE
T1_CREATED_DATE_IDX            YEAR_2017                      YEAR_2017_SMALL                USABLE

SQL>

Perform an online operation to split the "small" subpartition to create a new partition for rows with a CODE column with values 'TWO' and 'THREE'.

ALTER TABLE t1 SPLIT SUBPARTITION year_2016_small
  VALUES ('TWO', 'THREE') INTO (
    SUBPARTITION year_2016_two_three,
    SUBPARTITION year_2016_small
  ) 
  ONLINE;

EXEC DBMS_STATS.gather_table_stats(USER, 'T1', cascade => TRUE);

Display the current status of the table subpartitions and indexes. Notice both the global and local indexes are left in a usable state without having to specify the UPDATE INDEXES clause.

-- Table subpartitions.
SELECT table_name,
       partition_name,
       subpartition_name,
       num_rows
FROM   user_tab_subpartitions
ORDER BY 1,2,3;

TABLE_NAME                     PARTITION_NAME                 SUBPARTITION_NAME                NUM_ROWS
------------------------------ ------------------------------ ------------------------------ ----------
T1                             YEAR_2016                      YEAR_2016_OTHERS                        1
T1                             YEAR_2016                      YEAR_2016_SMALL                         1
T1                             YEAR_2016                      YEAR_2016_TWO_THREE                     2
T1                             YEAR_2017                      YEAR_2017_OTHERS                        1
T1                             YEAR_2017                      YEAR_2017_SMALL                         3

SQL>


-- Indexes.
SELECT index_name,
       partitioned,
       status
FROM   user_indexes
ORDER BY 1;

INDEX_NAME                     PARTITIONED STATUS
------------------------------ ----------- --------
T1_CODE_IDX                    NO          VALID
T1_CREATED_DATE_IDX            YES         N/A
T1_PK                          NO          VALID

SQL>


-- Index subpartitions.
SELECT index_name,
       partition_name,
       subpartition_name,
       status
FROM   user_ind_subpartitions
ORDER BY 1,2,3;

INDEX_NAME                     PARTITION_NAME                 SUBPARTITION_NAME              STATUS
------------------------------ ------------------------------ ------------------------------ --------
T1_CREATED_DATE_IDX            YEAR_2016                      YEAR_2016_OTHERS               USABLE
T1_CREATED_DATE_IDX            YEAR_2016                      YEAR_2016_SMALL                USABLE
T1_CREATED_DATE_IDX            YEAR_2016                      YEAR_2016_TWO_THREE            USABLE
T1_CREATED_DATE_IDX            YEAR_2017                      YEAR_2017_OTHERS               USABLE
T1_CREATED_DATE_IDX            YEAR_2017                      YEAR_2017_SMALL                USABLE

SQL>

For a comparison, perform an offline operation to split the subpartition for the second partition.

ALTER TABLE t1 SPLIT SUBPARTITION year_2017_small
  VALUES ('TWO', 'THREE') INTO (
    SUBPARTITION year_2017_two_three,
    SUBPARTITION year_2017_small
  );

EXEC DBMS_STATS.gather_table_stats(USER, 'T1', cascade => TRUE);

Display the current status of the table subpartitions and indexes. Notice both the global and local indexes on the affected subpartitions are left in a unusable state because we didn't specify the UPDATE INDEXES clause or ONLINE keyword.

-- Table subpartitions.
SELECT table_name,
       partition_name,
       subpartition_name,
       num_rows
FROM   user_tab_subpartitions
ORDER BY 1,2,3;

TABLE_NAME                     PARTITION_NAME                 SUBPARTITION_NAME                NUM_ROWS
------------------------------ ------------------------------ ------------------------------ ----------
T1                             YEAR_2016                      YEAR_2016_OTHERS                        1
T1                             YEAR_2016                      YEAR_2016_SMALL                         1
T1                             YEAR_2016                      YEAR_2016_TWO_THREE                     2
T1                             YEAR_2017                      YEAR_2017_OTHERS                        1
T1                             YEAR_2017                      YEAR_2017_SMALL                         1
T1                             YEAR_2017                      YEAR_2017_TWO_THREE                     2

SQL>


-- Indexes.
SELECT index_name,
       partitioned,
       status
FROM   user_indexes
ORDER BY 1;

INDEX_NAME                     PARTITIONED STATUS
------------------------------ ----------- --------
T1_CODE_IDX                    NO          UNUSABLE
T1_CREATED_DATE_IDX            YES         N/A
T1_PK                          NO          UNUSABLE

SQL>


-- Index subpartitions.
SELECT index_name,
       partition_name,
       subpartition_name,
       status
FROM   user_ind_subpartitions
ORDER BY 1,2,3;

INDEX_NAME                     PARTITION_NAME                 SUBPARTITION_NAME              STATUS
------------------------------ ------------------------------ ------------------------------ --------
T1_CREATED_DATE_IDX            YEAR_2016                      YEAR_2016_OTHERS               USABLE
T1_CREATED_DATE_IDX            YEAR_2016                      YEAR_2016_SMALL                USABLE
T1_CREATED_DATE_IDX            YEAR_2016                      YEAR_2016_TWO_THREE            USABLE
T1_CREATED_DATE_IDX            YEAR_2017                      YEAR_2017_OTHERS               USABLE
T1_CREATED_DATE_IDX            YEAR_2017                      YEAR_2017_SMALL                UNUSABLE
T1_CREATED_DATE_IDX            YEAR_2017                      YEAR_2017_TWO_THREE            UNUSABLE

SQL>

For more information see:

Hope this helps. Regards Tim...

Back to the Top.