Adding a new value to an existing ENUM Type

前端 未结 18 1296
春和景丽
春和景丽 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:15

    Disclaimer: I haven't tried this solution, so it might not work ;-)

    You should be looking at pg_enum. If you only want to change the label of an existing ENUM, a simple UPDATE will do it.

    To add a new ENUM values:

    • First insert the new value into pg_enum. If the new value has to be the last, you're done.
    • If not (you need to a new ENUM value in between existing ones), you'll have to update each distinct value in your table, going from the uppermost to the lowest...
    • Then you'll just have to rename them in pg_enum in the opposite order.

    Illustration
    You have the following set of labels:

    ENUM ('enum1', 'enum2', 'enum3')
    

    and you want to obtain:

    ENUM ('enum1', 'enum1b', 'enum2', 'enum3')
    

    then:

    INSERT INTO pg_enum (OID, 'newenum3');
    UPDATE TABLE SET enumvalue TO 'newenum3' WHERE enumvalue='enum3';
    UPDATE TABLE SET enumvalue TO 'enum3' WHERE enumvalue='enum2';
    

    then:

    UPDATE TABLE pg_enum SET name='enum1b' WHERE name='enum2' AND enumtypid=OID;
    

    And so on...

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

    PostgreSQL 9.1 introduces ability to ALTER Enum types:

    ALTER TYPE enum_type ADD VALUE 'new_value'; -- appends to list
    ALTER TYPE enum_type ADD VALUE 'new_value' BEFORE 'old_value';
    ALTER TYPE enum_type ADD VALUE 'new_value' AFTER 'old_value';
    
    0 讨论(0)
  • 2020-12-04 05:19

    I don't know if have other option but we can drop the value using:

    select oid from pg_type where typname = 'fase';'
    select * from pg_enum where enumtypid = 24773;'
    select * from pg_enum where enumtypid = 24773 and enumsortorder = 6;
    delete from pg_enum where enumtypid = 24773 and enumsortorder = 6;
    
    0 讨论(0)
  • 2020-12-04 05:20

    For those looking for an in-transaction solution, the following seems to work.

    Instead of an ENUM, a DOMAIN shall be used on type TEXT with a constraint checking that the value is within the specified list of allowed values (as suggested by some comments). The only problem is that no constraint can be added (and thus neither modified) to a domain if it is used by any composite type (the docs merely says this "should eventually be improved"). Such a restriction may be worked around, however, using a constraint calling a function, as follows.

    START TRANSACTION;
    
    CREATE FUNCTION test_is_allowed_label(lbl TEXT) RETURNS BOOL AS $function$
        SELECT lbl IN ('one', 'two', 'three');
    $function$ LANGUAGE SQL IMMUTABLE;
    
    CREATE DOMAIN test_domain AS TEXT CONSTRAINT val_check CHECK (test_is_allowed_label(value));
    
    CREATE TYPE test_composite AS (num INT, word test_domain);
    
    CREATE TABLE test_table (val test_composite);
    INSERT INTO test_table (val) VALUES ((1, 'one')::test_composite), ((3, 'three')::test_composite);
    -- INSERT INTO test_table (val) VALUES ((4, 'four')::test_composite); -- restricted by the CHECK constraint
    
    CREATE VIEW test_view AS SELECT * FROM test_table; -- just to show that the views using the type work as expected
    
    CREATE OR REPLACE FUNCTION test_is_allowed_label(lbl TEXT) RETURNS BOOL AS $function$
        SELECT lbl IN ('one', 'two', 'three', 'four');
    $function$ LANGUAGE SQL IMMUTABLE;
    
    INSERT INTO test_table (val) VALUES ((4, 'four')::test_composite); -- allowed by the new effective definition of the constraint
    
    SELECT * FROM test_view;
    
    CREATE OR REPLACE FUNCTION test_is_allowed_label(lbl TEXT) RETURNS BOOL AS $function$
        SELECT lbl IN ('one', 'two', 'three');
    $function$ LANGUAGE SQL IMMUTABLE;
    
    -- INSERT INTO test_table (val) VALUES ((4, 'four')::test_composite); -- restricted by the CHECK constraint, again
    
    SELECT * FROM test_view; -- note the view lists the restricted value 'four' as no checks are made on existing data
    
    DROP VIEW test_view;
    DROP TABLE test_table;
    DROP TYPE test_composite;
    DROP DOMAIN test_domain;
    DROP FUNCTION test_is_allowed_label(TEXT);
    
    COMMIT;
    

    Previously, I used a solution similar to the accepted answer, but it is far from being good once views or functions or composite types (and especially views using other views using the modified ENUMs...) are considered. The solution proposed in this answer seems to work under any conditions.

    The only disadvantage is that no checks are performed on existing data when some allowed values are removed (which might be acceptable, especially for this question). (A call to ALTER DOMAIN test_domain VALIDATE CONSTRAINT val_check ends up with the same error as adding a new constraint to the domain used by a composite type, unfortunately.)

    Note that a slight modification such as CHECK (value = ANY(get_allowed_values())), where get_allowed_values() function returned the list of allowed values, would not work - which is quite strange, so I hope the solution proposed above works reliably (it does for me, so far...). (it works, actually - it was my error)

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

    When using Navicat you can go to types (under view -> others -> types) - get the design view of the type - and click the "add label" button.

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

    Updating pg_enum works, as does the intermediary column trick highlighted above. One can also use USING magic to change the column's type directly:

    CREATE TYPE test AS enum('a', 'b');
    CREATE TABLE foo (bar test);
    INSERT INTO foo VALUES ('a'), ('b');
    
    ALTER TABLE foo ALTER COLUMN bar TYPE varchar;
    
    DROP TYPE test;
    CREATE TYPE test as enum('a', 'b', 'c');
    
    ALTER TABLE foo ALTER COLUMN bar TYPE test
    USING CASE
    WHEN bar = ANY (enum_range(null::test)::varchar[])
    THEN bar::test
    WHEN bar = ANY ('{convert, these, values}'::varchar[])
    THEN 'c'::test
    ELSE NULL
    END;
    

    As long as you've no functions that explicitly require or return that enum, you're good. (pgsql will complain when you drop the type if there are.)

    Also, note that PG9.1 is introducing an ALTER TYPE statement, which will work on enums:

    http://developer.postgresql.org/pgdocs/postgres/release-9-1-alpha.html

    0 讨论(0)
提交回复
热议问题