Как удалить значение типа перечисления в postgres?

109

Как удалить значение типа перечисления, созданное в postgresql?

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

Например, я хочу удалить moderatorиз списка.

Кажется, я ничего не могу найти в документации.

Я использую Postgresql 9.3.4.

Амджит
источник
4
drop type admin_level1?
bereal
1
Эмпирическое правило: для каждого create xxxестьdrop xxx
a_horse_with_no_name
IMO выбранный ответ необходимо заменить на другой.
Роман

Ответы:

180

Вы удаляете (отбрасываете) типы перечислений, как и любой другой тип, с помощью DROP TYPE:

DROP TYPE admin_level1;

Возможно ли, что вы действительно спрашиваете, как удалить отдельное значение из типа перечисления ? Если так, то не получится. Не поддерживается :

Хотя 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;
Крэйг Рингер
источник
1
Это великолепно! Благодаря этому мне удалось решить проблему миграции Alembic. Я не смог добавить новый тип перечисления из-за(psycopg2.InternalError) ALTER TYPE ... ADD cannot run inside a transaction block
karantan
добавить disable_ddl_transaction! в начало файла миграции.
Chell
УДАЛИТЬ ОТ blah WHERE power = 'god'; не работает в моем случае
ankit
1
ТБХ Я не понимаю, почему был выбран этот ответ. Это неверный ответ! Вы можете удалить значение из pg_enum с указанной меткой.
Роман
2
@RomanPoelinov прямую манипуляцию каталогом я бы сделал на свой страх и риск. Есть причины, по которым postgres не поддерживает удаление значений перечисления изначально. Чем это "не правильно" по сравнению с неподдерживаемым и небезопасным взломом каталога?
Craig Ringer
41

Здесь очень хорошо написано:

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;
днайк
источник
Эта ссылка теперь возвращает 503.
Оливер Эванс,
32

Если вы хотите удалить элемент типа enum, вы должны работать с системной таблицей PostgreSQL.

С помощью этой команды вы можете отобразить все типы перечисления элементов.

ВЫБРАТЬ * ИЗ pg_enum;

Затем убедитесь, что найденное значение уникально. Для увеличения уникальности при удалении рекору необходимо передать enumtypid в дополнение к enumlabel.

Эта команда удаляет запись в типе перечисления, где «уникальный» - ваше значение.

УДАЛИТЬ ИЗ pg_enum en WHERE en.enumtypid = 124 AND en.enumlabel = 'unique';

ПРИМЕЧАНИЕ . Пример, который я описал, должен использоваться, когда мы случайно добавляем новое значение в тип перечисления, но мы еще не использовали его нигде в базе данных.

Элькудро
источник
20
Это очень опасная операция , но она позволяет очень быстро и кратко удалить значение из типа перечисления, если вы знаете, что делаете. Во-первых, убедитесь, что ни одна таблица не использует значение перечисления, которое вы хотите удалить. Если вы этого не сделаете, вы будете плохо разбить все таблицы , которые ссылаются на значение перечисления (например , выбирающим из такой таблицы будет возвращать ERROR: invalid internal value for enumи выход NO результатов.)
Клинт Pachl
5
Правильно, это самый важный аспект, который следует учитывать. Пример, который я описал, необходимо использовать, когда мы случайно добавляем новое значение к типу перечисления, но мы нигде не использовали его в базе данных.
elcudro
1
Учитывая, насколько опасна эта команда, DELETE FROM pg_enum en WHERE en.enumtypid=124 AND en.enumlabel='unigue';ПРИМЕЧАНИЕ должно быть выделено жирным шрифтом, а не команда. Если вы использовали значение из какой-либо таблицы, вы не можете восстановить его. Вы не можете обновить строки, содержащие значение, вы не можете преобразовать. Единственный способ - удалить всю строку.
Сильвен
8

Для тех, кто хочет изменить значения перечисления, его воссоздание кажется единственным жизнеспособным и безопасным решением.

Он состоит во временном преобразовании столбца перечисления в строковый формат, воссоздании перечисления и последующем преобразовании строкового столбца обратно в тип перечисления.

Вот пример:

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
источник
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;
Мануэль Дарво
7

Используйте следующий запрос для удаления значения ENUM из типа Postgresql

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 в нижнем регистре. так что он не работает, если только не используется enum_type в нижнем регистре SELECT oid FROM pg_type WHERE typname = 'enum_type'
fzerorubigd
2

Программный способ сделать это следующий. Уместны те же общие шаги, что и в https://stackoverflow.com/a/47305844/629272 , но они скорее ручные, чем имеют смысл для моих целей (написание перегонной миграции вниз). my_type,, my_type_oldи value_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;
Калифорнийский
источник
0

если ваш набор данных не такой большой, вы можете сделать дамп, --column-insertsотредактировав дамп с помощью текстового редактора, удалите значение и повторно импортируйте дамп

шерпья
источник
0

Была такая же проблема в версии 10. postgres. Для удаления требуются определенные процедуры, и если последовательность неверна, то есть вероятность того, что таблица будет заблокирована для чтения.

Написал удобный скрипт для удаления. Уже несколько раз доказана его работоспособность. Однако эта процедура включает замену удаленного значения новым (оно может иметь значение 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. Элемент списка
СерхиоБазилейроЭльМурдоМендес
источник
-1

Невозможно удалить отдельное значение из ENUM, единственное возможное решение - DROP и воссоздать ENUM с необходимыми значениями.

Зайцев Дмитрий
источник
Вполне возможно, вы имели в виду, что "официально не поддерживается".
Rikudou_Sennin
@Rikudou_Sennin, не могли бы вы предоставить код, который может удалить одно точное значение из ENUM?
Зайцев Дмитрий
2
@ZaytsevDmitry, вот ты где:DELETE FROM pg_enum WHERE enumlabel='saml' AND enumsortorder=4;
Роман