기존 ENUM 유형에 새 값 추가
enum
유형 을 사용하는 테이블 열이 있습니다. enum
가능한 추가 값을 갖도록 해당 유형 을 업데이트하고 싶습니다 . 기존 값을 삭제하고 싶지 않고 새 값을 추가하십시오. 가장 간단한 방법은 무엇입니까?
참고 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을 반복해야합니다.
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__;
또한 이런 방식으로 열 순서가 변경되지 않습니다.
당신은 추가해야하는 상황에 빠질 경우 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
. 새 값이 마지막 값이어야한다면 완료된 것입니다. - 그렇지 않은 경우 (기존 값 사이에 새로운 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은 유형을 삭제할 때 불평합니다.)
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
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.
Simplest: get rid of enums. They are not easily modifiable, and thus should very rarely be used.
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.
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
(it works, actually - it was my error)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...).
As discussed above, ALTER
command cannot be written inside a transaction. The suggested way is to insert into the pg_enum table directly, by retrieving the typelem from pg_type table
and calculating the next enumsortorder number
;
Following is the code that I use. (Checks if duplicate value exists before inserting (constraint between enumtypid and enumlabel name)
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'
)
Note that your type name is prepended with an underscore in the pg_type table. Also, the typname needs to be all lowercase in the where clause.
Now this can be written safely into your db migrate script.
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;
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.
참고URL : https://stackoverflow.com/questions/1771543/adding-a-new-value-to-an-existing-enum-type
'IT story' 카테고리의 다른 글
Python mysqldb : 라이브러리가로드되지 않았습니다 : libmysqlclient.18.dylib (0) | 2020.05.27 |
---|---|
Pandas Datetime 열과 별도로 월과 연도 추출 (0) | 2020.05.27 |
Rails에서 관련 레코드가없는 레코드를 찾으려면 (0) | 2020.05.27 |
네비게이션 바에서 뒤로 버튼의 색상 변경 (0) | 2020.05.27 |
PHP에서 배열을 반향 또는 인쇄하는 방법은 무엇입니까? (0) | 2020.05.27 |