【问题标题】:How to delete an enum type value in postgres?如何删除 postgres 中的枚举类型值?
【发布时间】:2014-11-06 19:03:15
【问题描述】:

如何删除我在 postgresql 中创建的枚举类型值?

create type admin_level1 as enum('classifier', 'moderator', 'god');

例如我想从列表中删除moderator

我似乎在文档中找不到任何内容。

我使用的是 Postgresql 9.3.4。

【问题讨论】:

  • drop type admin_level1?
  • 经验法则:对于每个create xxx,都有一个drop xxx
  • IMO 必须将所选答案更改为另一个答案。

标签: postgresql enums


【解决方案1】:

您可以像任何其他类型一样删除(删除)枚举类型,使用DROP TYPE

DROP TYPE admin_level1;

您是否真的在询问如何从枚举类型中删除单个值?如果是这样,你不能。 It's not supported:

虽然enum 类型主要用于静态值集,但支持向现有枚举类型添加新值和重命名值(请参阅ALTER TYPE)。无法从枚举类型中删除现有值,也无法更改这些值的排序顺序,除非删除并重新创建枚举类型。

您必须创建一个没有值的新类型,将旧类型的所有现有用途转换为使用新类型,然后删除旧类型。

例如

CREATE TYPE admin_level1 AS ENUM ('classifier', 'moderator');

CREATE TABLE blah (
    user_id integer primary key,
    power admin_level1 not null
);

INSERT INTO blah(user_id, power) VALUES (1, 'moderator'), (10, 'classifier');

ALTER TYPE admin_level1 ADD VALUE 'god';

INSERT INTO blah(user_id, power) VALUES (42, 'god');

-- .... oops, maybe that was a bad idea

CREATE TYPE admin_level1_new AS ENUM ('classifier', 'moderator');

-- Remove values that won't be compatible with new definition
-- You don't have to delete, you might update instead
DELETE FROM blah WHERE power = 'god';

-- Convert to new type, casting via text representation
ALTER TABLE blah 
  ALTER COLUMN power TYPE admin_level1_new 
    USING (power::text::admin_level1_new);

-- and swap the types
DROP TYPE admin_level1;

ALTER TYPE admin_level1_new RENAME TO admin_level1;

【讨论】:

  • 这太棒了!有了这个,我设法解决了 Alembic 迁移问题。由于(psycopg2.InternalError) ALTER TYPE ... ADD cannot run inside a transaction block,我无法添加新的枚举类型
  • 添加 disable_ddl_transaction!到迁移文件的顶部。
  • 从 blah 中删除 power = 'god';在我的情况下不起作用
  • TBH 我不明白为什么选择了这个答案。这个答案不正确!您可以使用指定标签从 pg_enum 中删除值。
  • @RomanPoelinov 直接目录操作,风险自负。 postgres 不支持原生删除枚举值是有原因的。与不受支持和不安全的目录黑客攻击相比,这“不正确”如何?
【解决方案2】:

这里写的很好:

http://blog.yo1.dog/updating-enum-values-in-postgresql-the-safe-and-easy-way/

重命名现有类型

ALTER TYPE status_enum RENAME TO status_enum_old;

创建新类型

CREATE TYPE status_enum AS ENUM('queued', 'running', 'done');

更新列以使用新类型

ALTER TABLE job ALTER COLUMN job_status TYPE status_enum USING job_status::text::status_enum;

删除旧类型

DROP TYPE status_enum_old;

可能的错误和故障排除:

  • invalid input value for enum {enum name}: "{some value}" - 一行或多行的值 ("{some value}") 不在您的新类型中。您必须先处理这些行,然后才能更新列类型。
  • default for column "{column_name}" cannot be cast automatically to type {enum_name} - 该列的默认值不在您的新类型中。您必须先更改或删除列的默认值,然后才能更新列类型。感谢 Philipp 的加入。

【讨论】:

  • 此链接现在返回 503。
  • 这是迄今为止最好的解决方案!
  • 请注意:ERROR 25001: ALTER TYPE ... ADD cannot run inside a transaction block
  • 请注意,如果在约束中使用该列,您可能会在<> 上收到operator does not exist 错误。我不得不使用列删除检查约束,修改字段,然后重新添加约束。
  • 这不只是所选答案的重复吗?
【解决方案3】:

如果要删除枚举类型的项目,必须对PostgreSQL的系统表进行操作。

使用此命令,可以显示所有项目的枚举类型。

SELECT * FROM pg_enum;

然后检查搜索到的值是否唯一。为了在移除 rekoru 期间增加唯一性,除了 'enumlabel' 之外,还必须传递 'enumtypid'。

此命令删除枚举类型的条目,其中 'unique' 是您的值。

DELETE FROM pg_enum en WHERE en.enumtypid=124 AND en.enumlabel='unique';

