기존 ENUM 유형에 새 값 추가


208

enum유형 을 사용하는 테이블 열이 있습니다. enum가능한 추가 값을 갖도록 해당 유형 을 업데이트하고 싶습니다 . 기존 값을 삭제하고 싶지 않고 새 값을 추가하십시오. 가장 간단한 방법은 무엇입니까?

답변:


153

참고 PostgreSQL 9.1 이상을 사용 중이고 트랜잭션 외부에서 변경을 수행해도 괜찮은 경우 더 간단한 접근 방법 은 이 답변 을 참조하십시오 .


며칠 전에 같은 문제가 있었고이 게시물을 찾았습니다. 그래서 내 대답은 해결책을 찾는 사람에게 도움이 될 수 있습니다. :)

변경할 열거 형을 사용하는 하나 또는 두 개의 열만있는 경우이를 시도 할 수 있습니다. 또한 새 유형의 값 순서를 변경할 수 있습니다.

-- 1. rename the enum type you want to change
alter type some_enum_type rename to _some_enum_type;
-- 2. create new type
create type some_enum_type as enum ('old', 'values', 'and', 'new', 'ones');
-- 3. rename column(s) which uses our enum type
alter table some_table rename column some_column to _some_column;
-- 4. add new column of new type
alter table some_table add some_column some_enum_type not null default 'new';
-- 5. copy values to the new column
update some_table set some_column = _some_column::text::some_enum_type;
-- 6. remove old column and type
alter table some_table drop column _some_column;
drop type _some_enum_type;

하나 이상의 열이 있으면 3-6을 반복해야합니다.


9
이 작업은 모두 단일 트랜잭션으로 수행 할 수 있으므로 프로덕션 데이터베이스에서 수행하는 것이 안전합니다.
David Leppik

52
이것은 결코 좋은 생각이 아닙니다. 9.1부터는 모두 할 수 있습니다 ALTER TYPE. 그러나 그 이전에도 ALTER TABLE foo ALTER COLUMN bar TYPE new_type USING bar::text::new_type;훨씬 우수했습니다.
Erwin Brandstetter

1
이전 버전의 Postgres는 이름 변경 유형을 지원하지 않습니다. 특히 Heroku의 Postgres 버전 (공유 db, PG 8.3을 사용한다고 생각합니다)은 지원하지 않습니다.
Ortwin Gentz

13
3, 4, 5 및 6 단계를 단일 문으로 함께 축소 할 수 있습니다.ALTER TABLE some_table ALTER COLUMN some_column TYPE some_enum_type USING some_column::text::some_enum_type;
glyphobet

3
라이브 테이블에서이 작업을 수행하는 경우 절차 중에 테이블을 잠그십시오. postgresql의 기본 트랜잭션 격리 수준으로 인해이 트랜잭션 중에 다른 트랜잭션이 새 행을 삽입 할 수 없으므로 행이 잘못 채워질 수 있습니다.
Sérgio Carvalho

422

PostgreSQL 9.1 은 다음과 같은 ALTER 열거 형 기능을 제공 합니다.

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';

1
"enum_type"은 무엇입니까? 필드 이름, table_field 이름? 또는 다른 것? 내가 어떻게 칠까? 테이블 "grades"가 있고 열 "type"이 있고 db dump에서 다음을 얻습니다. CONSTRAINT grades_type_check CHECK (((type) :: text = ANY ((ARRAY [ 'exam':: character varying, 'test')) : 문자 변경, '추가':: 문자 변경, '중기':: 문자 변경, '최종':: 문자 변경]] :: text [])))

1
enum_type은 자신의 열거 형 유형 이름 @mariotanenbaum입니다. 열거 형이 "유형"인 경우 이것이 사용해야합니다.
다리우스

26
하나를 제거 할 수 있습니까?
Ced

8
트랜잭션에서 실행되는 db-migrate를 사용하는 경우 @DrewNoakes의 설명에 추가하면 오류가 발생할 수 있습니다. 오류 : ALTER TYPE ... ADD는 트랜잭션 블록 내에서 실행할 수 없습니다. ) : stackoverflow.com/a/41696273/1161370
Mahesh

1
다우 마이그레이션이 불가능하므로 다른 방법을
사용해야

65

가능한 해결책은 다음과 같습니다. 전제 조건은 사용 된 열거 형 값에 충돌이없는 것입니다. (예를 들어 열거 형 값을 제거 할 때이 값을 더 이상 사용하지 않아야합니다.)

-- rename the old enum
alter type my_enum rename to my_enum__;
-- create the new enum
create type my_enum as enum ('value1', 'value2', 'value3');

-- alter all you enum columns
alter table my_table
  alter column my_column type my_enum using my_column::text::my_enum;

