将新值添加到现有ENUM类型

Ian*_*Ian 178 database postgresql enums

我有一个使用enum类型的表列.我希望更新该enum类型以获得额外的可能值.我不想删除任何现有值,只需添加新值.最简单的方法是什么?

Dar*_*usz 356

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';
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这些命令无法在事务中运行. (83认同)
  • 是否可以删除一个? (15认同)
  • 添加到@DrewNoakes'注释,如果您使用的是db-migrate(在事务中运行),那么您可能会收到错误:错误:ALTER TYPE ... ADD无法在事务块内运行此处提到了解决方案(由Hubbitus提供) ):/sf/answers/2918739141/ (7认同)
  • 什么是“enum_type”?字段名称,表字段名称?或者是其他东西?我该怎么打?我有表“成绩”,我有“类型”列,在数据库转储中,我得到了这个:CONSTRAINT Grades_type_check CHECK (((type)::text = ANY ((ARRAY['exam'::character variables, 'test': :character 变化,'extra'::character 变化,'midterm'::character 变化,'final'::character 变化])::text[]))) (3认同)
  • enum_type 只是您自己的枚举类型名称@mariotanenbaum。如果您的枚举是“类型”,那么这就是您应该使用的。 (2认同)
  • @Ced 不是没有删除并重新创建枚举类型。https://www.postgresql.org/docs/9.1/sql-altertype.html 中的注释部分描述了详细信息。 (2认同)
  • 您无法删除它,因此它使陶氏迁移变得不可能,因此必须诉诸其他方法 (2认同)

小智 137

注意如果您使用的是PostgreSQL 9.1或更高版本,请参阅此答案以获得更简单的方法.


前几天我遇到了同样的问题并发现了这篇文章.所以我的回答对寻找解决方案的人有帮助:)

如果您只有一列或两列使用要更改的枚举类型,则可以尝试此操作.您还可以更改新类型中值的顺序.

-- 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;
Run Code Online (Sandbox Code Playgroud)

