¿Cómo eliminar un valor de tipo de enumeración en postgres?

109

¿Cómo elimino un valor de tipo de enumeración que creé en postgresql?

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

Por ejemplo, quiero eliminar moderatorde la lista.

Parece que no puedo encontrar nada en los documentos.

Estoy usando Postgresql 9.3.4.

Amjith
fuente
4
drop type admin_level1?
Bereal
1
La regla general: por cada create xxxhay undrop xxx
un_horse_with_no_name
En mi opinión, la respuesta seleccionada debe cambiarse a otra.
Roman Podlinov

Respuestas:

180

Elimina (suelta) tipos de enumeración como cualquier otro tipo, con DROP TYPE:

DROP TYPE admin_level1;

¿Es posible que esté preguntando cómo eliminar un valor individual de un tipo de enumeración ? Si es así, no puede. No es compatible :

Aunque los enumtipos están pensados ​​principalmente para conjuntos de valores estáticos, existe soporte para agregar nuevos valores a un tipo de enumeración existente y para cambiar el nombre de los valores (ver ALTER TYPE). Los valores existentes no se pueden eliminar de un tipo de enumeración, ni se puede cambiar el orden de clasificación de dichos valores, salvo eliminar y volver a crear el tipo de enumeración.

Debe crear un nuevo tipo sin el valor, convertir todos los usos existentes del tipo anterior para usar el nuevo tipo y luego eliminar el tipo anterior.

P.ej

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;
Craig Ringer
fuente
1
¡Esto es brillante! Con esto logré solucionar el problema de migración de Alembic. No pude agregar un nuevo tipo de enumeración debido a(psycopg2.InternalError) ALTER TYPE ... ADD cannot run inside a transaction block
karantan
¡añadir disable_ddl_transaction! al principio del archivo de migración.
chell
BORRAR DE bla DONDE poder = 'dios'; no funciona en mi caso
ankit
1
TBH No entiendo por qué se seleccionó esta respuesta. ¡Esta respuesta no es correcta! Puede eliminar el valor de pg_enum con la etiqueta especificada.
Roman Podlinov
2
@RomanPoelinov manipulación directa del catálogo. Corría bajo su propio riesgo. Hay razones por las que Postgres no admite la eliminación de valores de enumeración de forma nativa. ¿Cómo es esto "incorrecto" en comparación con un truco de catálogo inseguro y no compatible?
Craig Ringer
41

Muy bien escrito aquí:

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

cambiar el nombre del tipo existente

ALTER TYPE status_enum RENAME TO status_enum_old;

crea el nuevo tipo

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

actualice las columnas para usar el nuevo tipo

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

eliminar el tipo antiguo

DROP TYPE status_enum_old;
dnaik
fuente
Este enlace ahora devuelve un 503.
Oliver Evans
32

Si desea eliminar el elemento de tipo enumeración, debe operar en la tabla del sistema de PostgreSQL.

Con este comando, puede mostrar todos los elementos del tipo de enumeración.

SELECCIONAR * DE pg_enum;

Luego verifique que el valor buscado sea único. Para aumentar la unicidad durante la eliminación de rekoru, se debe pasar 'enumtypid' además de 'enumlabel'.

Este comando elimina la entrada en el tipo de enumeración, donde 'único' es su valor.

BORRAR DE pg_enum en DONDE en.enumtypid = 124 Y en.enumlabel = 'único';

NOTA El ejemplo que describí debe usarse, cuando por casualidad agregamos un nuevo valor al tipo de enumeración, y aún no lo hemos usado en ninguna parte de la base de datos.

