• Home
  • SQL & PL/SQL Guidelines
  • Publications / Talks
  • Videos
  • Monday Morning Tech Talk
  • About
  • Contact

Just another ORACLE Development blog

Just another ORACLE Development blog

Category Archives: Partitioning

ORACLE 12.2 -Auto List Partitioning

08 Sunday Jan 2017

Posted by troroblog in Partitioning

≈ Leave a comment

Tags

ORACLE, ORACLE 12c Release 2

Another new feature in the partitioning aerea is the automatic list partitioning. Like with the interval partitioning that came with ORACLE 11.1 the automatic list partitioning creates new partitions as they are required.

The following lines show an example of automatic list partitioning:

Example:

-- =============================================================================
-- Create an automatic list partitioned table
-- =============================================================================
CREATE TABLE employees_part (
   employee_id     NUMBER(4)
  ,first_name      VARCHAR2(30)
  ,last_name       VARCHAR2(30)
  ,email           VARCHAR2(30)
  ,phone_number    VARCHAR2(20)
  ,hire_date       DATE
  ,salary          NUMBER(6,0)
  ,commission_pct  NUMBER(3,2)
  ,manager_id      NUMBER(4)
  ,department_id   NUMBER(4)
)
PARTITION BY LIST (department_id) AUTOMATIC (
   PARTITION P_10 VALUES (10)
);

SELECT partition_name, high_value, partition_position
  FROM all_tab_partitions 
 WHERE table_name = 'EMPLOYEES_PART';

PARTITION_NAME       HIGH_VALUE           PARTITION_POSITION
-------------------- -------------------- ------------------
P_10                 10                                    1


Adding new values to the partitioned table:

In a first step I try to create a list partitioned table having a global and a local index. 

INSERT INTO employees_part (employee_id
                          , first_name
                          , last_name
                          , email
                          , phone_number
                          , hire_date
                          , salary
                          , commission_pct
                          , manager_id
                          , department_id)
SELECT employee_id
     , first_name
     , last_name
     , email
     , phone_number
     , hire_date
     , salary
     , commission_pct
     , manager_id
     , department_id
 FROM employees;

107 rows inserted.

SELECT partition_name, high_value, partition_position
  FROM all_tab_partitions 
 WHERE table_name = 'EMPLOYEES_PART';

PARTITION_NAME       HIGH_VALUE           PARTITION_POSITION
-------------------- -------------------- ------------------
P_10                 10                                    1
SYS_P2161            90                                    2
SYS_P2162            60                                    3
SYS_P2163            100                                   4
SYS_P2164            30                                    5
SYS_P2165            50                                    6
SYS_P2166            80                                    7
SYS_P2167            NULL                                  8
SYS_P2168            20                                    9
SYS_P2169            40                                   10
SYS_P2170            70                                   11
SYS_P2171            110                                  12

How to find out whether a partitioned table is set to automatic?

The information whether a table is set to automatic is stored in the …_PART_TABLES dictionary views.

SELECT table_name, partitioning_type, autolist 
  FROM user_part_tables;

TABLE_NAME      PARTITIONING_TYPE  AUTOLIST
--------------- ------------------ --------
EMPLOYEES_PART  LIST               YES

Conclusion:

The new automatic list partitioning feature dispenses us from the duty to either have to manually (or programatically) create new partitions for new values or having a default partition to store all values that are not covered by the existing partitions. 

This burdon was not that big…but it is still a useful simplification.

ORACLE 12.2 – Convert a non-partitioned table to a partitioned table

31 Saturday Dec 2016

Posted by troroblog in Partitioning

≈ Leave a comment

Tags

ORACLE, ORACLE 12c Release 2

ORACLE 12.2 allows the conversion of a non partitioned table to a partitioned table using the ALTER TABLE command. The whole conversion can be done “online” meaning, that during the conversion DML operations against the table are allowed.

Preparation:

