既存のENUMタイプに新しい値を追加する


208

を使用するテーブル列があります enumタイプ。enum追加の可能な値を持つようにそのタイプを更新したいと思います。既存の値を削除するのではなく、新しい値を追加するだけです。これを行う最も簡単な方法は何ですか?

回答:


153

あなたは、PostgreSQL 9.1以降を使用している、とあなたが外取引の変更を行うと、OKであれば、参照この回答を単純なアプローチのために。


私は数日前に同じ問題を抱えていて、この投稿を見つけました。だから私の答えは解決策を探している人のために役立ちます:)

変更する列挙型を使用する列が1つまたは2つしかない場合は、これを試すことができます。また、新しいタイプの値の順序を変更することもできます。

-- 1. rename the enum type you want to change
alter type some_enum_type rename to _some_enum_type;
-- 2. create new type
create type some_enum_type as enum ('old', 'values', 'and', 'new', 'ones');
-- 3. rename column(s) which uses our enum type
alter table some_table rename column some_column to _some_column;
-- 4. add new column of new type
alter table some_table add some_column some_enum_type not null default 'new';
-- 5. copy values to the new column
update some_table set some_column = _some_column::text::some_enum_type;
-- 6. remove old column and type
alter table some_table drop column _some_column;
drop type _some_enum_type;

列が複数ある場合は、3〜6を繰り返す必要があります。


9
これはすべて1つのトランザクションで実行できるため、本番データベースで実行するのがほとんど安全です。
David Leppik

52
これは決して良い考えではありませんでした。9.1以降では、すべてをで実行できますALTER TYPE。しかし、その前でさえ、ALTER TABLE foo ALTER COLUMN bar TYPE new_type USING bar::text::new_type;はるかに優れていました。
Erwin Brandstetter、2011年

1
古いバージョンのPostgresはタイプの名前変更をサポートしていないことに注意してください。具体的には、Heroku上のPostgresのバージョン(共有db、PG 8.3を使用していると思います)はそれをサポートしていません。
Ortwin Gentz、2011年

13
あなたは、単一のステートメントにステップ3、4、5、6、一緒に折りたたむことができます:ALTER TABLE some_table ALTER COLUMN some_column TYPE some_enum_type USING some_column::text::some_enum_type;
glyphobet

3
ライブテーブルでこれを行う場合は、手順中にテーブルをロックしてください。postgresqlのデフォルトのトランザクション分離レベルでは、このトランザクション中に他のトランザクションによって新しい行が挿入されるのを防ぐことはできないため、誤って入力された行が残る場合があります。
セルジオカルバリョ

422

PostgreSQLの9.1への紹介の能力ALTER enum型:

ALTER TYPE enum_type ADD VALUE 'new_value'; -- appends to list
ALTER TYPE enum_type ADD VALUE 'new_value' BEFORE 'old_value';
ALTER TYPE enum_type ADD VALUE 'new_value' AFTER 'old_value';

1
「enum_type」とは何ですか?フィールド名、テーブルフィールド名?または、他の何か?どうすればいいですか?テーブル「グレード」があり、列「タイプ」があり、データベースダンプで次のように取得します。 :文字が変化する、 '追加' ::文字が変化する、 '中間' ::文字が変化する、 '最終' ::文字が変化する]):: text [])))

1
enum_typeは、独自の列挙型名@mariotanenbaumです。あなたの列挙型が「タイプ」であるなら、これはあなたが使うべきものです。
Dariusz 2014

26
1つを削除することは可能ですか?
Ced

8
@DrewNoakesのコメントに追加して、db-migrate(トランザクションで実行される)を使用している場合は、エラーが発生する可能性があります。エラー:ALTER TYPE ... ADDはトランザクションブロック内では実行できません):stackoverflow.com/a/41696273/1161370
Mahesh

1
あなたはそれを取り除くことができないのでダウの移動を不可能にするので他の方法に頼らなければなりません
Muhammad Umer

65

可能な解決策は次のとおりです。前提条件は、使用される列挙値に競合がないことです。(たとえば、列挙値を削除するときは、この値が使用されていないことを確認してください。)

