非パーティション表をパーティション表に変更する（Oracle12.2）

# はじめに

Oracle 12.1では、非パーティション表をパーティション表に変更する場合に以下の方法がありました。

- CTAS(CREATE TABLE AS SELECT)で新しいパーティション表を作成し、テーブル名をリネームする
- EXCHANGE PARTITIONを使用する
- DBMS_REDEFINITIONを使用する

いずれの方法も操作が複雑であり、操作ミスや作業漏れが発生しやすい方法です。
Oracle 12.2ではALTER TABLE文にて1コマンドで簡単に非パーティション表をパーティション表に変更することができるようになっています。しかもオンラインでも実行できるため、変換中でもDML操作が可能です。 
ただし、表領域を変えたいなど他にも要件がある場合は、別の方法を利用するか、組み合わせることになります。

パーティション表に変換した場合、元々作成していたインデックスは以下のように変換されます。
テーブルばかりに気をとられて、インデックスはグローバル非パーティションインデックスのままでしたなんてことがないように注意が必要です（意図しているなら別ですが）

- 非同一キーインデックスは、グローバル非パーティションインデックスになります。
- 同一キーインデックスは、ローカルパーティションインデックスに変換されます。
- ビットマップインデックスは、ローカルパーティションインデックスになります。


# 変換例

非パーティション表をパーティション表に変換する例を以降に示します。

まずテストの事前準備として、テスト用のテーブルとインデックスを以下のように作成しました。
なお、例のためテーブル、インデックス、パーティションは適当です（良い構成ではない）。

```sql
drop table p_test purge;
create table p_test (
  id number,
  pref_id number,
  pref varchar(10),
  customer_id number,
  created_at timestamp,
  constraint pk_id primary key(id)
);

insert into p_test values(1, 1, 'HOKKAIDO', 100, '2019-12-07');
insert into p_test values(2, 20, 'NAGANO', 200, '2019-12-07');
insert into p_test values(3, 20, 'NAGANO', 201, '2019-11-07');
insert into p_test values(4, 40, 'FUKUOKA', 300, '2019-12-07');

commit;

create index idx_pref_id on p_test(pref_id);
create index idx_created_dt on p_test(created_at);
create index idx_pref on p_test(pref);
```


## レンジパーティションへ変換

online句をつけることで、変換中もDMLを実行できます。
update indexesはなくても変換できますが、インデックスの種類を指定したい場合は記載します。

```sql
alter table p_test modify
  partition by range(created_at) (
    partition r_part_2019_11 values less than (to_date('2019-12-01', 'yyyy-mm-dd')),
    partition r_part_2019_12 values less than (to_date('2020-01-01', 'yyyy-mm-dd'))
  ) online
  update indexes
  (
    pk_id global,
	idx_pref_id local
  );
```

インデックスを確認すると以下のようになります。
idx_pref_idはlocalを指定したのでローカルパーティションインデックス、created_atはパーティションキーなのでローカルパーティションインデックスになっています。

```sql
SQL> select index_name, table_name, partitioning_type, subpartitioning_type, partition_count, locality from user_part_indexes where table_name = 'P_TEST';

INDEX_NAME           TABLE_NAME           PARTITIONING_TYPE    SUBPARTITIONING_TYPE PARTITION_COUNT LOCALI
-------------------- -------------------- -------------------- -------------------- --------------- ------
IDX_PREF_ID          P_TEST               RANGE                NONE                               2 LOCAL
IDX_CREATED_DT       P_TEST               RANGE                NONE                               2 LOCAL
```

なお、「pk_id local」と指定するとエラーになります。

```
ORA-14039: パーティション化列はUNIQUE索引のキー列のサブセットを構成する必要があります
```

これは一意性を担保できないからで、「constraint pk_id primary key(id, created_at)」のように主キーにパーティションキーを含むようにすればエラーとなりません。


## リストパーティションへ変換


```sql
alter table p_test modify
  partition by list(pref) (
    partition l_part_hokkaido values ('HOKKAIDO'),
    partition l_part_nagano values ('NAGANO'),
	partition l_part_fukuoka values ('FUKUOKA')
  ) online
  update indexes
  (
    pk_id global,
	idx_pref_id local
  );
```

インデックスを確認すると以下のようになります。
idx_pref_idはlocalを指定したのでローカルパーティションインデックス、prefはパーティションキーなのでローカルパーティションインデックスになっています。