elcudro
fuente
20
Esta es una operación muy peligrosa , pero es muy rápida y sucinta para eliminar un valor de un tipo de enumeración si sabe lo que está haciendo. Primero, asegúrese de que ninguna tabla esté usando el valor de enumeración que desea eliminar. Si no lo hace, romperá todas las tablas que hacen referencia al valor de enumeración (p. Ej., Seleccionar de una tabla de este tipo devolverá ERROR: invalid internal value for enumy producirá NO resultados).
Clint Pachl
5
Así es, este es el aspecto más importante que se debe tener en cuenta. El ejemplo que describí debe usarse, cuando por casualidad agregamos un nuevo valor al tipo de enumeración, y aún no lo hemos usado en ninguna parte de la base de datos.
elcudro
1
Dado lo peligroso que es este comando DELETE FROM pg_enum en WHERE en.enumtypid=124 AND en.enumlabel='unigue'; la NOTA debe estar en NEGRITA, no el comando. Si ha utilizado el valor en alguna tabla, no podrá recuperarse de él. No puede actualizar las filas que contienen el valor, no puede convertir. La única forma es eliminar toda la fila.
Sylvain
8

Para aquellos que deseen modificar los valores de enumeración, recrearlos parece ser la única solución viable y segura.

Consiste en convertir temporalmente la columna de enumeración a un formato de cadena, recrear la enumeración y luego reconvertir la columna de cadena al tipo de enumeración.

Aquí hay un ejemplo:

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');
sveilleux2
fuente
ALTER TABLE your_schema.your_table ALTER COLUMN your_column TYPE your_schema.your_enum_name USING your_enum_name::your_schema.your_column;debería serALTER TABLE your_schema.your_table ALTER COLUMN your_column TYPE your_schema.your_enum_name USING your_schema.your_column::your_enum_name;
Manuel Darveau
7

Utilice la siguiente consulta para eliminar el valor ENUM del tipo Postgresql

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

Solo información sobre el tipo y el valor

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

Debería cambiar los valores existentes a otros. Para eso, si necesita agregar un nuevo valor, use:

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

Antes de eliminar, actualice el valor de tipo a un nuevo valor de tipo o valor existente.

Somnath Muluk
fuente
El único problema es que el nombre del tipo en pg_type está en minúsculas. por lo que no funciona, a menos que use la minúscula enum_type en SELECT oid FROM pg_type WHERE typname = 'enum_type'
fzerorubigd
2

La forma programática de hacer esto es la siguiente. Los mismos pasos generales que se dan en https://stackoverflow.com/a/47305844/629272 son apropiados, pero son más manuales que lógicos para mis propósitos (escribir una migración descendente de alambique). my_type, my_type_oldy value_to_delete, por supuesto, debería cambiarse según corresponda.

  1. Cambie el nombre de su tipo.

    ALTER TYPE my_type RENAME TO my_type_old;
  2. Cree un nuevo tipo con los valores de su tipo anterior, excluyendo el que desea eliminar.

    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. Cambie todas las columnas existentes que usan el tipo antiguo por el nuevo.

    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. Elimina el tipo antiguo.

    DROP TYPE my_type_old;
californiano
fuente
0

si su conjunto de datos no es tan grande, puede volcar con --column-insertseditar el volcado con un editor de texto, eliminar el valor y volver a importar el volcado

Sherpya
fuente
0

Tuve el mismo problema en el v.10. postgres. La eliminación requiere ciertos procedimientos y, si la secuencia no es correcta, incluso habrá una posibilidad de que la tabla se bloquee para su lectura.

Escribió un guión conveniente para eliminar. Ya probado varias veces su rendimiento. Sin embargo, este procedimiento implica reemplazar el valor eliminado por uno nuevo (puede ser NULO si el campo de la tabla lo permite).

Para usarlo, solo necesita completar 3 valores.

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. Elemento de lista
SergioBazileyroElMurdoMendez
fuente
-1

No es posible eliminar un valor individual de ENUM, la única solución posible es DROP y volver a crear ENUM con los valores necesarios.

Zaytsev Dmitry
fuente
Es muy posible, lo que probablemente quiso decir es "no oficialmente compatible".
Rikudou_Sennin
@Rikudou_Sennin, ¿le importaría proporcionar un código que pueda eliminar un valor exacto de ENUM?
Zaytsev Dmitry
2
@ZaytsevDmitry aquí está:DELETE FROM pg_enum WHERE enumlabel='saml' AND enumsortorder=4;
Roman Podlinov