注意 必须使用我描述的示例,当我们偶然为枚举类型添加新值时,我们还没有在数据库中的任何地方使用它。

【讨论】:

  • 这是一个非常危险的操作,但是如果您知道自己在做什么,那么从枚举类型中删除一个值会非常快速和简洁。首先,确保没有表正在使用您要删除的枚举值。如果不这样做,您将严重破坏所有引用枚举值的表(例如,从此类表中选择将返回 ERROR: invalid internal value for enum 并产生 NO 结果。)
  • 没错,这是最应该考虑的方面。必须使用我描述的示例,当我们偶然为枚举类型添加新值时,我们还没有在数据库中的任何地方使用它。
  • 鉴于此命令有多危险DELETE FROM pg_enum en WHERE en.enumtypid=124 AND en.enumlabel='unigue'; 注意应该是粗体,而不是命令。如果您在某个表中使用了该值,则无法从中恢复。您无法更新包含该值的行,也无法进行转换。唯一的方法是删除整行。
  • 请不要太吓人。只需确保他们尽最大努力清理所有相关表中的值,并且他们在枚举删除操作之前执行完整的数据库转储(使用 pg_dumpall)。 因此,如果表损坏,他们可以手动从转储。
  • 另外,您可以使用insert into pg_enum (oid, enumtypid, enumsortorder, enumlabel) values (myoid, myenumtypid, 88888888, '*dummy*'); 临时放回一个虚拟枚举值,然后使用update mytable set myenumcolumn='mynewvalue' where myenumcolumn='*dummy*'; 更新中断表,最后使用delete from pg_enum where oid=myoid; 再次删除该枚举。这里myoid 是您将从错误消息中获得的已删除枚举值的 oid。
【解决方案4】:

对于那些希望修改枚举值的人来说,重新创建它似乎是唯一可行且安全的解决方案。

它包括暂时将枚举列转换为字符串格式,重新创建枚举,然后将字符串列重新转换回枚举类型。

这是一个例子:

ALTER TABLE your_schema.your_table ALTER COLUMN your_column TYPE varchar(255);
ALTER TABLE your_schema.your_table ALTER COLUMN your_column SET DEFAULT('your_default_enum_value');
DROP TYPE your_schema.your_enum_name;
CREATE TYPE your_schema.your_enum_name AS ENUM ('enum1', 'enum2', 'enum3');
ALTER TABLE your_schema.your_table ALTER your_column DROP DEFAULT;
ALTER TABLE your_schema.your_table ALTER COLUMN your_column TYPE your_schema.your_enum_name USING your_enum_name::your_schema.your_column;
ALTER TABLE your_schema.your_table ALTER COLUMN your_column SET DEFAULT('your_default_enum_value');

【讨论】:

  • ALTER TABLE your_schema.your_table ALTER COLUMN your_column TYPE your_schema.your_enum_name USING your_enum_name::your_schema.your_column; 应该是ALTER TABLE your_schema.your_table ALTER COLUMN your_column TYPE your_schema.your_enum_name USING your_schema.your_column::your_enum_name;
【解决方案5】:

使用以下查询从 Postgresql 类型中删除 ENUM 值

DELETE FROM pg_enum
WHERE enumlabel = 'moderator'
AND enumtypid = ( SELECT oid FROM pg_type WHERE typname = 'admin_level1');

只是关于什么是类型和什么价值的信息

DELETE FROM pg_enum
WHERE enumlabel = 'ENUM_VALUE'
AND enumtypid = ( SELECT oid FROM pg_type WHERE typname = 'ENUM_TYPE')

您应该将现有值更改为其他值。为此,如果您需要添加新值,请使用:

ALTER TYPE **ENUM_TYPE** ADD VALUE '**ENUM_VALUE2**'; 

在删除之前,将类型值更新为新的类型值或现有值。

【讨论】:

  • 唯一的问题是 pg_type 中的 typname 是小写的。所以它不起作用,除非在 SELECT oid FROM pg_type WHERE typname = 'enum_type' 中使用小写的 enum_type
  • 如上所述 (stackoverflow.com/questions/25811017/…) 这非常危险
【解决方案6】:

执行此操作的编程方式如下。与给定相同的一般步骤 在https://stackoverflow.com/a/47305844/629272 是合适的,但那些是 对我的目的而言,手动操作比有意义(编写 alembic 向下迁移)。 my_typemy_type_oldvalue_to_delete,当然应该酌情更改。

  1. 重命名您的类型。

    ALTER TYPE my_type RENAME TO my_type_old;
    
  2. 用旧类型的值创建一个新类型,不包括那个 你想删除。

    DO $$
    BEGIN
        EXECUTE format(
            'CREATE TYPE my_type AS ENUM (%s)',
            (
                SELECT string_agg(quote_literal(value), ',')
                FROM unnest(enum_range(NULL::my_type_old)) value
                WHERE value <> 'value_to_delete'
            )
        );
    END $$;
    
  3. 将所有使用旧类型的现有列更改为使用新类型。

    DO $$
    DECLARE
        column_data record;
        table_name varchar(255);
        column_name varchar(255);
    BEGIN
        FOR column_data IN
            SELECT cols.table_name, cols.column_name
                FROM information_schema.columns cols
                WHERE udt_name = 'my_type_old'
        LOOP
            table_name := column_data.table_name;
            column_name := column_data.column_name;
            EXECUTE format(
                '
                    ALTER TABLE %s
                    ALTER COLUMN %s
                    TYPE my_type
                    USING %s::text::my_type;
                ',
                table_name, column_name, column_name
            );
        END LOOP;
    END $$;
    
  4. 删除旧类型。

    DROP TYPE my_type_old;
    