-- drop the old enum
drop type my_enum__;

또한 이런 방식으로 열 순서가 변경되지 않습니다.


1
+1 이것은 9.1 이전 버전으로가는 방법이며 여전히 요소를 삭제하거나 수정하는 방법입니다.

이것은 내 솔루션에 가장 적합한 대답입니다. 기존 열거 형에 새 열거 형을 추가하여 기존 열거 형을 모두 유지하고 새 열거 형을 추가합니다. 또한 업데이트 스크립트는 트랜잭션 방식입니다. 좋은 포스트!
대린 피터슨

1
훌륭한 답변! 주변을 피 해킹 pg_enum이 실제로 일을 휴식과 달리 트랜잭션입니다 수 있습니다 ALTER TYPE ... ADD.
NathanAldenSr

4
열에 기본값이있는 경우 다음 오류가 발생 default for column "my_column" cannot be cast automatically to type "my_enum"합니다.. 다음을 수행해야합니다. ALTER TABLE "my_table" ALTER COLUMN "my_column" DROP DEFAULT, ALTER COLUMN "my_column" TYPE "my_type" USING ("my_column"::text::"my_type"), ALTER COLUMN "my_column" SET DEFAULT 'my_default_value';
n1ru4l

30

당신은 추가해야하는 상황에 빠질 경우 enum거래의 값을, FE가 이동 경로의 마이그레이션을 실행 ALTER TYPE당신이 얻을 오류가있을 것입니다 문 ERROR: ALTER TYPE ... ADD cannot run inside a transaction block(참조 이동 경로 문제 # 350 당신이로 같은 값을 추가 할 수 있습니다) pg_enum해결 방법으로 직접 ( type_egais_units대상의 이름입니다 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 )

9
그러나 시스템 테이블이 변경되므로 관리자 권한을 부여해야합니다.
asnelzin

22

@Dariusz 보완 1

Rails 4.2.1에는 다음과 같은 문서 섹션이 있습니다.

== 트랜잭션 마이그레이션

데이터베이스 어댑터가 DDL 트랜잭션을 지원하면 모든 마이그레이션이 자동으로 트랜잭션으로 랩핑됩니다. 트랜잭션 내에서 실행할 수없는 쿼리가 있으며 이러한 상황에서는 자동 트랜잭션을 해제 할 수 있습니다.

class ChangeEnum < ActiveRecord::Migration
  disable_ddl_transaction!

  def up
    execute "ALTER TYPE model_size ADD VALUE 'new_value'"
  end
end

3
이! 현대 레일에서 열거 형을 가지고 노는 경우 이것이 바로 당신이 찾고있는 것입니다.
Eli Albert

1
큰 도움이되었습니다!
Dmytro Uhnichenko

10

Postgres 9.1 문서에서 :

ALTER TYPE name ADD VALUE new_enum_value [ { BEFORE | AFTER } existing_enum_value ]

예:

ALTER TYPE user_status ADD VALUE 'PROVISIONAL' AFTER 'NORMAL'

3
또한 문서에서 : 열거 형 값이 추가 된 비교는 때로는 열거 형의 원래 멤버 만 포함하는 비교보다 느릴 수 있습니다. [.... stackoverflow 주석에 대해 너무 오래 간절히 설명했습니다 ...] 속도 저하는 일반적으로 중요하지 않습니다. 그러나 중요한 경우 열거 형 유형을 삭제하고 다시 만들거나 데이터베이스를 덤프 및 다시로드하여 최적의 성능을 다시 얻을 수 있습니다.
Aaron Zinman

8

면책 조항 : 이 솔루션을 시도하지 않았으므로 작동하지 않을 수 있습니다 ;-)

보고 있어야합니다 pg_enum. 기존 ENUM의 레이블 만 변경하려는 경우 간단한 UPDATE가 수행합니다.

새로운 ENUM 값을 추가하려면

  • 먼저 새 값을 pg_enum . 새 값이 마지막 값이어야한다면 끝났습니다.
  • 그렇지 않은 경우 (기존 값 사이에 새로운 ENUM 값이 필요함), 테이블에서 각각의 고유 한 값을 가장 높은 값에서 가장 낮은 값으로 업데이트해야합니다.
  • 그런 다음 pg_enum반대 순서 로 이름을 바꾸면됩니다 .

그림
당신은 라벨의 다음 세트를 가지고 :

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

그리고 당신은 얻고 싶습니다 :

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

그때:

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

그때:

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

등등...


1
"일반적으로 카탈로그를 수동으로 조작하지 말고 시스템 테이블을 해킹하는 것을 피해야합니다." 앤드류 던스턴과 나는 그가 옳다고 생각하는 경향이 있다고 말합니다.