-- =============================================================================
-- Create a non partitioned Table
-- =============================================================================
CREATE TABLE emp_dept
AS
SELECT e.empno ,e.ename ,e.job ,e.mgr
      ,e.hiredate ,e.sal ,e.comm ,d.deptno
      ,d.dname ,d.loc
  FROM emp e
  JOIN dept d ON (e.deptno = d.deptno);

-- =============================================================================
-- Add 2 indexes to the table
-- =============================================================================
CREATE UNIQUE INDEX emp_dept_empno_idx ON emp_dept(empno);
CREATE INDEX emp_dept_deptno_empno_idx ON emp_dept (deptno, empno);

 


ALTER TABLE MODIFY:

In a first step I try to create a list partitioned table having a global and a local index. 

-- =============================================================================
-- ALTER TABLE MODIFY
-- =============================================================================
ALTER TABLE emp_dept MODIFY
   PARTITION BY LIST (deptno) (
      PARTITION P_DEPT10 VALUES (10)
      ,PARTITION P_DEPT20 VALUES (20)
      ,PARTITION P_DEPT30 VALUES (30)
      ,PARTITION P_DEPT40 VALUES (40)
      ,PARTITION P_DEFAULT VALUES (DEFAULT)
   ) 
ONLINE
UPDATE INDEXES (
   emp_dept_empno_idx GLOBAL
  ,emp_dept_deptno_empno_idx LOCAL); 

-- =============================================================================
-- Check partitions
-- =============================================================================
SELECT table_name, partition_name
  FROM user_tab_partitions
 WHERE table_name = 'EMP_DEPT';

TABLE_NAME                     PARTITION_NAME 
------------------------------ ------------------------------
EMP_DEPT                       P_DEFAULT 
EMP_DEPT                       P_DEPT10 
EMP_DEPT                       P_DEPT20 
EMP_DEPT                       P_DEPT30 
EMP_DEPT                       P_DEPT40 

-- ============================================================================= 
-- Check indexes 
-- ============================================================================= 
SELECT index_name, uniqueness, status, partitioned 
  FROM user_indexes 
 WHERE table_name = 'EMP_DEPT';

INDEX_NAME                     UNIQUENES STATUS   PARTITIONED 
------------------------------ --------- -------- ---------------
EMP_DEPT_DEPTNO_EMPNO_IDX      NONUNIQUE N/A      YES 
EMP_DEPT_EMPNO_IDX             UNIQUE    VALID    NO 

SELECT index_name, uniqueness, status, partitioned 
 FROM user_indexes 
 WHERE table_name = 'EMP_DEPT';

INDEX_NAME                     PARTITION_NAME                 STATUS 
------------------------------ ------------------------------ --------
EMP_DEPT_DEPTNO_EMPNO_IDX      P_DEFAULT                      USABLE 
EMP_DEPT_DEPTNO_EMPNO_IDX      P_DEPT10                       USABLE 
EMP_DEPT_DEPTNO_EMPNO_IDX      P_DEPT20                       USABLE 
EMP_DEPT_DEPTNO_EMPNO_IDX      P_DEPT30                       USABLE 
EMP_DEPT_DEPTNO_EMPNO_IDX      P_DEPT40                       USABLE

How about subpartitioning?

Now, let’s try to modify a table and create partitions and subpartitions from a non partitioned table. The base will be the same emp_dept table with the same indexes as in the previous example.

-- =============================================================================
-- ALTER TABLE MODIFY
-- =============================================================================
ALTER TABLE emp_dept MODIFY
   PARTITION BY RANGE (hiredate) SUBPARTITION BY LIST (deptno) ( 
      PARTITION hiredate_p1 VALUES LESS THAN (date '1990-01-01') ( 
         SUBPARTITION hiredate_p1_10 VALUES (10)
        ,SUBPARTITION hiredate_p1_20 VALUES (20)
        ,SUBPARTITION hiredate_p1_30 VALUES (30))
     ,PARTITION hiredate_p2 VALUES LESS THAN (date '2000-01-01') ( 
        SUBPARTITION hiredate_p2_10 VALUES (10)
       ,SUBPARTITION hiredate_p2_20 VALUES (20)
       ,SUBPARTITION hiredate_p2_30 VALUES (30))
   )
