Como excluir um valor de tipo enum no postgres?

109

Como excluo um valor de tipo enum que criei no postgresql?

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

Por exemplo, quero remover moderatorda lista.

Não consigo encontrar nada nos documentos.

Estou usando o Postgresql 9.3.4.

Amjith
fonte
4
drop type admin_level1?
bereal
1
A regra: para cada create xxxhá umdrop xxx
a_horse_with_no_name
IMO a resposta selecionada deve ser alterada para outra.
Roman Podlinov

Respostas:

180

Você exclui (elimina) tipos de enum como qualquer outro tipo, com DROP TYPE:

DROP TYPE admin_level1;

É possível que você esteja realmente perguntando sobre como remover um valor individual de um tipo de enum ? Se sim, você não pode. Não é compatível :

Embora os enumtipos sejam principalmente destinados a conjuntos de valores estáticos, há suporte para adicionar novos valores a um tipo de enum existente e para renomear valores (consulte Recursos ALTER TYPE). Os valores existentes não podem ser removidos de um tipo de enum, nem a ordem de classificação de tais valores pode ser alterada, exceto para descartar e recriar o tipo de enum.

Você deve criar um novo tipo sem o valor, converter todos os usos existentes do tipo antigo para usar o novo tipo e, em seguida, eliminar o tipo antigo.

Por exemplo

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
fonte
1
Isto é brilhante! Com isso consegui resolver o problema de migração do Alembic. Não consegui adicionar um novo tipo de enum por causa de(psycopg2.InternalError) ALTER TYPE ... ADD cannot run inside a transaction block
karantan
adicione disable_ddl_transaction! para o início do arquivo de migração.
chell
EXCLUA DE blah ONDE poder = 'deus'; não está funcionando no meu caso
ankit
1
TBH Não entendo por que essa resposta foi selecionada. Esta resposta não está correta! Você pode excluir o valor de pg_enum com o rótulo especificado.
Roman Podlinov
2
@RomanPoelinov manipulação direta do catálogo Eu por sua própria conta e risco. Existem razões pelas quais o postgres não suporta a exclusão nativa de valores enum. Como isso é "incorreto" em comparação com um hack de catálogo sem suporte e inseguro?
Craig Ringer
41

Muito bem escrito aqui:

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

renomear o tipo existente

ALTER TYPE status_enum RENAME TO status_enum_old;

crie o novo tipo

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

atualize as colunas para usar o novo tipo

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

remova o tipo antigo

DROP TYPE status_enum_old;
dnaik
fonte
Este link agora retorna um 503.
Oliver Evans
31

Se você deseja excluir um item do tipo enum, deve operar na tabela de sistema do PostgreSQL.

Com este comando, você pode exibir todos os tipos de enum de itens.

SELECT * FROM pg_enum;

Em seguida, verifique se o valor pesquisado é único. Para aumentar a exclusividade durante a remoção do rekoru, deve ser passado 'enumtypid' além de 'enumlabel'.

Este comando remove a entrada no tipo enum, onde 'exclusivo' é o seu valor.

DELETE FROM pg_enum pt WHERE en.enumtypid = 124 AND en.enumlabel = 'único';

NOTA O exemplo que descrevi deve ser usado, quando por acaso adicionamos um novo valor ao tipo enum, e ainda não o usamos em nenhum lugar do banco de dados.

Elcudro
fonte
20
Esta é uma operação muito perigosa , mas é muito rápida e sucinta na remoção de um valor de um tipo de enum se você souber o que está fazendo. Primeiro, certifique-se de que nenhuma tabela esteja usando o valor enum que você deseja remover. Se não o fizer, você danificará todas as tabelas que fazem referência ao valor enum (por exemplo, selecionar a partir de tal tabela retornará ERROR: invalid internal value for enume NÃO produzirá resultados.)
Clint Pachl
5
Isso mesmo, esse é o aspecto mais importante que deve ser levado em consideração. O exemplo que descrevi deve ser usado, quando por acaso adicionamos um novo valor ao tipo enum, e ainda não o usamos em nenhum lugar do banco de dados.
elcudro
1
Dado o quão perigoso é este comando, DELETE FROM pg_enum en WHERE en.enumtypid=124 AND en.enumlabel='unigue';A NOTA deve estar em NEGRITO, não o comando. Se você usou o valor em alguma tabela, você não pode recuperá-lo. Você não pode atualizar as linhas que contêm o valor, você não pode converter. A única maneira é excluir a linha inteira.
Sylvain
8

Para aqueles que desejam modificar os valores de enum, recriá-los parece ser a única solução viável e segura.

Consiste em converter temporariamente a coluna enum em um formato de string, recriar o enum e então reconverter a coluna de string de volta para o tipo enum.

Aqui está um exemplo:

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
fonte
ALTER TABLE your_schema.your_table ALTER COLUMN your_column TYPE your_schema.your_enum_name USING your_enum_name::your_schema.your_column;deveria 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
6

Use a seguinte consulta para excluir o valor ENUM do tipo Postgresql

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

Apenas informações sobre o tipo e o valor

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

Você deve alterar os valores existentes para outros. Para isso, se você precisar adicionar um novo valor, use:

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

Antes de excluir, atualize o valor do tipo para o novo valor do tipo ou valor existente.

Somnath Muluk
fonte
O único problema é que o nome do tipo em pg_type é minúsculo. então não está funcionando, a menos que seja usado o enum_type minúsculo em SELECT oid FROM pg_type WHERE typname = 'enum_type'
fzerorubigd
2

A maneira programática de fazer isso é a seguinte. As mesmas etapas gerais fornecidas em https://stackoverflow.com/a/47305844/629272 são apropriadas, mas são mais manuais do que faria sentido para os meus propósitos (escrever uma migração para baixo do alambique). my_type,, my_type_olde value_to_delete, deve, é claro, ser alterado conforme apropriado.

  1. Renomeie seu tipo.

    ALTER TYPE my_type RENAME TO my_type_old;
  2. Crie um novo tipo com os valores do seu tipo antigo, excluindo aquele que você deseja excluir.

    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. Altere todas as colunas existentes que usam o tipo antigo para usar o novo.

    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. Exclua o tipo antigo.

    DROP TYPE my_type_old;
Californiano
fonte
0

se o seu conjunto de dados não for tão grande, você pode despejar com --column-insertseditar o despejo com um editor de texto, remover o valor e reimportar o despejo

Sherpya
fonte
0

Tive o mesmo problema no v.10. postgres. A exclusão requer certos procedimentos e, se a sequência não estiver correta, haverá até uma chance de a tabela ser bloqueada para leitura.

Escreveu um script conveniente para excluir. Já provou várias vezes o seu desempenho. No entanto, este procedimento envolve a substituição do valor excluído por um novo (pode ser NULL se o campo da tabela permitir).

Para usar, você só precisa preencher 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. Item da lista
SergioBazileyroElMurdoMendez
fonte
-1

Não é possível excluir um valor individual de ENUM, a única solução possível é DROP e recriar ENUM com os valores necessários.

Zaytsev Dmitry
fonte
É muito possível, o que você provavelmente quis dizer com "não é oficialmente suportado".
Rikudou_Sennin
@Rikudou_Sennin, você se importaria em fornecer um código que possa remover um valor exato de ENUM?
Zaytsev Dmitry
2
@ZaytsevDmitry aqui está você:DELETE FROM pg_enum WHERE enumlabel='saml' AND enumsortorder=4;
Roman Podlinov