PostgreSQL – 更新枚举types

我有一个使用枚举types的字段。 我希望更新枚举有一个额外的字段(我不想删除任何东西,只是添加一个新的标签)。 什么是最简单的方法来做到这一点?

注意如果你使用的是PostgreSQL 9.1或更高版本,请参阅这个答案来获得更简单的方法。


我前几天遇到了同样的问题,发现这个post。 所以我的答案可以帮助正在寻找解决scheme的人:)

如果您只有一列或两列使用您想要更改的枚举types,则可以尝试此操作。 你也可以改变新types的值的顺序。

-- 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; 

如果列数超过1列,应重复3-6。

PostgreSQL 9.1引入了ALTER Enumtypes的能力:

 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'; 

一个可能的解决scheme如下: 先决条件是,所使用的枚举值没有冲突。 (例如,当删除一个枚举值时,确保这个值不再被使用。)

 -- rename the old enum alter type old_enum rename to old_enum__; -- create the new enum create type new_enum as enum ('value1', 'value2', 'value3'); -- alter all you enum columns alter table my_table alter column my_column type new_enum using my_column::text::new_enum; -- drop the old enum drop type old_enum__; 

也是这样,列顺序不会改变。

如果在事务中添加enum值的情况下,在ALTER TYPE语句上的flyway迁移中执行它将会出错ERROR: ALTER TYPE ... ADD cannot run inside a transaction block (请参阅flyway issue#350 )您可以直接将这些值作为变通方法( type_egais_units是目标enum名称)添加到pg_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 ) 

免责声明:我没有试过这个解决scheme,所以它可能只是没有工作;-)

你应该看看pg_enum。 如果你只想改变现有的枚举的标签,一个简单的更新将做到这一点。

添加一个新的枚举值:首先将新值插入到pg_enum中。 如果新的价值应该是最后的,那么你就完成了。 如果没有(你需要一个新的枚举值在现有的值之间),你将不得不更新表中每个不同的值,从“最大”到最低…在你只需要重命名在pg_enum中以相反的顺序。

插图:你有enum('enum1','enum2','enum3'),你想要去('enum1','enum1b','enum2',enum3')插入到pg_enum(OID,'newenum3') ; 更新表设置enumvalue为'newenum3'其中enumvalue ='enum3'; 更新表设置enumvalue为'enum3'其中enumvalue ='enum2'; 然后更新表pg_enum set name ='enum1b'其中name ='enum2'和enumtypid = OID; 等等…

来自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' 

更新pg_enum的工作,上面突出显示的中间列技巧。 也可以使用USING magic来直接改变列的types:

 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会在你放弃types的时候发生抱怨)

另外请注意,PG9.1引入了一个ALTER TYPE语句,它将在枚举上工作:

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

最简单:摆脱枚举。 它们不易修改,因此应该很less使用。

我似乎无法发表评论,所以我只是说,更新pg_enum在Postgres 8.4中工作。 对于我们的枚举设置的方式,我已经通过以下方式为现有的枚举types添加了新的值:

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

这有点吓人,但考虑到Postgres实际存储数据的方式,这是有道理的。

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

ALTER table ALTER COLUMN bar DROP DEFAULT ;

然后它工作。

补充@Dariusz 1

对于Rails 4.2.1,有这个文档部分:

==事务性迁移

如果数据库适配器支持DDL事务,则所有迁移将自动包装在事务中。 有些查询不能在事务中执行,对于这些情况,您可以closures自动事务。

 class ChangeEnum < ActiveRecord::Migration disable_ddl_transaction! def up execute "ALTER TYPE model_size ADD VALUE 'new_value'" end end 

对于那些正在寻求交易解决scheme的人来说,下面的工作似乎有效。

ENUMDOMAIN应该用于TEXTtypes的一个约束,检查该值是否在指定的允许值列表中(正如一些注释所build议的那样)。 唯一的问题是,如果任何复合types都使用了约束(文档只是说“最终应该改进”),那么不能添加约束(也就不能修改)。 但是,这样的限制可能会被解决,但是,使用调用函数的约束,如下所示。

 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; 

以前,我使用类似于接受的答案的解决scheme,但是一旦考虑了视图或函数或复合types(特别是使用其他视图使用修改后的ENUM的视图),它就远没有那么好。 在这个答案中提出的解决scheme似乎在任何条件下工作。

唯一的缺点是,当某些允许的值被删除时(这可能是可以接受的,特别是对于这个问题),不对现有数据执行检查。 (对ALTER DOMAIN test_domain VALIDATE CONSTRAINT val_check的调用ALTER DOMAIN test_domain VALIDATE CONSTRAINT val_check结果与向复合types使用的域添加新约束的错误相同,不幸的是。

请注意,如CHECK (value = ANY(get_allowed_values())) ,其中get_allowed_values()函数返回允许的值列表将不起作用 – 这是很奇怪的,所以我希望上面提出的解决scheme可靠地工作它对我来说,迄今为止…)。 (它的工作,实际上 – 这是我的错误)

这是一个比较一般的,但速度相当快的解决scheme,除了更改types本身之外,还使用它更新数据库中的所有列。 即使新版本的ENUM与多个标签不同,或者遗漏了一些原始标签,也可以应用该方法。 以下代码用ENUM ('a', 'b', 'd', 'e')replacemy_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(); 

整个过程将运行相当快,因为​​如果标签的顺序依然存在,则不会发生实际的数据更改。 我使用my_type在5个表格上应用了这个方法,每个表格有5 my_type到7 my_type行,整个过程只需要10秒钟。

当然,如果在数据中的某个地方使用ENUM新版本中缺less的标签,函数将会返回一个exception,但是在这种情况下,事先应该做一些事情。

我不知道是否有其他select,但我们可以使用下面的值:

 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; 

当使用Navicat时,可​​以进入types(在视图 – >其他 – >types下) – 获取types的devise视图 – 然后单击“添加标签”button。