ONLINE
UPDATE INDEXES (
   emp_dept_empno_idx GLOBAL
  ,emp_dept_deptno_empno_idx LOCAL); 

-- =============================================================================
-- Check partitions
-- =============================================================================
SELECT utp.table_name, utp.partition_name, utsp.subpartition_name
  FROM user_tab_partitions utp
  JOIN user_tab_subpartitions utsp ON (utsp.partition_name = utp.partition_name)
 WHERE utp.table_name = 'EMP_DEPT'
 ORDER BY utp.partition_name, utsp.subpartition_name;

TABLE_NAME                   PARTITION_NAME             SUBPARTITION_NAME 
---------------------------- -------------------------- -------------------------
EMP_DEPT                     HIREDATE_P1                HIREDATE_P1_10 
EMP_DEPT                     HIREDATE_P1                HIREDATE_P1_20 
EMP_DEPT                     HIREDATE_P1                HIREDATE_P1_30 
EMP_DEPT                     HIREDATE_P2                HIREDATE_P2_10 
EMP_DEPT                     HIREDATE_P2                HIREDATE_P2_20 
EMP_DEPT                     HIREDATE_P2                HIREDATE_P2_30 

-- ============================================================================= 
-- Check indexes 
-- ============================================================================= 
SELECT index_name, uniqueness, status, partitioned 
  FROM user_indexes 
 WHERE table_name = 'EMP_DEPT';

INDEX_NAME                     UNIQUENES STATUS   PARTITIONED 
------------------------------ --------- -------- ---------------
EMP_DEPT_DEPTNO_EMPNO_IDX      NONUNIQUE N/A      YES 
EMP_DEPT_EMPNO_IDX             UNIQUE    VALID    NO 

SELECT uip.index_name, uip.partition_name, uip.subpartition_name, uip.status
 FROM user_ind_subpartitions uip
 JOIN user_indexes ui ON (ui.index_name = uip.index_name)
 WHERE ui.table_name = 'EMP_DEPT';

INDEX_NAME                     PARTITION_NAME       SUBPARTITION_NAME    STATUS 
------------------------------ -------------------- -------------------- --------
EMP_DEPT_DEPTNO_EMPNO_IDX      HIREDATE_P1          HIREDATE_P1_10       USABLE 
EMP_DEPT_DEPTNO_EMPNO_IDX      HIREDATE_P1          HIREDATE_P1_20       USABLE 
EMP_DEPT_DEPTNO_EMPNO_IDX      HIREDATE_P1          HIREDATE_P1_30       USABLE 
EMP_DEPT_DEPTNO_EMPNO_IDX      HIREDATE_P2          HIREDATE_P2_10       USABLE 
EMP_DEPT_DEPTNO_EMPNO_IDX      HIREDATE_P2          HIREDATE_P2_20       USABLE 
EMP_DEPT_DEPTNO_EMPNO_IDX      HIREDATE_P2          HIREDATE_P2_30       USABLE 

Conclusion:

Everything worked as expected. 

 

ORACLE 12.2 – create table for exchange with clause

30 Friday Dec 2016

Posted by troroblog in Partitioning

≈ Leave a comment

Tags

ORACLE, ORACLE 12c Release 2, SQL

With ORACLE 12.2 the create table statement has been enriched by the new “for exchange with” clause. This enables us to create a exchange table that is a clone of the partitioned table which is the target of a later exchange table command

The new clause takes care of datatypes, data scale and precision, NOT NULL constraints, hidden column, virtual columns, unused columns,  etc.

Indexes, constraints (other than NOT NULL) are not cloned.

Preparation:

-- =============================================================================
-- Create Partitioned Table
-- =============================================================================
CREATE TABLE EMP_PART ( 
   EMPNO    NUMBER(4,0)       NOT NULL
  ,ENAME    VARCHAR2(10 CHAR) NOT NULL
  ,JOB      VARCHAR2(9 CHAR)  NOT NULL
  ,MGR      NUMBER(4,0)
  ,HIREDATE DATE              NOT NULL
  ,SAL      NUMBER(7,2)
  ,COMM     NUMBER(7,2)
  ,DEPTNO   NUMBER(2,0)       NOT NULL
  ,CONSTRAINT chk_sal_comm_less_1000 CHECK (NVL(sal,0) + NVL(comm,0) < 10000)
  ,CONSTRAINT chk_empno CHECK (empno between 1000 and 9999)
) 
PARTITION BY LIST (deptno) (
   PARTITION p_accounting VALUES (10)
  ,PARTITION p_research   VALUES (20)
  ,PARTITION p_sales      VALUES (30)
  ,PARTITION p_default    VALUES (DEFAULT)
);

-- =============================================================================
-- Create Local Index
-- =============================================================================
CREATE INDEX emp_part_idx ON EMP_PART (hiredate)
   LOCAL (
      PARTITION p_accounting_hiredate_idx
     ,PARTITION p_research_hiredate_idx
     ,PARTITION p_sales_hiredate_idx
     ,PARTITION p_default_hiredate_idx
);

-- =============================================================================
-- Add a Primary Key
-- =============================================================================
ALTER TABLE emp_part 
   ADD CONSTRAINT emp_part_pk
   PRIMARY KEY (empno);

-- =============================================================================
-- Fill Data
-- =============================================================================
INSERT INTO emp_part (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO)
SELECT EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO
  FROM emp
 WHERE deptno != 20;
 
COMMIT;

-- =============================================================================
-- add a virtual column
-- =============================================================================
ALTER TABLE emp_part
   ADD total_income GENERATED ALWAYS AS (NVL(SAL,0) + NVL(COMM,0));

-- =============================================================================
-- set a column to be hidden
-- =============================================================================
ALTER TABLE emp_part MODIFY deptno INVISIBLE;

-- =============================================================================
-- set a column unused
-- =============================================================================
ALTER TABLE emp_part SET UNUSED COLUMN job;

If we wanted to load the data of department 20 using an exchange partition command with an existing table holding all those rows we somehow had to make sure that the table used for the exchange has the exact same structure, indexes, constraints, etc. as the partitioned table. The new clause in the create table command help us solving this (at least some of the issues).


Create exchange table:

-- =============================================================================
-- Create exchange table
-- =============================================================================
CREATE TABLE emp_part_exchange
  FOR EXCHANGE WITH TABLE emp_part;

-- =============================================================================
-- Fill exchange table w/o job as it is unused
-- =============================================================================
INSERT INTO emp_part_exchange(EMPNO,ENAME,MGR,HIREDATE,SAL,COMM,DEPTNO)
SELECT EMPNO,ENAME,MGR,HIREDATE,SAL,COMM,DEPTNO
 FROM emp
 WHERE deptno = 20;

-- =============================================================================
-- Check the table
-- =============================================================================
SELECT column_name
      ,data_type || 
       CASE data_type
          WHEN 'VARCHAR2' THEN '(' || data_length || ')'
          WHEN 'NUMBER' THEN NULLIF('(' || data_precision || ',' || data_scale || ')','(,)')
          WHEN 'DATE' THEN NULL
       END AS data_type
      ,nullable
      ,hidden_column
      ,virtual_column
  FROM user_tab_cols
 WHERE table_name = 'EMP_PART_EXCHANGE'
 ORDER BY segment_column_id; 

COLUMN_NAME                    DATA_TYPE       NULL HIDDEN  VIRTUAL
------------------------------ --------------- ---- ------- --------
EMPNO                          NUMBER(4,0)     N    NO      NO 
ENAME                          VARCHAR2(10)    N    NO      NO 
SYS_C00003_16123011:26:29$     VARCHAR2(9)     Y    YES     NO 
MGR                            NUMBER(4,0)     Y    NO      NO 
HIREDATE                       DATE            N    NO      NO 
SAL                            NUMBER (7,2)    Y    NO      NO 
COMM                           NUMBER (7,2)    Y    NO      NO 
DEPTNO                         NUMBER (2,0)    N    YES     NO 
TOTAL_INCOME                   NUMBER (,)      Y    NO      YES 