-- rename the old enum
alter type my_enum rename to my_enum__;
-- create the new enum
create type my_enum as enum ('value1', 'value2', 'value3');

-- alter all you enum columns
alter table my_table
  alter column my_column type my_enum using my_column::text::my_enum;

-- drop the old enum
drop type my_enum__;

この方法でも、列の順序は変更されません。


1
+1これは9.1より前の方法であり、要素を削除または変更する方法です。

これは、既存の列挙型に新しい列挙型を追加する私のソリューションの断然最良の答えです。この場合、古い列挙型をすべて保持し、新しい列挙型を追加します。さらに、更新スクリプトはトランザクション対応です。素晴らしいポスト!
Darin Peterson

1
素晴らしい答え!pg_enumとは異なり、実際に物事を破壊する可能性があり、トランザクションであるハックを回避しますALTER TYPE ... ADD
NathanAldenSr 2015年

4
列にデフォルト値がある場合、次のエラーを受け取りますdefault for column "my_column" cannot be cast automatically to type "my_enum"。次のことを行う必要があります ALTER TABLE "my_table" ALTER COLUMN "my_column" DROP DEFAULT, ALTER COLUMN "my_column" TYPE "my_type" USING ("my_column"::text::"my_type"), ALTER COLUMN "my_column" SET DEFAULT 'my_default_value';
。– n1ru4l

30

enumトランザクションで値を追加する必要がある状況に陥った場合、ALTER TYPEステートメントのフライウェイマイグレーションでそれを実行すると、エラーが発生しますERROR: ALTER TYPE ... ADD cannot run inside a transaction blockフライウェイの問題#350を参照)。pg_enum回避策としてそのような値を直接追加できます(type_egais_unitsはターゲットの名前ですenum)。

INSERT INTO pg_enum (enumtypid, enumlabel, enumsortorder)
    SELECT 'type_egais_units'::regtype::oid, 'NEW_ENUM_VALUE', ( SELECT MAX(enumsortorder) + 1 FROM pg_enum WHERE enumtypid = 'type_egais_units'::regtype )

9
ただし、システムテーブルを変更するため、管理者権限を付与する必要があります。
asnelzin

22

@Dariuszを補完する1

Rails 4.2.1の場合、このドキュメントセクションがあります。

==トランザクションの移行

データベースアダプターがDDLトランザクションをサポートしている場合、すべての移行は自動的にトランザクションにラップされます。ただし、トランザクション内では実行できないクエリがあり、これらの状況では自動トランザクションをオフにできます。

class ChangeEnum < ActiveRecord::Migration
  disable_ddl_transaction!

  def up
    execute "ALTER TYPE model_size ADD VALUE 'new_value'"
  end
end

3
この!現代のレールで列挙型で遊んでいる場合、これはまさにあなたが探しているものです。
イーライアルバート

1
素晴らしい、たくさん助けてくれました!
Dmytro Uhnichenko

10

Postgres 9.1 ドキュメントから:

ALTER TYPE name ADD VALUE new_enum_value [ { BEFORE | AFTER } existing_enum_value ]

例:

ALTER TYPE user_status ADD VALUE 'PROVISIONAL' AFTER 'NORMAL'

3
また、ドキュメントから:追加された列挙値を含む比較は、列挙型の元のメンバーのみを含む比較よりも遅くなる場合があります。[.... stackoverflowコメントには長すぎるので詳細を省略...]スローダウンは通常重要ではありません。ただし、問題がある場合は、列挙型を削除して再作成するか、データベースをダンプして再ロードすることで、最適なパフォーマンスを回復できます。
アーロンジンマン2013年

8

免責事項:私はこの解決策を試していないので、うまくいかないかもしれません;-)

あなたは見ている必要がありますpg_enum。既存のENUMのラベルのみを変更したい場合は、単純なUPDATEで変更できます。

新しいENUM値を追加するには:

  • 最初に新しい値を挿入します pg_enumます。新しい値を最後にする必要がある場合は、これで完了です。
  • そうでない場合(既存の値の間に新しいENUM値を入れる必要がある場合)、テーブル内の個別の値をそれぞれ上から下に更新する必要があります...
  • 次にpg_enum、逆の順序で名前を変更する必要があります。