如果有超过1列,则应重复3-6.

  • 这从来都不是一个好主意.从9.1开始,您可以使用[`ALTER TYPE`](http://www.postgresql.org/docs/9.1/static/sql-altertype.html)完成所有操作.但即便在此之前,`ALTER TABLE foo ALTER COLUMN bar TYPE new_type USING bar :: text :: new_type;`远远优于. (42认同)
  • 您可以将步骤3,4,5和6一起折叠为单个语句:`ALTER TABLE some_table ALTER COLUMN some_column TYPE some_enum_type使用some_column :: text :: some_enum_type;` (12认同)
  • 值得一提的是,这一切都可以在一个事务中完成,因此在生产数据库中进行操作最为安全. (9认同)
  • 如果在实时表上执行此操作,请在此过程中锁定表.postgresql中的默认事务隔离级别不会阻止在此事务期间由其他事务插入新行,因此可能会留下错误填充的行. (3认同)

小智 62

可能的解决方案如下:前提条件是,使用的枚举值中没有冲突.(例如,在删除枚举值时,请确保不再使用此值.)

-- 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__;
Run Code Online (Sandbox Code Playgroud)

同样以这种方式不会更改列顺序.

  • +1 这是 9.1 之前的方式,仍然是删除或修改元素的方式。 (2认同)
  • 如果您的列具有默认值,则会收到以下错误:`列“ my_column”的默认值不能自动转换为键入“ 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';` (2认同)

Hub*_*tus 28

如果您遇到应enum在事务中添加值的情况,请在flyway migration on ALTER TYPEstatement中执行它,您将收到错误ERROR: ALTER TYPE ... ADD cannot run inside a transaction block(请参阅flyway问题#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 )
Run Code Online (Sandbox Code Playgroud)

  • 但是,这将需要授予管理员权限,因为它更改了系统表. (5认同)

小智 18

补充@Dariusz 1

对于Rails 4.2.1,有这个doc部分:

==交易迁移

如果数据库适配器支持DDL事务,则所有迁移将自动包装在事务中.但是有些查询无法在事务中执行,并且在这些情况下,您可以关闭自动事务.

class ChangeEnum < ActiveRecord::Migration
  disable_ddl_transaction!

  def up
    execute "ALTER TYPE model_size ADD VALUE 'new_value'"
  end
end
Run Code Online (Sandbox Code Playgroud)

  • 这个!如果您正在使用现代轨道中的枚举,这正是您所需要的。 (3认同)

Lev*_*sov 18

如果您使用的是 Postgres 12(或更高版本),您可以ALTER TYPE ... ADD VALUE在事务内部运行(文档)。

如果在事务块内执行 ALTER TYPE ... ADD VALUE(向枚举类型添加新值的形式),则在提交事务之前不能使用新值。

所以在迁移中不需要黑客。

UPD:这是一个例子(感谢尼克)

ALTER TYPE enum_type ADD VALUE 'new_value';

  • 是的,例如:“ALTER TYPE enum_type ADD VALUE 'new_value';”谢谢! (2认同)
  • 如何从现有枚举中删除枚举值? (2认同)
  • 另一个问题是您无法在同一交易中使用该新值(如答案中链接的文档中所述)。如果使用 Flyway,您只需为同一版本创建第二个转换文件,因为第二个文件将启动新的事务。 (2认同)

Pey*_*nkh 10

来自Postgres 9.1 文档:

ALTER TYPE name ADD VALUE new_enum_value [ { BEFORE | AFTER } existing_enum_value ]
Run Code Online (Sandbox Code Playgroud)

例:

ALTER TYPE user_status ADD VALUE 'PROVISIONAL' AFTER 'NORMAL'
Run Code Online (Sandbox Code Playgroud)

  • 同样来自文档:涉及增加的枚举值的比较有时会比仅涉及枚举类型的原始成员的比较慢。[....详细信息被删除,因为太长了,以至于stackoverflow评论...]减速通常不明显;但是,如果重要的话,可以通过删除并重新创建枚举类型,或通过转储并重新加载数据库来获得最佳性能。 (3认同)

小智 7

免责声明:我没有尝试过这个解决方案,所以它可能无法正常工作;-)

你应该看看pg_enum.如果您只想更改现有ENUM的标签,则可以使用简单的UPDATE.

要添加新的ENUM值:

  • 首先将新值插入pg_enum.如果新值必须是最后一个,那么你就完成了.
  • 如果不是(您需要在现有的ENUM值之间),您必须更新表中的每个不同值,从最高到最低......
  • 然后你只需要以pg_enum相反的顺序重命名它们.

插图
您有以下标签集:

ENUM ('enum1', 'enum2', 'enum3')
Run Code Online (Sandbox Code Playgroud)

而你想获得:

ENUM ('enum1', 'enum1b', 'enum2', 'enum3')
Run Code Online (Sandbox Code Playgroud)

然后:

INSERT INTO pg_enum (OID, 'newenum3');
UPDATE TABLE SET enumvalue TO 'newenum3' WHERE enumvalue='enum3';
UPDATE TABLE SET enumvalue TO 'enum3' WHERE enumvalue='enum2';
Run Code Online (Sandbox Code Playgroud)

然后:

UPDATE TABLE pg_enum SET name='enum1b' WHERE name='enum2' AND enumtypid=OID;
Run Code Online (Sandbox Code Playgroud)

等等...

  • 完美的工作! (2认同)

edy*_*chk 7

以防万一,如果您使用的是 Rails 并且有多个语句,则需要一一执行,例如:

execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'YYY';"
execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'ZZZ';"
Run Code Online (Sandbox Code Playgroud)

  • “如果不存在”这一点对于我正在做的事情来说是无价的。感谢那。 (3认同)

小智 6

无法将注释添加到适当的位置,但ALTER TABLE foo ALTER COLUMN bar TYPE new_enum_type USING bar::text::new_enum_type在列上使用默认值失败。我不得不:

ALTER table ALTER COLUMN bar DROP DEFAULT;

然后就成功了。


Jos*_*iah 5

我似乎无法发表评论,所以我只是说更新pg_enum适用于Postgres 8.4.对于我们的枚举设置方式,我通过以下方式为现有枚举类型添加了新值:

INSERT INTO pg_enum (enumtypid, enumlabel)
  SELECT typelem, 'NEWENUM' FROM pg_type WHERE
    typname = '_ENUMNAME_WITH_LEADING_UNDERSCORE';
Run Code Online (Sandbox Code Playgroud)

这有点可怕,但鉴于Postgres实际存储数据的方式,这是有道理的.

  • ["一般来说,你应该尽量避免手动破坏目录......像这样破解系统表."](http://www.depesz.com/2010/10/27/waiting-for-9-1 -adding-values-to-enums /)安德鲁邓斯坦说,我倾向于认为他是对的. (3认同)

Den*_*rdy 5

更新pg_enum的工作原理与上面突出显示的中间列技巧一样.也可以使用USING magic直接更改列的类型:

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;
Run Code Online (Sandbox Code Playgroud)

只要你没有明确要求或返回枚举的函数,你就会很好.(如果丢弃类型,pgsql会抱怨.)

另请注意,PG9.1引入了一个ALTER TYPE语句,该语句适用于枚举:

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