```sql
SQL> select index_name, table_name, partitioning_type, subpartitioning_type, partition_count, locality from user_part_indexes where table_name = 'P_TEST';

INDEX_NAME           TABLE_NAME           PARTITIONING_TYPE    SUBPARTITIONING_TYPE PARTITION_COUNT LOCALI
-------------------- -------------------- -------------------- -------------------- --------------- ------
IDX_PREF_ID          P_TEST               LIST                 NONE                               3 LOCAL
IDX_PREF             P_TEST               LIST                 NONE                               3 LOCAL
```


## ハッシュパーティションへ変換

```sql
alter table p_test modify
  partition by hash(id) (
    partition h_part1,
    partition h_part2,
	partition h_part3,
	partition h_part4
  ) online;
```

インデックスを確認すると以下のようになります。
idx_idはパーティションキーなのでローカルパーティションインデックスになっています。

```sql
SQL> select index_name, table_name, partitioning_type, subpartitioning_type, partition_count, locality from user_part_indexes where table_name = 'P_TEST';

INDEX_NAME           TABLE_NAME           PARTITIONING_TYPE    SUBPARTITIONING_TYPE PARTITION_COUNT LOCALI
-------------------- -------------------- -------------------- -------------------- --------------- ------
PK_ID                P_TEST               HASH                 NONE                               4 LOCAL
```


## インターバルパーティションへ変換

インターバルパーティションは事前に指定したインターバルに応じて、レコード挿入時に自動でパーティションを作成します。


```sql
alter table p_test modify
  partition by range(created_at)
  interval(numtoyminterval(1, 'MONTH'))
  (
    partition p1 values less than (to_date('2019-10-1', 'yyyy-mm-dd'))
  ) online
  update indexes
  (
    pk_id global,
	idx_pref_id local
  );
```

インデックスを確認すると以下のようになります。
レンジパーティションと同様に、idx_pref_idとcreated_atはローカルパーティションインデックスになっています。

```sql
SQL> select index_name, table_name, partitioning_type, subpartitioning_type, partition_count, locality from user_part_indexes where table_name = 'P_TEST';

INDEX_NAME           TABLE_NAME           PARTITIONING_TYPE    SUBPARTITIONING_TYPE PARTITION_COUNT LOCALI
-------------------- -------------------- -------------------- -------------------- --------------- ------
IDX_PREF_ID          P_TEST               RANGE                NONE                               2 LOCAL
IDX_CREATED_DT       P_TEST               RANGE                NONE                               2 LOCAL
```


## コンポジットパーティション（レンジ＋ハッシュ）へ変換


```sql
alter table p_test modify
  partition by range(created_at) subpartition by hash(customer_id) (
    partition r_part_2019_11 values less than (to_date('2019-12-01', 'yyyy-mm-dd')) (
      subpartition h_part1_2019_11,
      subpartition h_part2_2019_11,
	  subpartition h_part3_2019_11,
	  subpartition h_part4_2019_11
	),
    partition r_part_2019_12 values less than (to_date('2020-01-01', 'yyyy-mm-dd')) (
      subpartition h_part1_2019_12,
      subpartition h_part2_2019_12,
	  subpartition h_part3_2019_12,
	  subpartition h_part4_2019_12
	)
  ) online
  update indexes
  (
    pk_id global,
	idx_pref_id local
  );
```

# 参考

- [Oracle Partitioningの能力を最大限に活用する実践ガイドとリファレンス](https://www.oracle.com/technetwork/jp/database/partitioningguidev43-2703320-ja.pdf)
- [パーティション表への非パーティション表の変換(12.2)](https://docs.oracle.com/cd/E82638_01/vldbg/evolve-nopartition-table.html#GUID-5FDB7D59-DD05-40E4-8AB4-AF82EA0D0FE5)
- [パーティション表への非パーティション表の変換(19c)](https://docs.oracle.com/cd/F19136_01/vldbg/evolve-nopartition-table.html#GUID-6054142E-207A-4DF0-A62A-4C1A94DD36C4)
- [Extremely useful Partitioning enhancements in Oracle Database 12c](
https://sqlmaria.com/2017/05/16/extremely-useful-partitioning-enhancements-in-oracle-database-12c/)