将新值添加到现有ENUM类型

时间:2009-11-20 16:13:12

标签: database postgresql enums

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

18 个答案:

答案 0 :(得分:312)

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 :(得分:126)

注意如果您使用的是PostgreSQL 9.1或更高版本,请参阅this answer以获得更简单的方法。


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

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

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

答案 2 :(得分:59)

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

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

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

答案 3 :(得分:25)

如果您遇到在事务中添加enum值的情况,请参见f.e.在ALTER TYPE语句的flyway迁移中执行它,您将收到错误ERROR: ALTER TYPE ... ADD cannot run inside a transaction block(请参阅flyway issue #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 )

答案 4 :(得分:12)

补充@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

答案 5 :(得分:9)

来自Postgres 9.1 Documentation

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

示例:

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

答案 6 :(得分: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;

等等......

答案 7 :(得分: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;

只要您没有明确要求或返回该枚举的功能,您就会很好。 (如果丢弃类型,pgsql会抱怨。)

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

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

答案 8 :(得分: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实际存储数据的方式,这是有道理的。

答案 9 :(得分:4)

如果您使用的是Postgres 12,则可以在事务(documentation)内运行ALTER TYPE ... ADD VALUE

如果ALTER TYPE ... ADD VALUE(将新值添加到枚举的形式 类型)在事务块内执行,则新值不能为 直到交易完成后才使用。

因此,在迁移过程中无需黑客。

UPD:这是一个示例(感谢Nick的帮助)

ALTER TYPE enum_type ADD VALUE 'new_value';

答案 10 :(得分:3)

最简单:摆脱枚举。它们不易修改,因此非常很少使用。

答案 11 :(得分:3)

无法在适当的位置添加评论,但列上的默认值ALTER TABLE foo ALTER COLUMN bar TYPE new_enum_type USING bar::text::new_enum_type失败。我必须:

ALTER table ALTER COLUMN bar DROP DEFAULT;

然后它奏效了。

答案 12 :(得分:2)

对于那些寻找交易中解决方案的人来说,以下似乎有效。

而不是ENUMDOMAIN应在类型TEXT上使用,并且约束检查该值是否在指定的允许值列表中(如某些注释所示) 。唯一的问题是,如果任何复合类型使用了约束,那么就不能将约束添加(因此也不会被修改)到域中(文档只是说“最终应该改进”)。但是,这种限制可以使用调用函数的约束来解决,如下所示。

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()函数返回允许值列表,将无效 - 这很奇怪,所以我希望上面提出的解决方案有效可靠(它对我而言,到目前为止......)。(它确实有效 - 这是我的错误)

答案 13 :(得分: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();

整个过程运行得相当快,因为​​如果标签的顺序仍然存在,则不会发生实际的数据更改。我使用my_type在5个表上应用了该方法,每个表中有50,000-70,000行,整个过程只需10秒。

当然,如果新版本ENUM中缺少的标签被用于数据中的某个地方,该函数将返回异常,但在这种情况下,事先应该事先做好。

答案 14 :(得分:0)

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

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

答案 15 :(得分:0)

如上所述,不能在事务内部编写ALTER命令。建议的方法是通过retrieving the typelem from pg_type tablecalculating the next enumsortorder number直接插入pg_enum表中;

以下是我使用的代码。 (在插入之前检查是否存在重复值(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表中带有下划线。另外,在where子句中,类型名必须全部小写。

现在可以安全地将其写入数据库迁移脚本中。

答案 16 :(得分:-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;

答案 17 :(得分:-2)

使用Navicat时,您可以转到类型(在视图下 - &gt;其他 - &gt;类型) - 获取类型的设计视图 - 然后单击“添加标签”按钮。