イラスト
次のラベルセットがあります。

ENUM ('enum1', 'enum2', 'enum3')

取得したいもの:

ENUM ('enum1', 'enum1b', 'enum2', 'enum3')

次に:

INSERT INTO pg_enum (OID, 'newenum3');
UPDATE TABLE SET enumvalue TO 'newenum3' WHERE enumvalue='enum3';
UPDATE TABLE SET enumvalue TO 'enum3' WHERE enumvalue='enum2';

次に:

UPDATE TABLE pg_enum SET name='enum1b' WHERE name='enum2' AND enumtypid=OID;

等々...



5

私はコメントを投稿することができないようですので、pg_enumの更新はPostgres 8.4で機能すると言います。列挙型の設定方法について、次の方法で既存の列挙型に新しい値を追加しました。

INSERT INTO pg_enum (enumtypid, enumlabel)
  SELECT typelem, 'NEWENUM' FROM pg_type WHERE
    typname = '_ENUMNAME_WITH_LEADING_UNDERSCORE';

少し怖いですが、Postgresが実際にデータを保存する方法を考えると、それは理にかなっています。


1
正解です。新しい列挙型に追加するためだけに役立ちますが、再注文する必要がある場合は明らかに解決しません。
Mahmoud Abdelkader


typenameの先頭のアンダースコアに加えて、大文字と小文字も区別されます。pg_typeテーブルからtypenameで選択しようとすることに、ほとんど気を失いました。
Mahesh、

5

pg_enumの更新は、上記で強調された中間列のトリックと同様に機能します。USINGマジックを使用して、列のタイプを直接変更することもできます。

CREATE TYPE test AS enum('a', 'b');
CREATE TABLE foo (bar test);
INSERT INTO foo VALUES ('a'), ('b');

ALTER TABLE foo ALTER COLUMN bar TYPE varchar;

DROP TYPE test;
CREATE TYPE test as enum('a', 'b', 'c');

ALTER TABLE foo ALTER COLUMN bar TYPE test
USING CASE
WHEN bar = ANY (enum_range(null::test)::varchar[])
THEN bar::test
WHEN bar = ANY ('{convert, these, values}'::varchar[])
THEN 'c'::test
ELSE NULL
END;

その列挙型を明示的に要求または返す関数がない限り、問題はありません。(存在する場合、タイプをドロップするとpgsqlは文句を言うでしょう。)

また、PG9.1は、列挙型で機能するALTER TYPEステートメントを導入していることに注意してください。

http://developer.postgresql.org/pgdocs/postgres/release-9-1-alpha.html


PostgreSQL 9.1の関連ドキュメントは、postgresql.org / docs / 9.1 / static / sql-altertype.htmlにあります。
docs /

1
ALTER TABLE foo ALTER COLUMN bar TYPE test USING bar::text::new_type;しかし、現在はほとんど関係ありません...
Erwin Brandstetter

アーウィンが言ったことと同様に、 ... USING bar::type、私のため働いた。私も指定する必要はありませんでした::text
Daniel Werner

3

最も簡単:列挙型を削除します。彼らは簡単に変更できませんので、必要がある非常にまれに使用すること。


2
おそらく単純なチェック制約で十分でしょうか?

1
そして、正確に値を文字列として保存する問題は何ですか?

5
@Grazer:9.1では、列挙型に値を追加できます(depesz.com/index.php/2010/10/27/…)-ただし、古い値を削除することはできません。

3
@WillSheppard - 私は基本的には決してそれを考えていません。どんな場合でも、チェック制約付きのテキストに基づくカスタムタイプは、はるかに優れていると思います。

3
@JackDouglas-確かに。いつでもチェックオーバー列挙型でドメインを取得します。

3

適切な場所にコメントを追加できませんがALTER TABLE foo ALTER COLUMN bar TYPE new_enum_type USING bar::text::new_enum_type、列のデフォルトでは失敗しました。そうしなければならなかった:

ALTER table ALTER COLUMN bar DROP DEFAULT;

そしてそれはうまくいった。


3

念のため、Railsを使用していて、いくつかのステートメントがある場合は、次のように1つずつ実行する必要があります。

execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'YYY';"
execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'ZZZ';"

1

これはより一般的ですが、かなり高速に動作するソリューションです。タイプ自体の変更は別として、それを使用してデータベースのすべての列を更新します。この方法は、ENUMの新しいバージョンが複数のラベルで異なる場合や、元のラベルの一部を逃した場合でも適用できます。以下のコードはに置き換えmy_schema.my_type AS ENUM ('a', 'b', 'c')られENUM ('a', 'b', 'd', 'e')ます:

CREATE OR REPLACE FUNCTION tmp() RETURNS BOOLEAN AS
$BODY$

DECLARE
    item RECORD;

BEGIN

    -- 1. create new type in replacement to my_type
    CREATE TYPE my_schema.my_type_NEW
        AS ENUM ('a', 'b', 'd', 'e');

    -- 2. select all columns in the db that have type my_type
    FOR item IN
        SELECT table_schema, table_name, column_name, udt_schema, udt_name
            FROM information_schema.columns
            WHERE
                udt_schema   = 'my_schema'
            AND udt_name     = 'my_type'
    LOOP
        -- 3. Change the type of every column using my_type to my_type_NEW
        EXECUTE
            ' ALTER TABLE ' || item.table_schema || '.' || item.table_name
         || ' ALTER COLUMN ' || item.column_name
         || ' TYPE my_schema.my_type_NEW'
         || ' USING ' || item.column_name || '::text::my_schema.my_type_NEW;';
    END LOOP;

    -- 4. Delete an old version of the type
    DROP TYPE my_schema.my_type;

    -- 5. Remove _NEW suffix from the new type
    ALTER TYPE my_schema.my_type_NEW
        RENAME TO my_type;

    RETURN true;

END
$BODY$
LANGUAGE 'plpgsql';

SELECT * FROM tmp();
DROP FUNCTION tmp();

ラベルの順序が変わらない場合、データの実際の変更は発生しないため、プロセス全体はかなり迅速に実行されます。このメソッドを5つのテーブルに適用し、my_typeそれぞれ50,000〜70,000行を使用して、プロセス全体でわずか10秒かかりました。

もちろん、ENUMの新しいバージョンで欠落しているラベルがデータのどこかで使用されている場合、関数は例外を返しますが、そのような状況では、とにかく事前に何かを行う必要があります。


これは本当に価値があります。ただし、問題は古いENUMを使用するビューにあります。それらはドロップして再作成する必要がありますが、ドロップしたビューに応じて他のビューを考慮すると、はるかに複雑になります。...複合タイプについて話していない
オンドレイBouda

1

トランザクション内のソリューションを探している人にとって、次のように機能するようです。

の代わりにENUM、a DOMAINTEXT、値が指定された許可された値のリスト内にあることをチェックする制約付きの型で使用されます(一部のコメントで提案されています)。唯一の問題は、複合型で使用されている場合、ドメインに制約を追加できない(したがって、変更できない)ことです(ドキュメントでは、これは「最終的には改善する必要がある」と述べています)。ただし、このような制限は、次のように関数を呼び出す制約を使用して回避できます。

START TRANSACTION;

CREATE FUNCTION test_is_allowed_label(lbl TEXT) RETURNS BOOL AS $function$
    SELECT lbl IN ('one', 'two', 'three');
$function$ LANGUAGE SQL IMMUTABLE;

CREATE DOMAIN test_domain AS TEXT CONSTRAINT val_check CHECK (test_is_allowed_label(value));

CREATE TYPE test_composite AS (num INT, word test_domain);

CREATE TABLE test_table (val test_composite);
INSERT INTO test_table (val) VALUES ((1, 'one')::test_composite), ((3, 'three')::test_composite);
-- INSERT INTO test_table (val) VALUES ((4, 'four')::test_composite); -- restricted by the CHECK constraint

CREATE VIEW test_view AS SELECT * FROM test_table; -- just to show that the views using the type work as expected

CREATE OR REPLACE FUNCTION test_is_allowed_label(lbl TEXT) RETURNS BOOL AS $function$
    SELECT lbl IN ('one', 'two', 'three', 'four');
$function$ LANGUAGE SQL IMMUTABLE;