SELECT constraint_name, constraint_type, search_condition_vc
  FROM user_constraints
 WHERE table_name = 'EMP_PART_EXCHANGE';

CONSTRAINT_NAME                TYPE SEARCH_CONDITION_VC 
------------------------------ ---- --------------------------------------------------
SYS_C008877                    C    "DEPTNO" IS NOT NULL 
SYS_C008878                    C    "EMPNO" IS NOT NULL 
SYS_C008879                    C    "ENAME" IS NOT NULL 
SYS_C008880                    C    "HIREDATE" IS NOT NULL 

SELECT index_name, index_type, partitioned
  FROM user_indexes
 WHERE table_name = 'EMP_PART_EXCHANGE';

no rows selected

Exchange Partition?

So if we did an exchange partition at this point we would fail if the exchange table is not empty…

-- =============================================================================
-- Exchange Partition
-- =============================================================================
ALTER TABLE emp_part 
   EXCHANGE PARTITION p_research 
   WITH TABLE emp_part_exchange; 

Error starting at line : 1 in command -
ALTER TABLE emp_part 
 EXCHANGE PARTITION p_research 
 WITH TABLE emp_part_exchange
Error report -
SQL Error: ORA-14118: CHECK constraint mismatch in ALTER TABLE EXCHANGE PARTITION
14118. 00000 - "CHECK constraint mismatch in ALTER TABLE EXCHANGE PARTITION"
*Cause: The corresponding columns in the tables specified in the
 ALTER TABLE EXCHANGE PARTITION statement have CHECK constraint
 defined on them.
*Action: Ensure that the two tables do not have CHECK constraint
 defined on any column

Additional work to be done:

We still need to create constraints on the exchange table manually…

-- =============================================================================
-- Check constraint are not created but needed when exchanging the partition
-- =============================================================================
ALTER TABLE emp_part_exchange 
   ADD CONSTRAINT chk_sal_comm_less_1000_ep 
   CHECK (NVL(sal,0) + NVL(comm,0) < 10000);
 
ALTER TABLE emp_part_exchange 
   ADD CONSTRAINT chk_empno_ep 
   CHECK (empno between 1000 and 9999);

-- =============================================================================
-- Add primary key constraint as it is not created 
-- =============================================================================
ALTER TABLE emp_part_exchange 
   ADD CONSTRAINT emp_part_pk_ep
   PRIMARY KEY (empno);

After this is done the exchange partition is working.

-- =============================================================================
-- Exchange Partition
-- =============================================================================
ALTER TABLE emp_part 
   EXCHANGE PARTITION p_research 
   WITH TABLE emp_part_exchange; 

Table EMP_PART altered.

Conclusion:

The create table for exchange clause is nice but if would be even better if constraints would also be created on the clone. Furthermore the possibility to do a CTAS in combination with the for exchange clause would be on my wishlist.

Disclaimer

The opinions I express on my blog are my own and not necessarily those of the company I am working for.

Archives

Tags

Analytic Functions CAST Compiler Error Compiler Warning CONNECT BY Data Conversion DATE DEFAULT FIRST_VALUE Identity Column LAST_VALUE LISTAGG Model Clause New Feature ORACLE ORACLE 11g Release 2 ORACLE 12c Release 1 ORACLE 12c Release 2 OUTER JOIN PL/SQL PRAGMA RECURSION Regular Expression ROUNDING Row Pattern Matching Sequence SQL TABIBITOSAN Top-N TO_CHAR TO_DATE TO_NUMBER Truncate Table VALIDATE_CONVERSION VALIDITY WM_CONCAT XMLAGG

Recent Posts

  • ORACLE 23c – Concat with more than 2 parameters
  • Antipatterns SQL & PL/SQL – Redefine oracle defined exceptions
  • ORA12R2 – IMPdp may change segment_column_id
  • Antipatterns SQL & PL/SQL – “SELECT *” even if you only need a few columns
  • Antipatterns SQL & PL/SQL – Substitute NULL with empty string

Blog at WordPress.com.

Loading Comments...