5

의견을 게시 할 수 없으므로 pg_enum을 업데이트하면 Postgres 8.4에서 작동한다고 말할 수 있습니다. 열거 형을 설정하는 방법으로 다음을 통해 기존 열거 형에 새로운 값을 추가했습니다.

INSERT INTO pg_enum (enumtypid, enumlabel)
  SELECT typelem, 'NEWENUM' FROM pg_type WHERE
    typname = '_ENUMNAME_WITH_LEADING_UNDERSCORE';

약간 무섭지 만 Postgres가 실제로 데이터를 저장하는 방식을 고려하면 의미가 있습니다.


1
좋은 대답입니다! 새로운 열거 형을 추가하는 데 도움이되지만 재정렬 해야하는 경우는 분명히 해결되지 않습니다.
Mahmoud Abdelkader

3
"일반적으로 카탈로그를 수동으로 조작하지 말고 시스템 테이블을 해킹하는 것을 피해야합니다." 앤드류 던스턴과 나는 그가 옳다고 생각하는 경향이 있다고 말합니다.

typename에 대한 밑줄과 함께 대소 문자를 구분합니다. pg_type 테이블에서 typename으로 선택하려고 거의 마음을 잃었습니다.
Mahesh

5

pg_enum 업데이트는 위에서 강조한 중간 열 트릭과 마찬가지로 작동합니다. USING magic을 사용하여 열 유형을 직접 변경할 수도 있습니다.

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;

해당 열거 형을 명시 적으로 요구하거나 반환하는 함수가 없다면 좋을 것입니다. (있는 경우 pgsql은 유형을 삭제할 때 불평합니다.)

또한 PG9.1에 열거 형에서 작동하는 ALTER TYPE 문이 도입되었습니다.

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


PostgreSQL의 9.1 관련 문서는 이제에서 찾을 수 있습니다 postgresql.org/docs/9.1/static/sql-altertype.html
위 쳐트 아커 맨에게

1
ALTER TABLE foo ALTER COLUMN bar TYPE test USING bar::text::new_type;그러나 크게 관련이없는 지금 ...
어윈 Brandstetter

어윈의 말과 비슷하게 ... USING bar::type저를 위해 일했습니다. 나는 심지어 지정할 필요조차 없었다 ::text.
Daniel Werner

3

가장 간단 : 열거 형을 제거하십시오. 그것들은 쉽게 수정할 수 없으므로 매우 드물게 사용해야합니다.


2
아마도 간단한 검사 제한이 있습니까?

1
그리고 값을 문자열로 저장하는 문제는 정확히 무엇입니까?

5
@Grazer : 9.1에서는 enum에 값을 추가 할 수 있지만 ( depesz.com/index.php/2010/10/27/… ), 여전히 오래된 값 은 제거 할 수 없습니다.

3
@WillSheppard - 나는 그것을 기본적으로 결코 생각하지 않는다. 나는 어떤 경우에도 검사 제한 조건이있는 텍스트를 기반으로 한 사용자 정의 유형이 더 좋다고 생각합니다.

3
@JackDouglas-확실합니다. 언젠가 열거 형을 통해 도메인을 확인했습니다.

3

적절한 위치에 댓글을 추가 할 수 없지만 ALTER TABLE foo ALTER COLUMN bar TYPE new_enum_type USING bar::text::new_enum_type열의 기본값으로 실패했습니다. 나는해야했다 :

ALTER table ALTER COLUMN bar DROP DEFAULT;

그런 다음 효과가있었습니다.


3

만약 Rails를 사용하고 있고 여러 문장이 있다면 다음과 같이 하나씩 실행해야합니다 :

execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'YYY';"
execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'ZZZ';"

1

다음은 유형 자체를 변경하는 것 외에도 데이터베이스를 사용하여 데이터베이스의 모든 열을 업데이트하는보다 일반적이지만 다소 빠른 솔루션입니다. 이 방법은 새 버전의 ENUM이 둘 이상의 레이블에 의해 다르거 나 일부 원래 레이블이 누락 된 경우에도 적용 할 수 있습니다. 아래 코드는 다음으로 대체 my_schema.my_type AS ENUM ('a', 'b', 'c')됩니다 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();

레이블 순서가 지속되면 실제 데이터 변경이 발생하지 않기 때문에 전체 프로세스가 상당히 빠르게 실행됩니다. my_type각 테이블에 50,000 ~ 70,000 개의 행을 사용 하고 5 개의 테이블 에이 방법을 적용했으며 전체 프로세스는 10 초 밖에 걸리지 않았습니다.

물론, ENUM의 새 버전에서 누락 된 레이블이 데이터 어딘가에 사용되는 경우 함수는 예외를 리턴하지만, 이러한 상황에서는 미리 무언가를 수행해야합니다.