INSERT INTO test_table (val) VALUES ((4, 'four')::test_composite); -- allowed by the new effective definition of the constraint

SELECT * FROM test_view;

CREATE OR REPLACE FUNCTION test_is_allowed_label(lbl TEXT) RETURNS BOOL AS $function$
    SELECT lbl IN ('one', 'two', 'three');
$function$ LANGUAGE SQL IMMUTABLE;

-- INSERT INTO test_table (val) VALUES ((4, 'four')::test_composite); -- restricted by the CHECK constraint, again

SELECT * FROM test_view; -- note the view lists the restricted value 'four' as no checks are made on existing data

DROP VIEW test_view;
DROP TABLE test_table;
DROP TYPE test_composite;
DROP DOMAIN test_domain;
DROP FUNCTION test_is_allowed_label(TEXT);

COMMIT;

以前は、受け入れられた回答と同様のソリューションを使用していましたが、ビューまたは関数または複合タイプ(特に、変更されたENUMを使用して他のビューを使用するビュー...)を検討した後は、それほど良い方法ではありません。この回答で提案されているソリューションは、どのような状況でも機能するようです。

唯一の欠点は、いくつかの許可された値が削除されたときに既存のデータに対してチェックが実行されないことです(これは、特にこの質問では許容できる場合があります)。(ALTER DOMAIN test_domain VALIDATE CONSTRAINT val_check残念ながら、を呼び出すと、複合型で使用されるドメインに新しい制約を追加するのと同じエラーが発生します。)

関数が許可された値のリストを返すようなのようなわずかな変更CHECK (value = ANY(get_allowed_values()))get_allowed_values()機能しないことに注意してください-これは非常に奇妙です。(実際に機能します-それは私のエラーでした)


0

上記のように、ALTERトランザクション内でコマンドを書き込むことはできません。推奨される方法は、retrieving the typelem from pg_type tableおよびによってpg_enumテーブルに直接挿入することcalculating the next enumsortorder numberです。

以下は私が使用するコードです。(挿入する前に重複する値が存在するかどうかを確認します(enumtypidとenumlabel名の間の制約)

INSERT INTO pg_enum (enumtypid, enumlabel, enumsortorder)
    SELECT typelem,
    'NEW_ENUM_VALUE',
    (SELECT MAX(enumsortorder) + 1 
        FROM pg_enum e
        JOIN pg_type p
        ON p.typelem = e.enumtypid
        WHERE p.typname = '_mytypename'
    )
    FROM pg_type p
    WHERE p.typname = '_mytypename'
    AND NOT EXISTS (
        SELECT * FROM 
        pg_enum e
        JOIN pg_type p
        ON p.typelem = e.enumtypid
        WHERE e.enumlabel = 'NEW_ENUM_VALUE'
        AND p.typname = '_mytypename'
    )

pg_typeテーブルでは、タイプ名の前にアンダースコアが付いていることに注意してください。また、typnameはwhere句ですべて小文字にする必要があります。

これで、db migrateスクリプトに安全に書き込むことができます。


-1

他のオプションがあるかどうかはわかりませんが、次のようにして値を削除できます。

select oid from pg_type where typname = 'fase';'
select * from pg_enum where enumtypid = 24773;'
select * from pg_enum where enumtypid = 24773 and enumsortorder = 6;
delete from pg_enum where enumtypid = 24773 and enumsortorder = 6;

-2

Navicatを使用すると、タイプ(ビュー->その他->タイプ)に移動し、タイプのデザインビューを取得して、[ラベルの追加]ボタンをクリックできます。


1
:いいですが、実際の生活の中で、それは便利ではないでしょうERROR: cannot drop type foo because other objects depend on it HINT: Use DROP ... CASCADE to drop the dependent objects too.
Ortwin Gentz

奇妙なことに、それは私にとってはうまくいった。(TSがenumフィールドに値を追加したいだけのときにDROPを使用する理由がわからない)
jvv

1
私は特にDROPをしませんでしたが、あなたの手順の後に正確に行きました。NavicatがバックグラウンドでDROPを実行して失敗したと思います。Navicat 9.1.5 Liteを使用しています。
Ortwin Gentz、2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.