답변:
참고 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을 반복해야합니다.
ALTER TYPE
. 그러나 그 이전에도 ALTER TABLE foo ALTER COLUMN bar TYPE new_type USING bar::text::new_type;
훨씬 우수했습니다.
ALTER TABLE some_table ALTER COLUMN some_column TYPE some_enum_type USING some_column::text::some_enum_type;
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';
가능한 해결책은 다음과 같습니다. 전제 조건은 사용 된 열거 형 값에 충돌이없는 것입니다. (예를 들어 열거 형 값을 제거 할 때이 값을 더 이상 사용하지 않아야합니다.)
-- 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__;
또한 이런 방식으로 열 순서가 변경되지 않습니다.
pg_enum
이 실제로 일을 휴식과 달리 트랜잭션입니다 수 있습니다 ALTER TYPE ... ADD
.
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';
당신은 추가해야하는 상황에 빠질 경우 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 )
@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
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'
면책 조항 : 이 솔루션을 시도하지 않았으므로 작동하지 않을 수 있습니다 ;-)
보고 있어야합니다 pg_enum
. 기존 ENUM의 레이블 만 변경하려는 경우 간단한 UPDATE가 수행합니다.
새로운 ENUM 값을 추가하려면
pg_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;
등등...
의견을 게시 할 수 없으므로 pg_enum을 업데이트하면 Postgres 8.4에서 작동한다고 말할 수 있습니다. 열거 형을 설정하는 방법으로 다음을 통해 기존 열거 형에 새로운 값을 추가했습니다.
INSERT INTO pg_enum (enumtypid, enumlabel)
SELECT typelem, 'NEWENUM' FROM pg_type WHERE
typname = '_ENUMNAME_WITH_LEADING_UNDERSCORE';
약간 무섭지 만 Postgres가 실제로 데이터를 저장하는 방식을 고려하면 의미가 있습니다.
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
ALTER TABLE foo ALTER COLUMN bar TYPE test USING bar::text::new_type;
그러나 크게 관련이없는 지금 ...
... USING bar::type
저를 위해 일했습니다. 나는 심지어 지정할 필요조차 없었다 ::text
.
가장 간단 : 열거 형을 제거하십시오. 그것들은 쉽게 수정할 수 없으므로 매우 드물게 사용해야합니다.
다음은 유형 자체를 변경하는 것 외에도 데이터베이스를 사용하여 데이터베이스의 모든 열을 업데이트하는보다 일반적이지만 다소 빠른 솔루션입니다. 이 방법은 새 버전의 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
, 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()
은 작동하지 않습니다. 이는 매우 이상합니다. 위에서 제안한 솔루션이 안정적으로 작동하기를 바랍니다 (지금까지는 가능합니다 ...).
위에서 설명한 것처럼 ALTER
트랜잭션 내부 에서 명령을 쓸 수 없습니다. 제안 된 방법은 retrieving the typelem from pg_type table
and로 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 마이그레이션 스크립트에 안전하게 쓸 수 있습니다.
Navicat을 사용하는 경우 유형 (보기-> 기타-> 유형)으로 이동하여 유형의 디자인보기를 얻은 다음 "라벨 추가"버튼을 클릭하십시오.
ERROR: cannot drop type foo because other objects depend on it HINT: Use DROP ... CASCADE to drop the dependent objects too.