정말 귀중합니다. 문제는 이전 ENUM을 사용하는 뷰에 있습니다. 삭제하고 다시 작성해야합니다. 삭제 된보기에 따라 다른보기를 고려하면 훨씬 더 복잡합니다. 복합 유형에 대해 말하지 않는 ...
Ondřej Bouda

1

거래 중 솔루션을 찾는 사람들에게는 다음과 같이 작동하는 것 같습니다.

대신의 ENUM, a는 DOMAIN타입에 사용되어야한다 TEXT제약은 (일부 의견에 의해 제안) 값이 허용되는 값의 지정된 목록 내에 있는지 확인과 함께. 유일한 문제는 어떤 컴포지트 유형에 의해 도메인이 사용될 경우 제약 조건이 도메인에 추가 (따라서 수정되지 않음) 될 수 없다는 것입니다. 그러나 이러한 제한은 다음과 같이 함수를 호출하는 제약 조건을 사용하여 해결할 수 있습니다.

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;

이전에는 허용 된 답변과 비슷한 솔루션을 사용했지만 뷰 또는 함수 또는 복합 유형 (특히 수정 된 ENUM을 사용하는 다른 뷰를 사용하는 뷰)을 고려하면 좋은 것은 아닙니다. 이 답변에서 제안 된 솔루션은 모든 조건에서 작동하는 것 같습니다.

유일한 단점은 일부 허용 된 값이 제거 될 때 (특히이 질문에 허용 될 수있는) 기존 데이터에 대한 검사가 수행되지 않는다는 것입니다. (행 호출 ALTER DOMAIN test_domain VALIDATE CONSTRAINT val_check불행히도, 복합 타입에 의해 사용되는 도메인에 새로운 제약을 추가하는 것과 같은 에러 끝난다).

함수가 허용 된 값의 목록을 반환 한 CHECK (value = ANY(get_allowed_values()))곳 과 같은 약간의 수정 get_allowed_values()은 작동하지 않습니다. 이는 매우 이상합니다. 위에서 제안한 솔루션이 안정적으로 작동하기를 바랍니다 (지금까지는 가능합니다 ...). (실제로 작동합니다-내 오류였습니다)


0

위에서 설명한 것처럼 ALTER트랜잭션 내부 에서 명령을 쓸 수 없습니다. 제안 된 방법은 retrieving the typelem from pg_type tableand로 calculating the next enumsortorder number; pg_enum 테이블에 직접 삽입하는 것입니다 .

다음은 내가 사용하는 코드입니다. (삽입하기 전에 중복 값이 ​​존재하는지 확인합니다 (enumtypid와 enumlabel 이름 사이의 제약 조건)

INSERT INTO pg_enum (enumtypid, enumlabel, enumsortorder)
    SELECT typelem,
    'NEW_ENUM_VALUE',
    (SELECT MAX(enumsortorder) + 1 
        FROM pg_enum e
        JOIN pg_type p
        ON p.typelem = e.enumtypid
        WHERE p.typname = '_mytypename'
    )
    FROM pg_type p
    WHERE p.typname = '_mytypename'
    AND NOT EXISTS (
        SELECT * FROM 
        pg_enum e
        JOIN pg_type p
        ON p.typelem = e.enumtypid
        WHERE e.enumlabel = 'NEW_ENUM_VALUE'
        AND p.typname = '_mytypename'
    )

유형 이름 앞에 pg_type 테이블에 밑줄이 붙습니다. 또한 where 절에서 typname은 모두 소문자 여야합니다.

이제 이것을 db 마이그레이션 스크립트에 안전하게 쓸 수 있습니다.


-1

다른 옵션이 있는지 모르겠지만 다음을 사용하여 값을 삭제할 수 있습니다.

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;

-2

Navicat을 사용하는 경우 유형 (보기-> 기타-> 유형)으로 이동하여 유형의 디자인보기를 얻은 다음 "라벨 추가"버튼을 클릭하십시오.


1
멋지지만 실생활에서는 유용하지 않습니다 :ERROR: cannot drop type foo because other objects depend on it HINT: Use DROP ... CASCADE to drop the dependent objects too.
Ortwin Gentz

이상하게도 그것은 나를 위해 일했습니다. (TS가 열거 형 필드에만 값을 추가하려고 할 때 왜 DROP을 사용하는지 잘 모르겠습니다)
jvv

1
DROP을 구체적으로하지는 않았지만 정확히 절차를 마쳤습니다. Navicat이 장면 뒤에서 DROP을 수행하고 실패한다고 가정합니다. Navicat 9.1.5 Lite를 사용하고 있습니다.
Ortwin Gentz
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.