Respostas:
OBSERVAÇÃO Se você estiver usando o PostgreSQL 9.1 ou posterior, e está autorizado a fazer alterações fora de uma transação, consulte esta resposta para uma abordagem mais simples.
Eu tive o mesmo problema há alguns dias e encontrei este post. Portanto, minha resposta pode ser útil para alguém que está procurando solução :)
Se você tiver apenas uma ou duas colunas que usam o tipo de enumeração que deseja alterar, tente isso. Além disso, você pode alterar a ordem dos valores no novo tipo.
-- 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 deve ser repetido se houver mais de uma coluna.
ALTER TYPE
. Mas mesmo antes disso, ALTER TABLE foo ALTER COLUMN bar TYPE new_type USING bar::text::new_type;
era muito superior.
ALTER TABLE some_table ALTER COLUMN some_column TYPE some_enum_type USING some_column::text::some_enum_type;
O PostgreSQL 9.1 introduz a capacidade dos tipos ALTER 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';
Uma solução possível é a seguinte; pré-condição é que não haja conflitos nos valores de enum usados. (por exemplo, ao remover um valor de enumeração, verifique se esse valor não é mais usado.)
-- 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__;
Além disso, a ordem das colunas não será alterada.
pg_enum
que podem realmente quebrar as coisas e são transacionais, ao contrário ALTER TYPE ... ADD
.
default for column "my_column" cannot be cast automatically to type "my_enum"
. Você deverá fazer o seguinte: 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';
Se você se enquadra na situação em que deve adicionar enum
valores na transação, executá-lo na migração flyway na ALTER TYPE
instrução, você receberá um erro ERROR: ALTER TYPE ... ADD cannot run inside a transaction block
(consulte o número de flyway nº 350 ). Você pode adicionar esses valores pg_enum
diretamente como solução alternativa ( type_egais_units
é o nome do destino 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 )
Complementando @Dariusz 1
Para o Rails 4.2.1, há esta seção de documento:
== Migrações transacionais
Se o adaptador de banco de dados suportar transações DDL, todas as migrações serão agrupadas automaticamente em uma transação. Existem consultas que você não pode executar dentro de uma transação e, para essas situações, pode desativar as transações automáticas.
class ChangeEnum < ActiveRecord::Migration
disable_ddl_transaction!
def up
execute "ALTER TYPE model_size ADD VALUE 'new_value'"
end
end
Da documentação do Postgres 9.1 :
ALTER TYPE name ADD VALUE new_enum_value [ { BEFORE | AFTER } existing_enum_value ]
Exemplo:
ALTER TYPE user_status ADD VALUE 'PROVISIONAL' AFTER 'NORMAL'
Disclaimer: Eu não tentei esta solução, por isso pode não funcionar ;-)
Você deveria estar olhando pg_enum
. Se você deseja alterar apenas o rótulo de um ENUM existente, uma simples UPDATE fará isso.
Para adicionar um novo valor ENUM:
pg_enum
. Se o novo valor tiver que ser o último, você está pronto.pg_enum
ordem oposta.Ilustração
Você tem o seguinte conjunto de etiquetas:
ENUM ('enum1', 'enum2', 'enum3')
e você deseja obter:
ENUM ('enum1', 'enum1b', 'enum2', 'enum3')
então:
INSERT INTO pg_enum (OID, 'newenum3');
UPDATE TABLE SET enumvalue TO 'newenum3' WHERE enumvalue='enum3';
UPDATE TABLE SET enumvalue TO 'enum3' WHERE enumvalue='enum2';
então:
UPDATE TABLE pg_enum SET name='enum1b' WHERE name='enum2' AND enumtypid=OID;
E assim por diante...
Não consigo postar um comentário, então vou apenas dizer que a atualização do pg_enum funciona no Postgres 8.4. Para a maneira como nossas enumerações são configuradas, adicionamos novos valores aos tipos de enumerações existentes através de:
INSERT INTO pg_enum (enumtypid, enumlabel)
SELECT typelem, 'NEWENUM' FROM pg_type WHERE
typname = '_ENUMNAME_WITH_LEADING_UNDERSCORE';
É um pouco assustador, mas faz sentido, dada a maneira como o Postgres realmente armazena seus dados.
A atualização de pg_enum funciona, assim como o truque da coluna intermediária destacado acima. Pode-se também usar USING magic para alterar diretamente o tipo da coluna:
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;
Contanto que você não tenha funções que exijam explicitamente ou retornem essa enumeração, você é bom. (O pgsql reclamará quando você soltar o tipo, se houver.)
Além disso, observe que o PG9.1 está introduzindo uma instrução ALTER TYPE, que funcionará em enumerações:
http://developer.postgresql.org/pgdocs/postgres/release-9-1-alpha.html
ALTER TABLE foo ALTER COLUMN bar TYPE test USING bar::text::new_type;
Mas em grande parte irrelevante agora ...
... USING bar::type
funcionou para mim. Eu nem precisei especificar ::text
.
Mais simples: livre-se das enumerações. Eles não são facilmente modificáveis e, portanto, devem muito raramente ser usados.
Não é possível adicionar um comentário ao local apropriado, mas ALTER TABLE foo ALTER COLUMN bar TYPE new_enum_type USING bar::text::new_enum_type
com um padrão na coluna falhou. Eu precisei:
ALTER table ALTER COLUMN bar DROP DEFAULT
;
e então funcionou.
Aqui está uma solução mais geral, mas bastante rápida, que além de alterar o tipo atualiza todas as colunas no banco de dados usando-o. O método pode ser aplicado mesmo que uma nova versão do ENUM seja diferente em mais de um rótulo ou perca alguns dos originais. O código abaixo substitui my_schema.my_type AS ENUM ('a', 'b', 'c')
por 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();
Todo o processo será executado rapidamente, porque se a ordem dos rótulos persistir, nenhuma mudança real de dados ocorrerá. Eu apliquei o método em 5 tabelas usando my_type
e tendo 50.000 a 70.000 linhas em cada uma, e todo o processo levou apenas 10 segundos.
Obviamente, a função retornará uma exceção no caso de rótulos ausentes na nova versão do ENUM serem usados em algum lugar nos dados, mas nessa situação algo deve ser feito de qualquer maneira com antecedência.
Para quem procura uma solução em transação, o seguinte parece funcionar.
Em vez de um ENUM
, a DOMAIN
deve ser usado no tipo TEXT
com uma restrição, verificando se o valor está dentro da lista especificada de valores permitidos (conforme sugerido por alguns comentários). O único problema é que nenhuma restrição pode ser adicionada (e, portanto, nem modificada) a um domínio se for usada por qualquer tipo composto (os documentos dizem apenas que "isso deve ser melhorado"). Essa restrição pode ser contornada, no entanto, usando uma restrição que chama uma função, da seguinte maneira.
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;
Anteriormente, usei uma solução semelhante à resposta aceita, mas está longe de ser boa quando são consideradas visualizações ou funções ou tipos compostos (e principalmente visualizações usando outras visualizações usando os ENUMs modificados ...). A solução proposta nesta resposta parece funcionar sob quaisquer condições.
A única desvantagem é que nenhuma verificação é executada nos dados existentes quando alguns valores permitidos são removidos (o que pode ser aceitável, especialmente para esta pergunta). ALTER DOMAIN test_domain VALIDATE CONSTRAINT val_check
Infelizmente, uma chamada termina com o mesmo erro que adicionar uma nova restrição ao domínio usado por um tipo composto.
Observe que uma leve modificação, como (funciona, na verdade - foi o meu erro)CHECK (value = ANY(get_allowed_values()))
onde a get_allowed_values()
função retornou a lista de valores permitidos, não funcionaria - o que é bastante estranho, então espero que a solução proposta acima funcione de maneira confiável (funciona para mim até agora ...).
Como discutido acima, o ALTER
comando não pode ser gravado dentro de uma transação. A maneira sugerida é inserir diretamente na tabela pg_enum, por retrieving the typelem from pg_type table
ecalculating the next enumsortorder number
;
A seguir está o código que eu uso. (Verifica se existe um valor duplicado antes da inserção (restrição entre o nome da enumtypid e a 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'
)
Observe que o nome do seu tipo é anexado com um sublinhado na tabela pg_type. Além disso, o nome do tipo deve estar em letras minúsculas na cláusula where.
Agora isso pode ser gravado com segurança no seu script db migrate.
Não sei se tenho outra opção, mas podemos diminuir o valor usando:
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;
Ao usar o Navicat, você pode acessar os tipos (em exibição -> outros -> tipos) - obter a visualização do design do tipo - e clicar no botão "adicionar rótulo".
ERROR: cannot drop type foo because other objects depend on it HINT: Use DROP ... CASCADE to drop the dependent objects too.