Câu trả lời:
LƯU Ý nếu bạn đang sử dụng PostgreSQL 9.1 trở lên và bạn vẫn ổn khi thực hiện các thay đổi bên ngoài giao dịch, hãy xem câu trả lời này để biết cách tiếp cận đơn giản hơn.
Tôi đã có cùng một vấn đề vài ngày trước và tìm thấy bài đăng này. Vì vậy, câu trả lời của tôi có thể hữu ích cho những người đang tìm kiếm giải pháp :)
Nếu bạn chỉ có một hoặc hai cột sử dụng loại enum bạn muốn thay đổi, bạn có thể thử điều này. Ngoài ra, bạn có thể thay đổi thứ tự của các giá trị trong loại mới.
-- 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 nên được lặp lại nếu có nhiều hơn 1 cột.
ALTER TYPE
. Nhưng ngay cả trước đó, ALTER TABLE foo ALTER COLUMN bar TYPE new_type USING bar::text::new_type;
đã vượt trội hơn nhiều.
ALTER TABLE some_table ALTER COLUMN some_column TYPE some_enum_type USING some_column::text::some_enum_type;
PostgreQuery 9.1 giới thiệu khả năng ALTER Enum loại:
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';
Một giải pháp có thể là như sau; điều kiện tiên quyết là, không có xung đột trong các giá trị enum được sử dụng. (ví dụ: khi xóa giá trị enum, hãy chắc chắn rằng giá trị này không được sử dụng nữa.)
-- 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__;
Cũng theo cách này, thứ tự cột sẽ không được thay đổi.
pg_enum
có thể thực sự phá vỡ mọi thứ và là giao dịch, không giống như ALTER TYPE ... ADD
.
default for column "my_column" cannot be cast automatically to type "my_enum"
. Bạn sẽ phải làm như sau: 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';
Nếu bạn rơi vào tình huống khi bạn nên thêm enum
các giá trị trong giao dịch, hãy thực hiện nó trong di chuyển đường bay trên ALTER TYPE
câu lệnh, bạn sẽ gặp lỗi ERROR: ALTER TYPE ... ADD cannot run inside a transaction block
(xem vấn đề đường bay số 350 ), bạn có thể thêm các giá trị đó vào pg_enum
trực tiếp như cách giải quyết ( type_egais_units
là tên của mục tiêu 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 )
Bổ sung @Dariusz 1
Đối với Rails 4.2.1, có phần tài liệu này:
== Di chuyển giao dịch
Nếu bộ điều hợp cơ sở dữ liệu hỗ trợ các giao dịch DDL, tất cả các lần di chuyển sẽ tự động được gói trong một giao dịch. Tuy nhiên, có những truy vấn mà bạn không thể thực hiện trong một giao dịch và đối với những tình huống này, bạn có thể tắt các giao dịch tự động.
class ChangeEnum < ActiveRecord::Migration
disable_ddl_transaction!
def up
execute "ALTER TYPE model_size ADD VALUE 'new_value'"
end
end
Từ Tài liệu Postgres 9.1 :
ALTER TYPE name ADD VALUE new_enum_value [ { BEFORE | AFTER } existing_enum_value ]
Thí dụ:
ALTER TYPE user_status ADD VALUE 'PROVISIONAL' AFTER 'NORMAL'
Tuyên bố từ chối trách nhiệm: Tôi chưa thử giải pháp này, vì vậy nó có thể không hoạt động ;-)
Bạn nên nhìn vào pg_enum
. Nếu bạn chỉ muốn thay đổi nhãn của ENUM hiện có, một CẬP NHẬT đơn giản sẽ làm điều đó.
Để thêm một giá trị ENUM mới:
pg_enum
. Nếu giá trị mới phải là giá trị cuối cùng, bạn đã hoàn thành.pg_enum
thứ tự ngược lại.Minh họa
Bạn có bộ nhãn sau:
ENUM ('enum1', 'enum2', 'enum3')
và bạn muốn có được:
ENUM ('enum1', 'enum1b', 'enum2', 'enum3')
sau đó:
INSERT INTO pg_enum (OID, 'newenum3');
UPDATE TABLE SET enumvalue TO 'newenum3' WHERE enumvalue='enum3';
UPDATE TABLE SET enumvalue TO 'enum3' WHERE enumvalue='enum2';
sau đó:
UPDATE TABLE pg_enum SET name='enum1b' WHERE name='enum2' AND enumtypid=OID;
Và như thế...
Tôi dường như không thể đăng bình luận, vì vậy tôi sẽ chỉ nói rằng việc cập nhật pg_enum hoạt động trong Postgres 8.4. Đối với cách các enum của chúng tôi được thiết lập, tôi đã thêm các giá trị mới vào các loại enum hiện có thông qua:
INSERT INTO pg_enum (enumtypid, enumlabel)
SELECT typelem, 'NEWENUM' FROM pg_type WHERE
typname = '_ENUMNAME_WITH_LEADING_UNDERSCORE';
Điều đó hơi đáng sợ, nhưng thật hợp lý khi Postgres thực sự lưu trữ dữ liệu của mình.
Cập nhật pg_enum hoạt động, cũng như thủ thuật cột trung gian được tô sáng ở trên. Người ta cũng có thể sử dụng ma thuật SỬ DỤNG để thay đổi trực tiếp loại cột:
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;
Miễn là bạn không có chức năng nào yêu cầu rõ ràng hoặc trả lại enum đó, bạn vẫn ổn. (pssql sẽ khiếu nại khi bạn loại bỏ nếu có.)
Ngoài ra, lưu ý rằng PG9.1 đang giới thiệu câu lệnh ALTER TYPE, sẽ hoạt động trên enums:
http://developer.postgresql.org/pgdocs/postgres/release-9-1-alpha.html
ALTER TABLE foo ALTER COLUMN bar TYPE test USING bar::text::new_type;
Nhưng phần lớn không liên quan bây giờ ...
... USING bar::type
đã làm việc cho tôi. Tôi thậm chí không phải xác định ::text
.
Đơn giản nhất: thoát khỏi enums Chúng không dễ dàng sửa đổi, và do đó rất hiếm khi được sử dụng.
Không thể thêm nhận xét vào vị trí thích hợp, nhưng ALTER TABLE foo ALTER COLUMN bar TYPE new_enum_type USING bar::text::new_enum_type
với mặc định trên cột không thành công. Tôi phải:
ALTER table ALTER COLUMN bar DROP DEFAULT
;
và sau đó nó hoạt động.
Đây là một giải pháp tổng quát hơn nhưng hoạt động khá nhanh, ngoài việc thay đổi kiểu chính nó sẽ cập nhật tất cả các cột trong cơ sở dữ liệu bằng cách sử dụng nó. Phương pháp có thể được áp dụng ngay cả khi một phiên bản ENUM mới khác với nhiều nhãn hoặc bỏ lỡ một số nhãn gốc. Mã dưới đây thay thế my_schema.my_type AS ENUM ('a', 'b', 'c')
bằng 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();
Toàn bộ quá trình sẽ chạy khá nhanh, bởi vì nếu thứ tự nhãn vẫn tồn tại, sẽ không có thay đổi dữ liệu thực tế nào xảy ra. Tôi đã áp dụng phương pháp này trên 5 bảng bằng cách sử dụng my_type
và có 50.000−70.000 hàng trong mỗi bảng và toàn bộ quá trình chỉ mất 10 giây.
Tất nhiên, hàm sẽ trả về một ngoại lệ trong trường hợp nếu các nhãn bị thiếu trong phiên bản mới của ENUM được sử dụng ở đâu đó trong dữ liệu, nhưng trong trường hợp như vậy, dù sao cũng nên làm gì đó.
Đối với những người tìm kiếm một giải pháp trong giao dịch, sau đây dường như hoạt động.
Thay vì một ENUM
, a DOMAIN
sẽ được sử dụng trên loại TEXT
với một ràng buộc kiểm tra rằng giá trị nằm trong danh sách được chỉ định của các giá trị được phép (như được đề xuất bởi một số ý kiến). Vấn đề duy nhất là không có ràng buộc nào có thể được thêm vào (và do đó không được sửa đổi) cho một miền nếu nó được sử dụng bởi bất kỳ loại hỗn hợp nào (các tài liệu chỉ nói rằng "cuối cùng sẽ được cải thiện"). Một hạn chế như vậy có thể được xử lý xung quanh, tuy nhiên, bằng cách sử dụng một ràng buộc gọi hàm, như sau.
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;
Trước đây, tôi đã sử dụng một giải pháp tương tự như câu trả lời được chấp nhận, nhưng nó còn lâu mới tốt khi các chế độ xem hoặc chức năng hoặc loại tổng hợp (và đặc biệt là các chế độ xem sử dụng các chế độ xem khác sử dụng ENUM đã sửa đổi ...) được xem xét. Giải pháp được đề xuất trong câu trả lời này dường như hoạt động trong bất kỳ điều kiện nào.
Nhược điểm duy nhất là không có kiểm tra nào được thực hiện trên dữ liệu hiện có khi một số giá trị được phép bị xóa (có thể được chấp nhận, đặc biệt đối với câu hỏi này). ( ALTER DOMAIN test_domain VALIDATE CONSTRAINT val_check
Thật không may, một cuộc gọi kết thúc với cùng một lỗi như thêm một ràng buộc mới vào miền được sử dụng bởi một loại hỗn hợp.)
Lưu ý rằng một sửa đổi nhỏ như (nó hoạt động, thực sự - đó là lỗi của tôi)CHECK (value = ANY(get_allowed_values()))
, trong đó get_allowed_values()
hàm trả về danh sách các giá trị được phép, sẽ không hoạt động - điều này khá lạ, vì vậy tôi hy vọng giải pháp được đề xuất ở trên hoạt động đáng tin cậy (cho đến nay ...).
Như đã thảo luận ở trên, ALTER
lệnh không thể được viết trong một giao dịch. Cách được đề xuất là chèn trực tiếp vào bảng pg_enum, bởi retrieving the typelem from pg_type table
và calculating the next enumsortorder number
;
Sau đây là mã mà tôi sử dụng. (Kiểm tra nếu giá trị trùng lặp tồn tại trước khi chèn (ràng buộc giữa tên enumtypid và tên 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'
)
Lưu ý rằng tên loại của bạn được thêm vào một dấu gạch dưới trong bảng pg_type. Ngoài ra, tên chữ cần phải là chữ thường trong mệnh đề where.
Bây giờ điều này có thể được viết một cách an toàn vào tập lệnh di chuyển db của bạn.
Tôi không biết nếu có tùy chọn khác nhưng chúng tôi có thể loại bỏ giá trị bằng cách sử dụng:
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;
Khi sử dụng Navicat, bạn có thể chuyển đến các loại (trong chế độ xem -> loại khác -> loại) - nhận chế độ xem thiết kế của loại - và nhấp vào nút "thêm nhãn".
ERROR: cannot drop type foo because other objects depend on it HINT: Use DROP ... CASCADE to drop the dependent objects too.