はじめに
にゃーん
この記事は、PostgreSQL 10全部ぬこ Advent Calendar 2017 の3日目のエントリです。
ENUMとは
ENUMとはENUMERATION(列挙)型は値域を規定する一種のデータタイプ。例えば、神奈川県の市という内容のkanagawa_cityという列挙型を定義してみる。
enum=# CREATE TYPE kanagawa_city AS ENUM ('横浜','川崎','相模原','町田','横須賀');
CREATE TYPE
enum=#
そしてこのkanagawa_city型の列を含むテーブルを作成する。
enum=# CREATE TABLE test (id int, city kanagawa_city);
CREATE TABLE
enum=# \d test
Table "public.test"
Column | Type | Collation | Nullable | Default
--------+---------------+-----------+----------+---------
id | integer | | |
city | kanagawa_city | | |
このtestテーブルのcity列にENUMで列挙した値域以外を設定すると怒られる。
(横浜や町田はkanagawa_cityの値域なので挿入や更新ができるが、立川や八王子はkanagawa_cityの値域ではないのでエラーになる)
enum=# INSERT INTO test VALUES (1, '横浜');
INSERT 0 1
enum=# INSERT INTO test VALUES (2, '立川');
ERROR: invalid input value for enum kanagawa_city: "立川"
LINE 1: INSERT INTO test VALUES (2, '立川');
^
enum=# UPDATE test SET city = '町田';
UPDATE 1
enum=# UPDATE test SET city = '八王子';
ERROR: invalid input value for enum kanagawa_city: "八王子"
LINE 1: UPDATE test SET city = '八王子';
データベース側で列値を規定するという機能だが、「SQLアンチパターン」の「31のフレーバー」の章でも挙げられているように、利用時にはちょいと検討が必要な機能でもある。
参考:PostgreSQLでの管理方法
PostgreSQLではENUMをpg_enumというシステムカタログで管理している。
例えば、さっきのkanagawa_cityという列挙型は以下のようにpg_enumに格納されている。
enum=# TABLE pg_enum;
enumtypid | enumsortorder | enumlabel
-----------+---------------+-----------
16396 | 1 | 横浜
16396 | 2 | 川崎
16396 | 3 | 相模原
16396 | 4 | 町田
16396 | 5 | 横須賀
(5 rows)
enumsortorder に注意。ソートするときにはここの値でソートされ、enumlabel の値ではソートされない。
ENUM値の変更(PostgreSQL 9.6まで)
さて、CREATE TYPEで作成したENUMの内容を変更したい、というケースもあるかもしれない。
- 列挙値を追加したい
- 列挙値を削除したい
- 列挙値のラベルを変更したい。
PostgreSQL 9.6までは列挙値の追加のみ対応していた。
enum=# CREATE TYPE kanagawa_city AS ENUM ('横浜','川崎','相模原','横須賀');
CREATE TYPE
kanagawa_city型の値域として、町田を追加する。
enum=# ALTER TYPE kanagawa_city ADD VALUE '町田';
ALTER TYPE
enum=# TABLE pg_enum;
enumtypid | enumsortorder | enumlabel
-----------+---------------+-----------
41868 | 1 | 横浜
41868 | 2 | 川崎
41868 | 3 | 相模原
41868 | 4 | 横須賀
41868 | 5 | 町田
(5 rows)
町田がkanagawa_cityに追加された。町田は神奈川。
なお、PostgreSQL 9.6までは、一度追加したラベルの内容を変更することはできない。
また、ラベルの削除もできない。これがENUMは変更に弱い、という問題に繋がると。
もしラベルを変更したければ、
- この列挙型を使っている全テーブルをCOPY等でEXPORT、
- この列挙型を使っている全テーブルをDROP
- DROP TYPE ... CASCADEで列挙型を削除
- ラベルを変更した列挙型をCREATE TYPEで再生成
- この列挙型を使っていた全テーブルをCREATE TABLEで再生性
- EXPORTしたデータを再ロード
という手順が必要になり、ちょっと実運用ではやってられんよなあと。
PostgreSQL 10での改善
PostgreSQL 10では、列挙型に追加ずみのラベルを変更可能になった。
現在のkanagawa_city型の列挙値はこんな感じ。
enum=# TABLE pg_enum;
enumtypid | enumsortorder | enumlabel
-----------+---------------+-----------
41868 | 1 | 横浜
41868 | 2 | 川崎
41868 | 3 | 相模原
41868 | 4 | 横須賀
41868 | 5 | 町田
(5 rows)
で、この列挙型を使った列を定義して、データを格納する。
enum=# CREATE TABLE test (id int primary key, data kanagawa_city);
CREATE TABLE
enum=# INSERT INTO test VALUES (1, '横浜');
INSERT 0 1
enum=# TABLE test;
id | data
----+------
1 | 横浜
(1 row)
ここで、「横浜」というラベルを「横濱」に変更する。
enum=# ALTER TYPE kanagawa_city RENAME VALUE '横浜' TO '横濱';
ALTER TYPE
enum=# TABLE test;
id | data
----+------
1 | 横濱
(1 row)
ALTER TYPEの終了後にtestテーブルを参照すると、「横浜」は「横濱」に変更されている。
おわりに
町田は神奈川。
PostgreSQL 10ではENUMのラベル変更を大きなコストをかけずに実施できるようになった。
これでENUMの「変更に弱い」という問題点の一部は解決できそう。
残念ながらラベルの削除は未対応。つか、既に使用されているラベルの削除したらどうするんだ、みたいな仕様上の問題の整理から必要なんだろうなあ。
参考:該当するリリースノート
本エントリに関連するPostgreSQL 10リリースノートの記載です。
E.2.3.5. Data Types
- Allow ENUM values to be renamed (Dagfinn Ilmari Mannsaker)