Adding a new value to an existing ENUM Type

前端 未结 18 1295
春和景丽
春和景丽 2020-12-04 04:52

I have a table column that uses an enum type. I wish to update that enum type to have an additional possible value. I don\'t want to delete any exi

相关标签:
18条回答
  • 2020-12-04 05:07

    If you fall into situation when you should add enum values in transaction, f.e. execute it in flyway migration on ALTER TYPE statement you will be get error ERROR: ALTER TYPE ... ADD cannot run inside a transaction block (see flyway issue #350) you could add such values into pg_enum directly as workaround (type_egais_units is name of target 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 )
    
    0 讨论(0)
  • 2020-12-04 05:08

    Here is a more general but a rather fast-working solution, which apart from changing the type itself updates all columns in the database using it. The method can be applied even if a new version of ENUM is different by more than one label or misses some of the original ones. The code below replaces my_schema.my_type AS ENUM ('a', 'b', 'c') with 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();
    

    The whole process will run fairly quickly, because if the order of labels persists, no actual change of data will happen. I applied the method on 5 tables using my_type and having 50,000−70,000 rows in each, and the whole process took just 10 seconds.

    Of course, the function will return an exception in case if labels that are missing in the new version of the ENUM are used somewhere in the data, but in such situation something should be done beforehand anyway.

    0 讨论(0)
  • 2020-12-04 05:10

    If you are using Postgres 12 you can just run ALTER TYPE ... ADD VALUE inside of transaction (documentation).

    If ALTER TYPE ... ADD VALUE (the form that adds a new value to an enum type) is executed inside a transaction block, the new value cannot be used until after the transaction has been committed.

    So no hacks needed in migrations.

    UPD: here is an example (thanks to Nick for it)

    ALTER TYPE enum_type ADD VALUE 'new_value';

    0 讨论(0)
  • 2020-12-04 05:10

    Can't add a comment to the appropriate place, but ALTER TABLE foo ALTER COLUMN bar TYPE new_enum_type USING bar::text::new_enum_type with a default on the column failed. I had to:

    ALTER table ALTER COLUMN bar DROP DEFAULT;

    and then it worked.

    0 讨论(0)
  • 2020-12-04 05:11

    Complementing @Dariusz 1

    For Rails 4.2.1, there's this doc section:

    == Transactional Migrations

    If the database adapter supports DDL transactions, all migrations will automatically be wrapped in a transaction. There are queries that you can't execute inside a transaction though, and for these situations you can turn the automatic transactions off.

    class ChangeEnum < ActiveRecord::Migration
      disable_ddl_transaction!
    
      def up
        execute "ALTER TYPE model_size ADD VALUE 'new_value'"
      end
    end
    
    0 讨论(0)
  • 2020-12-04 05:12

    just in case, if you are using Rails and you have several statements you will need to execute one by one, like:

    execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'YYY';"
    execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'ZZZ';"
    
    0 讨论(0)
提交回复
热议问题