【讨论】:

    【解决方案7】:

    我的解决方案始于我在我的场景中没有权限执行DELETE FROM pg_enum,因为我遇到了权限错误。

    从其他回复开始,我创建了一个通用函数,可以用来从枚举中移除单个值,支持更新值释放给定值的使用

    -- https://stackoverflow.com/a/62444685
    -- https://stackoverflow.com/a/51073579
    create or replace function remove_enum_value(
        type_name text,         -- Name of the type where you need to remove a value from
        value_to_delete text,   -- Specific value of the given type you want to remove
        value_fallback text,    -- Which new value columns will have instead of the value deleted
        column_default text     -- DEFAULT value for the column after type alteration (DEFAULT need to be disabled before changing type, https://stackoverflow.com/a/41149789)
    )
    RETURNS VOID AS $body$
    
    declare 
        -- Used as temporary type
        _type_name_tmp text := type_name || '_tmp_' || floor(extract(epoch from now()) * 1000);
    
        -- Used to store statements to execute
        _sql text;
    
        -- Used to loop tables and switch type from current to temporary
        _column_data record;
        _table_name varchar(255);
        _column_name varchar(255);
       
    begin   
        
        --------------------------------------------------------------------------------------------------------------
        
        -- Check: required inputs
        if type_name is null
        then
            raise exception 'Parameter type_name is null';
        end if;
        if value_to_delete is null
        then
            raise exception 'Parameter value_to_delete is null';
        end if;
        
        -- Check: type exists
        IF not EXISTS (SELECT 1 FROM pg_type WHERE typname = type_name) THEN
            raise info 'Type %s does not exists', type_name;
            return;
        END IF;
        
        -- Check: value to delete exists
        if not exists(
            select * 
            FROM pg_enum -- check existing of value to delete
            WHERE enumtypid = (select oid from pg_type where typName=cast(type_name as varchar) limit 1) and enumlabel=cast(value_to_delete as varchar)
        )
        then 
            raise info 'Value to delete % does not exists in type %s', value_to_delete, type_name;
            return;
        end if;
            
        -- Check: fallback value is provided and exists
        if value_fallback is not null and not exists(
            select * 
            FROM pg_enum -- check existing of value to delete
            where
                enumtypid = (select oid from pg_type where typName=cast(type_name as varchar) limit 1)
                and enumlabel=cast(value_fallback as varchar)
        )
        then 
            raise info 'Fallback value % does not exists in type %s', value_fallback, type_name;
            return;
        end if;
    
        -- Check values are different
        if value_fallback = value_to_delete
        then
            raise info 'Value to delete %s is the same as fallback value %', value_to_delete, value_fallback;
            return;
        end if;
    
        raise info 'Checks passed, ready to process!';
    
        --------------------------------------------------------------------------------------------------------------
            
        -- Retrieve current values of type
        _sql := format('
            SELECT string_agg(quote_literal(value), '','')
            FROM unnest(enum_range(NULL::%s)) value
            WHERE value <> ''%s''
        ', type_name, value_to_delete);
        raise info '%', _sql;
        execute _sql into _sql;
    
        -- Create temporary enum
        _sql := format(
            'CREATE TYPE %s AS ENUM (%s)',
            _type_name_tmp,
            _sql
        );
        raise info '%', _sql;
        execute _sql;
    
        -- Rename all values from value that need to be deleted to new value (selecting all tables with schemas which has column with enum relation)
        for _column_data in (
            select
                concat(c.table_schema,'.',c.table_name ) as table_name,
                c.column_name
            FROM information_schema.columns c
            where
                c.udt_name = cast(type_name as varchar)
                and c.table_schema=c.udt_schema 
                and data_type = 'USER-DEFINED'
        )
        LOOP
            _sql:= format('UPDATE %1$s set %2$s = %3$L where %2$s=%4$L', _column_data.table_name, _column_data.column_name, value_fallback, value_to_delete);
            raise info 'Update by looping: %', _sql;
            EXECUTE _sql;
        END LOOP;
    
        -- Switch type from current to temporary
        FOR _column_data in (
            SELECT cols.table_name, cols.column_name
            FROM information_schema.columns cols
            WHERE udt_name = type_name
        )
        LOOP
            _table_name := _column_data.table_name;
            _column_name := _column_data.column_name;
            _sql := format(
                '
                    ALTER TABLE %s
                        ALTER COLUMN %s DROP DEFAULT,
                        ALTER COLUMN %s TYPE %s USING %s::text::%s,
                        ALTER COLUMN %s SET DEFAULT %s;
                ',
                _table_name, 
                _column_name, 
                _column_name, _type_name_tmp, _column_name, _type_name_tmp,
                _column_name, (case when column_default is null then null else '''' || column_default || '''::' || _type_name_tmp end)
            );
           
            raise info '%', _sql;
            execute _sql;
        END LOOP;
       
        -- Drop previous type
        _sql := format('DROP TYPE %s;', type_name);
        raise info '%', _sql;
        execute _sql;
        
        -- Rename type to previous name
        _sql := format('ALTER TYPE %s RENAME TO %s;', _type_name_tmp, type_name);
        raise info '%', _sql;
        execute _sql;
           
    END $body$
    LANGUAGE plpgsql;
    

    【讨论】:

      【解决方案8】:

      无法从 ENUM 中删除单个值,唯一可能的解决方案是 DROP 并使用所需值重新创建 ENUM。

      【讨论】:

      • 很有可能,你的意思应该是“没有官方支持”。
      • @Rikudou_Senin 您介意提供一个可以从 ENUM 中删除一个精确值的代码吗?
      【解决方案9】:

      在 v.10 中遇到了同样的问题。后格雷斯。 删除需要一定的程序,如果顺序不正确,甚至有可能导致表被锁读。

      写了一个方便的脚本来删除。已经多次证明了它的性能。但是,此过程涉及用新值替换已删除的值(如果表字段允许,它可以为 NULL)。

      要使用,您只需要填写 3 个值。

      DO $$
      DECLARE
          enumTypeName VARCHAR := 'enum_name'; -- VALUE #1, set yor value!
          enumOldFieldValue varchar := 'old_enum_value'; -- VALUE #2, enum value which have to be deleted
          enumNewFieldValue varchar := null; -- VALUE #3, which new value must be instead of deleted
          sql varchar:='';
          rec record;
      BEGIN
          raise info 'Check on old and new enum values.';
          IF exists(select * FROM pg_enum -- check existing of OLD enum value
                    WHERE enumtypid = (select oid from pg_type where typName=cast(enumTypeName as varchar) limit 1) and enumlabel=cast(enumOldFieldValue as varchar))
            AND
             (exists(select *
                     FROM pg_enum -- check existing of NEW enum value
                     WHERE enumtypid = (select oid from pg_type where typName = cast(enumTypeName as varchar) limit 1)
                       and enumlabel = cast(enumNewFieldValue as varchar))
                 OR
              enumNewFieldValue IS NULL)
              THEN
                  raise info 'Check passed!';
      
                  -- selecting all tables with schemas which has column with enum relation
                  create temporary table tmp_table_names
                   as SELECT concat(c.table_schema,'.',c.table_name ) as table_name, c.column_name
                      FROM information_schema.columns c
                      WHERE c.udt_name = cast(enumTypeName as varchar)
                        and c.table_schema=c.udt_schema and data_type = 'USER-DEFINED';
      
                  -- if we have table(s) that uses such enum
                  if exists(select * from tmp_table_names)
                      then
                          FOR rec in (select table_name, column_name from tmp_table_names) LOOP
                              sql:= format('UPDATE %1$s set %2$s = %3$L where %2$s=%4$L',rec.table_name, rec.column_name, enumNewFieldValue, enumOldFieldValue);
                              raise info 'Update by looping: %', sql;
                              EXECUTE sql;
                          END LOOP;
                  end if;
      
                  -- just after changing all old values in all tables we can delete old enum value
                  sql := format('DELETE FROM pg_enum WHERE enumtypid = (select oid from pg_type where typName=%1$L limit 1) and enumlabel=%2$L',enumTypeName,enumOldFieldValue);
                  raise info 'Delete enum value: %', sql;
                  EXECUTE sql;
      
                  drop table  tmp_table_names;
              ELSE
                  raise info 'Old or new enum values is missing.';
          end if;
      END $$;
      
      1. 列表项

      【讨论】:

        【解决方案10】:

        如果您的数据集不是那么大,您可以使用--column-inserts 转储使用文本编辑器编辑转储,删除值并重新导入转储

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-08-21
          • 2016-05-16
          • 2018-12-30
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多