Odpowiedzi:
UWAGA: jeśli korzystasz z PostgreSQL 9.1 lub nowszego i nie masz nic przeciwko wprowadzaniu zmian poza transakcją, zapoznaj się z tą odpowiedzią, aby uzyskać prostsze podejście.
Miałem ten sam problem kilka dni temu i znalazłem ten post. Moja odpowiedź może być więc pomocna dla kogoś, kto szuka rozwiązania :)
Jeśli masz tylko jedną lub dwie kolumny wykorzystujące typ wyliczenia, który chcesz zmienić, możesz spróbować. Możesz także zmienić kolejność wartości w nowym typie.
-- 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 należy powtórzyć, jeśli jest więcej niż 1 kolumna.
ALTER TYPE
. Ale nawet wcześniej ALTER TABLE foo ALTER COLUMN bar TYPE new_type USING bar::text::new_type;
był znacznie lepszy.
ALTER TABLE some_table ALTER COLUMN some_column TYPE some_enum_type USING some_column::text::some_enum_type;
PostgreSQL 9.1 wprowadza możliwość ALTER typów ENUM:
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';
Możliwe rozwiązanie jest następujące; warunkiem wstępnym jest brak konfliktów w używanych wartościach wyliczeniowych. (np. usuwając wartość wyliczenia, upewnij się, że ta wartość nie jest już używana).
-- 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__;
Również w ten sposób kolejność kolumn nie zostanie zmieniona.
pg_enum
które mogą uszkodzić rzeczy i jest transakcyjne, w przeciwieństwie do ALTER TYPE ... ADD
.
default for column "my_column" cannot be cast automatically to type "my_enum"
. Będziesz musiał wykonać następujące czynności: 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';
Jeśli wpadniesz w sytuację, w której powinieneś dodać enum
wartości w transakcji, np. Wykonaj ją podczas migracji flyway na ALTER TYPE
wyciągu, otrzymasz błąd ERROR: ALTER TYPE ... ADD cannot run inside a transaction block
(patrz problem flyway nr 350 ), możesz dodać takie wartości pg_enum
bezpośrednio jako obejście ( type_egais_units
to nazwa celu 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 )
Uzupełnienie @Dariusz 1
W przypadku Railsów 4.2.1 dostępna jest sekcja dokumentu:
== Migracje transakcyjne
Jeśli adapter bazy danych obsługuje transakcje DDL, wszystkie migracje zostaną automatycznie opakowane w transakcję. Istnieją jednak zapytania, których nie można wykonać w ramach transakcji, aw takich sytuacjach można wyłączyć automatyczne transakcje.
class ChangeEnum < ActiveRecord::Migration
disable_ddl_transaction!
def up
execute "ALTER TYPE model_size ADD VALUE 'new_value'"
end
end
Z dokumentacji Postgres 9.1 :
ALTER TYPE name ADD VALUE new_enum_value [ { BEFORE | AFTER } existing_enum_value ]
Przykład:
ALTER TYPE user_status ADD VALUE 'PROVISIONAL' AFTER 'NORMAL'
Oświadczenie: Nie wypróbowałem tego rozwiązania, więc może nie działać ;-)
Powinieneś patrzeć pg_enum
. Jeśli chcesz tylko zmienić etykietę istniejącego ENUM, wystarczy prosta AKTUALIZACJA.
Aby dodać nowe wartości ENUM:
pg_enum
. Jeśli nowa wartość musi być ostatnia, gotowe.pg_enum
w odwrotnej kolejności.Ilustracja
Masz następujący zestaw etykiet:
ENUM ('enum1', 'enum2', 'enum3')
i chcesz uzyskać:
ENUM ('enum1', 'enum1b', 'enum2', 'enum3')
następnie:
INSERT INTO pg_enum (OID, 'newenum3');
UPDATE TABLE SET enumvalue TO 'newenum3' WHERE enumvalue='enum3';
UPDATE TABLE SET enumvalue TO 'enum3' WHERE enumvalue='enum2';
następnie:
UPDATE TABLE pg_enum SET name='enum1b' WHERE name='enum2' AND enumtypid=OID;
I tak dalej...
Nie mogę opublikować komentarza, więc powiem tylko, że aktualizacja pg_enum działa w Postgres 8.4. Aby skonfigurować nasze wyliczenia, dodałem nowe wartości do istniejących typów wyliczeń poprzez:
INSERT INTO pg_enum (enumtypid, enumlabel)
SELECT typelem, 'NEWENUM' FROM pg_type WHERE
typname = '_ENUMNAME_WITH_LEADING_UNDERSCORE';
To trochę przerażające, ale ma sens, biorąc pod uwagę sposób, w jaki Postgres faktycznie przechowuje swoje dane.
Aktualizacja pg_enum działa, podobnie jak sztuczka z kolumną pośrednią zaznaczona powyżej. Można także użyć UŻYWANIA magii, aby bezpośrednio zmienić typ kolumny:
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;
Dopóki nie masz żadnych funkcji, które wyraźnie wymagają lub zwracają ten wyliczenie, jesteś dobry. (pgsql będzie narzekać, gdy upuścisz ten typ, jeśli istnieje).
Zauważ też, że PG9.1 wprowadza instrukcję ALTER TYPE, która będzie działać na wyliczeniach:
http://developer.postgresql.org/pgdocs/postgres/release-9-1-alpha.html
ALTER TABLE foo ALTER COLUMN bar TYPE test USING bar::text::new_type;
Ale w dużej mierze teraz nieistotne ...
... USING bar::type
pracował dla mnie. Nie musiałem nawet określać ::text
.
Najprościej: pozbądź się enumów. Nie można ich łatwo modyfikować i dlatego bardzo rzadko powinny być używane.
Nie można dodać komentarza do odpowiedniego miejsca, ale ALTER TABLE foo ALTER COLUMN bar TYPE new_enum_type USING bar::text::new_enum_type
ustawienie domyślne w kolumnie nie powiodło się. Musiałem:
ALTER table ALTER COLUMN bar DROP DEFAULT
;
i wtedy zadziałało.
Oto bardziej ogólne, ale raczej szybko działające rozwiązanie, które oprócz zmiany samego typu aktualizuje wszystkie kolumny w bazie danych, używając go. Metodę można zastosować, nawet jeśli nowa wersja ENUM różni się o więcej niż jedną etykietę lub pomija niektóre z oryginalnych. Poniższy kod zastępuje my_schema.my_type AS ENUM ('a', 'b', 'c')
się następującym 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();
Cały proces będzie przebiegał dość szybko, ponieważ jeśli kolejność etykiet będzie się utrzymywać, nie nastąpi faktyczna zmiana danych. Zastosowałem tę metodę na 5 tabelach, używając my_type
i mając 50 000–70 000 wierszy w każdym, a cały proces trwał zaledwie 10 sekund.
Oczywiście funkcja zwróci wyjątek na wypadek, gdyby etykiety, których brakuje w nowej wersji ENUM, były używane gdzieś w danych, ale w takiej sytuacji i tak należy coś wcześniej zrobić.
Dla tych, którzy szukają rozwiązania transakcji, wydaje się, że działają następujące.
Zamiast ENUM
, A DOMAIN
stosuje się na typ TEXT
z ograniczeniem sprawdzenie, czy wartość mieści się w podanym liście dozwolonych wartości (jak sugerują niektóre komentarze). Jedyny problem polega na tym, że żadne ograniczenie nie może zostać dodane (a więc i nie zmodyfikowane) do domeny, jeśli jest ono używane przez dowolny typ złożony (dokumenty mówią jedynie, że „powinno się to ostatecznie poprawić”). Takie ograniczenie można jednak obejść, stosując ograniczenie wywołujące funkcję, jak następuje.
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;
Wcześniej korzystałem z rozwiązania podobnego do przyjętej odpowiedzi, ale nie jest to dobre, gdy rozważane są widoki lub funkcje lub typy złożone (a zwłaszcza widoki z wykorzystaniem innych widoków przy użyciu zmodyfikowanych ENUM ...). Rozwiązanie zaproponowane w tej odpowiedzi wydaje się działać w każdych warunkach.
Jedyną wadą jest to, że nie są przeprowadzane żadne kontrole istniejących danych po usunięciu niektórych dozwolonych wartości (co może być dopuszczalne, szczególnie w przypadku tego pytania). ( ALTER DOMAIN test_domain VALIDATE CONSTRAINT val_check
Niestety wywołanie kończy się tym samym błędem, co dodanie nowego ograniczenia do domeny używanej przez typ złożony, niestety).
Należy pamiętać, że niewielkie modyfikacje, takie jak (faktycznie działa - to był mój błąd)CHECK (value = ANY(get_allowed_values()))
, gdzie get_allowed_values()
funkcja powrócił listę dozwolonych wartości, nie będzie działać - co jest dość dziwne, więc mam nadzieję, że rozwiązanie zaproponowane powyżej działa niezawodnie (to nie dla mnie, tak daleko ...).
Jak omówiono powyżej, ALTER
polecenia nie można zapisać w transakcji. Sugerowanym sposobem jest wstawienie bezpośrednio do tabeli pg_enum za pomocą retrieving the typelem from pg_type table
i calculating the next enumsortorder number
;
Poniżej znajduje się kod, którego używam. (Sprawdza, czy istnieje zduplikowana wartość przed wstawieniem (ograniczenie między enumtypid a nazwą 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'
)
Zauważ, że nazwa twojego typu jest poprzedzona znakiem podkreślenia w tabeli pg_type. Ponadto w nazwie klauzuli typ musi być pisany małymi literami.
Teraz można to bezpiecznie zapisać w skrypcie migracji db.
Nie wiem, czy mam inną opcję, ale możemy upuścić wartość, używając:
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;
Korzystając z Navicat, możesz przejść do typów (w widoku -> inne -> typy) - uzyskać widok projektu typu - i kliknąć przycisk „dodaj etykietę”.
ERROR: cannot drop type foo because other objects depend on it HINT: Use DROP ... CASCADE to drop the dependent objects too.