Mevcut bir ENUM Tipine yeni bir değer ekleme


208

Bir enumtür kullanan bir tablo sütunu var . Bu enumtürü, olası bir ek değere sahip olacak şekilde güncellemek istiyorum . Mevcut değerleri silmek istemiyorum, sadece yeni değeri ekleyin. Bunu yapmanın en basit yolu nedir?

Yanıtlar:


153

NOT PostgreSQL 9.1 veya üstünü kullanıyorsanız ve bir işlem dışında değişiklik yapma konusunda sorun yaşıyorsanız, daha basit bir yaklaşım için bu cevaba bakınız .


Aynı sorunu birkaç gün önce yaşadım ve bu gönderiyi buldum. Bu yüzden cevabım çözüm arayan biri için yararlı olabilir :)

Değiştirmek istediğiniz numaralandırma türünü kullanan yalnızca bir veya iki sütununuz varsa, bunu deneyebilirsiniz. Ayrıca, yeni türdeki değerlerin sırasını değiştirebilirsiniz.

-- 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;

1'den fazla sütun varsa 3-6 tekrarlanmalıdır.


9
Tüm bunların tek bir işlemde yapılabileceğini belirtmek gerekir, bu yüzden bunu bir üretim veritabanında yapmak genellikle güvenlidir.
David Leppik

52
Bu asla iyi bir fikir değildi. 9.1'den beri hepsini yapabilirsiniz ALTER TYPE. Ama ondan önce bile ALTER TABLE foo ALTER COLUMN bar TYPE new_type USING bar::text::new_type;çok daha üstündü.
Erwin Brandstetter

1
Postgres'in eski sürümlerinin yeniden adlandırma türlerini desteklemediğini unutmayın. Özellikle Heroku'daki Postgres sürümü (paylaşılan db, PG 8.3 kullandıklarına inanıyorum) bunu desteklemiyor.
Ortwin Gentz

13
3, 4, 5 ve 6. adımları tek bir ifadede ALTER TABLE some_table ALTER COLUMN some_column TYPE some_enum_type USING some_column::text::some_enum_type;
daraltabilirsiniz

3
Bunu canlı bir masada yapıyorsanız, işlem sırasında tabloyu kilitleyin. Postgresql'deki varsayılan işlem yalıtım düzeyi, bu işlem sırasında diğer işlemlerin yeni satırların eklenmesini engellemeyeceğinden, yanlış doldurulmuş satırlar bırakabilirsiniz.
Sérgio Carvalho

422

PostgreSQL 9.1 , ALTER Enum türlerine yetenek kazandırır :

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';

1
"enum_type" nedir? alan adı, table_field adı? veya başka bir şey? nasıl vurmalıyım? Tablo "notlar" var ve ben sütun "türü" var Ve db dökümü i bu olsun: CONSTRAINT grades_type_check CHECK (((type) :: text = ANY ((ARRAY ['sınav' :: karakter değişir, 'test'): : karakter değişiyor, 'ekstra' :: karakter değişiyor, 'ara sınav' :: karakter değişiyor, 'final' :: karakter değişiyor]) :: metin [])))

1
enum_type sadece kendi enum türü adınız @mariotanenbaum. Eğer numaralandırmanız bir "tip" ise o zaman kullanmanız gereken budur.
Dariusz

26
birini kaldırmak mümkün mü?
Ced

8
@DrewNoakes 'yorumuna ekleyerek, eğer db-migrate (işlemde çalışıyorsa) kullanıyorsanız, bir hata alabilirsiniz: HATA: ALTER TYPE ... ADD bir işlem bloğu içinde çalışamıyor Çözüm burada belirtilmiştir (Hubbitus tarafından ): stackoverflow.com/a/41696273/1161370
Mahesh

1
onu kaldıramazsınız, böylece dow göçünü imkansız hale getirir, bu yüzden diğer yöntemlere başvurmak zorundasınız
Muhammed Umer

65

Olası bir çözüm şudur; önkoşul, kullanılan enum değerlerinde çakışma olmamasıdır. (örneğin, bir enum değerini kaldırırken, bu değerin artık kullanılmadığından emin olun.)

-- 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__;

Ayrıca bu şekilde sütun sırası değiştirilmez.


1
+1, 9.1 öncesi sürümün ve yine de öğeleri silmenin veya değiştirmenin yoludur.

Bu, mevcut tüm numaralandırma türüne yeni numaralar ekleyen ve tüm eski sıralamaları tuttuğumuz ve yenilerini eklediğimiz çözümüm için açık ara en iyi cevap. Ayrıca güncelleme komut dosyamız işlemseldir. Harika gönderi!
Darin Peterson

1
Mükemmel cevap! pg_enumAslında şeyleri kırabilecek ve işlemden farklı olarak gelen saldırıları önler ALTER TYPE ... ADD.
NathanAldenSr

4
Durumda sütun aşağıdaki hatayı alırsınız varsayılan bir değeri vardır: default for column "my_column" cannot be cast automatically to type "my_enum". Aşağıdakileri yapmanız gerekecek: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';
n1ru4l

30

enumİşlemde değerler eklemeniz gerektiğinde duruma düşerseniz , ALTER TYPEhata alacağınız beyanında flyway geçişinde yürütün ERROR: ALTER TYPE ... ADD cannot run inside a transaction block(bkz. Flyway sorunu # 350 ), bu değerleri pg_enumdoğrudan geçici çözüm olarak ekleyebilirsiniz type_egais_units(hedefin adıdır 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 )

9
Ancak bu, sistem tablosunu değiştirdiği için yönetici izinlerinin verilmesini gerektirir.
asnelzin

22

Tamamlayıcı @Dariusz 1

Rails 4.2.1 için bu belge bölümü var:

== İşlemsel Göçler

Veritabanı bağdaştırıcısı DDL işlemlerini destekliyorsa, tüm geçişler otomatik olarak bir işleme sarılır. Bir işlemin içinde yürütemeyeceğiniz sorgular vardır ve bu durumlar için otomatik işlemleri kapatabilirsiniz.

class ChangeEnum < ActiveRecord::Migration
  disable_ddl_transaction!

  def up
    execute "ALTER TYPE model_size ADD VALUE 'new_value'"
  end
end

3
bu! Modern raylarda numaralandırmalarla oynuyorsanız, tam olarak aradığınız şey budur.
Eli Albert

1
Harika, bana çok yardımcı oldu!
Dmytro Uhnichenko

10

Postgres 9.1 Belgelerinden :

ALTER TYPE name ADD VALUE new_enum_value [ { BEFORE | AFTER } existing_enum_value ]

Misal:

ALTER TYPE user_status ADD VALUE 'PROVISIONAL' AFTER 'NORMAL'

3
Ayrıca dokümantasyondan: Eklenen bir enum değeri içeren karşılaştırmalar bazen yalnızca enum türünün orijinal üyelerini içeren karşılaştırmalardan daha yavaş olacaktır. [.... yığın akışı yorumu için çok uzun ayrıntılı kesildi ...] Yavaşlama genellikle önemsizdir; ancak önemliyse, enum türünü bırakarak ve yeniden oluşturarak veya veritabanını boşaltıp yeniden yükleyerek en iyi performans elde edilebilir.
Aaron Zinman

8

Yasal Uyarı: Bu çözümü denemedim, bu yüzden işe yaramayabilir ;-)

Bakmalısın pg_enum . Yalnızca mevcut bir ENUM etiketini değiştirmek istiyorsanız, basit bir GÜNCELLEME bunu yapacaktır.

Yeni bir ENUM değerleri eklemek için:

  • Önce yeni değeri pg_enum . Yeni değerin son olması gerekiyorsa, işleminiz tamamlanmıştır.
  • Değilse (mevcut olanlar arasında yeni bir ENUM değerine ihtiyacınız varsa), tablonuzdaki her bir ayrı değeri en üstten en alta doğru güncellemeniz gerekir ...
  • O zaman onları pg_enumters sırayla yeniden adlandırmanız gerekir .

İllüstrasyon
Aşağıdaki etiket grubuna sahipsiniz:

ENUM ('enum1', 'enum2', 'enum3')

ve şunu elde etmek istersiniz:

ENUM ('enum1', 'enum1b', 'enum2', 'enum3')

sonra:

INSERT INTO pg_enum (OID, 'newenum3');
UPDATE TABLE SET enumvalue TO 'newenum3' WHERE enumvalue='enum3';
UPDATE TABLE SET enumvalue TO 'enum3' WHERE enumvalue='enum2';

sonra:

UPDATE TABLE pg_enum SET name='enum1b' WHERE name='enum2' AND enumtypid=OID;

Ve bunun gibi...


1
"Genel olarak katalogları manuel olarak yönetmekten kaçınmalısınız ... bu şekilde sistem tablolarını hacklemek." Andrew Dunstan ve ben onun haklı olduğunu düşünmeye meyilli olduğumu söylüyor.

5

Yorum gönderemiyorum, bu yüzden sadece pg_enum güncellemesinin Postgres 8.4'te çalıştığını söyleyeceğim. Numaralandırmalarımızın kurulum şekli için mevcut numaralandırma türlerine şu yollarla yeni değerler ekledim:

INSERT INTO pg_enum (enumtypid, enumlabel)
  SELECT typelem, 'NEWENUM' FROM pg_type WHERE
    typname = '_ENUMNAME_WITH_LEADING_UNDERSCORE';

Biraz korkutucu, ancak Postgres'in gerçekte verilerini saklama şekli göz önüne alındığında mantıklı.


1
Mükemmel cevap! Sadece yeni bir numaraya eklenmesine yardımcı olur, ancak yeniden sipariş vermeniz gereken durumu çözmez.
Mahmoud Abdelkader

3
"Genel olarak katalogları manuel olarak yönetmekten kaçınmalısınız ... bu şekilde sistem tablolarını hacklemek." Andrew Dunstan ve ben onun haklı olduğunu düşünmeye meyilli olduğumu söylüyor.

Typename için önde gelen alt çizgi ile birlikte, bunlar büyük / küçük harfe duyarlıdır. Neredeyse pg_type tablosundan typename seçmeye çalışırken fikrimi kaybettim.
Mahesh

5

Pg_enum'un güncellenmesi, yukarıda vurgulanan ara sütun hilesi gibi çalışır. Sütun türünü doğrudan değiştirmek için USING magic'i de kullanabilirsiniz:

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;

Bu numaralandırmayı açıkça gerektiren veya döndüren işlevleriniz olmadığı sürece, iyisiniz. (varsa türü bıraktığınızda pgsql şikayet edecektir.)

Ayrıca, PG9.1'in numaralandırmalarda çalışacak bir ALTER TYPE deyimi sunduğunu unutmayın:

http://developer.postgresql.org/pgdocs/postgres/release-9-1-alpha.html


PostgreSQL 9.1 için ilgili belgeleri artık postgresql.org/docs/9.1/static/sql-altertype.html
Wichert Akkerman

1
ALTER TABLE foo ALTER COLUMN bar TYPE test USING bar::text::new_type;Ama şimdi büyük ölçüde alakasız ...
Erwin Brandstetter

Erwin'in söylediklerine benzer şekilde ... USING bar::typebenim için çalıştı. Belirtmek zorunda bile değildim ::text.
Daniel Werner

3

En basit: numaralandırmalardan kurtulun. Kolayca değiştirilemezler ve bu nedenle çok nadiren kullanılmalıdırlar.


2
belki basit bir kontrol kısıtlaması yapar?

1
Ve değerleri dize olarak saklama problemi tam olarak nedir?

5
@Grazer: 9.1'de numaralamaya değerler ekleyebilirsiniz ( depesz.com/index.php/2010/10/27/… ) - ancak eskilerini kaldıramazsınız.

3
@WillSheppard - Ben bu temelde asla düşünüyorum. Bence kontrol kısıtlamaları olan metne dayalı özel tipler her durumda çok daha iyi.

3
@JackDouglas - tabi. Ben her gün enum üzerinde kontrol ile alan adı almak istiyorum.

3

Uygun yere yorum eklenemiyor, ancak ALTER TABLE foo ALTER COLUMN bar TYPE new_enum_type USING bar::text::new_enum_typesütunda varsayılan olarak başarısız oldu. Yapmak zorundaydım:

ALTER table ALTER COLUMN bar DROP DEFAULT;

ve sonra işe yaradı.


3

Rails, Rails kullanıyorsanız ve birkaç ifadeniz varsa, tek tek yürütmeniz gerekir, örneğin:

execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'YYY';"
execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'ZZZ';"

1

Burada, türün kendisini değiştirmenin dışında, veritabanındaki tüm sütunları kullanarak güncelleştiren daha genel ama oldukça hızlı çalışan bir çözüm var. ENUM'un yeni bir sürümü birden fazla etiketten farklı olsa veya orijinal etiketlerden bazılarını özlemese bile yöntem uygulanabilir. Cümledeki aşağıdaki kod my_schema.my_type AS ENUM ('a', 'b', 'c')ile 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();

Tüm süreç oldukça hızlı bir şekilde çalışacaktır, çünkü etiketlerin sırası devam ederse, gerçek bir veri değişikliği olmayacaktır. Yöntemi, my_typeher birinde 50.000-70.000 satır kullanarak ve sahip 5 tabloya uyguladım ve tüm süreç sadece 10 saniye sürdü.

Tabii ki, ENUM'un yeni sürümünde eksik olan etiketlerin verilerinde bir yerde kullanılması durumunda işlev bir istisna döndürür, ancak böyle bir durumda önceden bir şey yapılması gerekir.


Bu gerçekten değerlidir. Sorun eski ENUM kullanarak görüşlere rağmen. Bırakılmalı ve yeniden oluşturulmalıdır, bu da bırakılanlara bağlı olarak diğer görüşler göz önüne alındığında çok daha karmaşıktır. Kompozit türleri hakkında konuşmuyorum ...
Ondřej Bouda

1

Bir işlem içi çözüm arayanlar için aşağıdakiler işe yarıyor gibi görünüyor.

An yerine ENUMa DOMAIN, TEXTdeğerin belirtilen izin verilen değerler listesinde (bazı yorumlarda belirtildiği gibi) olup olmadığını kontrol eden bir kısıtlama ile tipte kullanılır . Tek sorun, herhangi bir kompozit tür tarafından kullanılıyorsa bir alana hiçbir kısıtlamanın eklenemeyeceği (ve dolayısıyla değiştirilemeyeceğidir) (dokümanlar yalnızca bunun "sonunda iyileştirilmesi gerektiğini" söylüyor). Bununla birlikte, böyle bir kısıtlama, aşağıdaki gibi bir işlevi çağıran bir kısıtlama kullanılarak çözülebilir.

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;

Önceden, kabul edilen cevaba benzer bir çözüm kullandım, ancak görünümler veya işlevler veya kompozit türler (ve özellikle değiştirilmiş ENUM'ları kullanan diğer görünümleri kullanan görünümler ...) düşünüldüğünde iyi olmaktan uzaktır. Bu cevapta önerilen çözüm her koşulda işe yarıyor gibi görünüyor.

Tek dezavantajı, izin verilen bazı değerler kaldırıldığında mevcut veriler üzerinde hiçbir kontrol yapılmamasıdır (bu özellikle bu soru için kabul edilebilir). ( ALTER DOMAIN test_domain VALIDATE CONSTRAINT val_checkMaalesef, bileşik tür tarafından kullanılan alan adına yeni bir kısıtlama eklemekle aynı hatayla sonuçlanır.)

Not gibi hafif bir modifikasyon yani CHECK (value = ANY(get_allowed_values())), get_allowed_values()fonksiyon izin değerler listesini döndü olmaz iş - Ben çözeltisi (şimdiye kadar, benim için yapar ...) güvenilir eserler yukarıda önerilen umut oldukça garip, yani. (aslında çalışıyor - benim hatamdı)


0

Yukarıda tartışıldığı gibi, ALTERkomut bir işlemin içine yazılamaz. Önerilen yolu, doğrudan pg_enum tabloya eklemek tarafından etmektir retrieving the typelem from pg_type tableve calculating the next enumsortorder number;

Kullandığım kod aşağıdadır. (Eklemeden önce yinelenen değerin olup olmadığını kontrol eder (enumtypid ile enumlabel adı arasındaki kısıtlama)

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'
    )

Tür adınızın pg_type tablosunda bir alt çizgi ile eklendiğini unutmayın. Ayrıca, typname öğesinin where yan tümcesinde küçük harf olması gerekir.

Şimdi bu güvenli bir şekilde db migrate script içine yazılabilir.


-1

Başka bir seçeneğiniz olup olmadığını bilmiyorum ama kullanarak değeri düşürebiliriz:

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;

-2

Navicat kullanırken türlere gidebilirsiniz (görünüm altında -> diğerleri -> türler) - türün tasarım görünümünü alabilirsiniz - ve "etiket ekle" düğmesini tıklayabilirsiniz.


1
Güzel olurdu ama gerçek hayatta, yararlı değil:ERROR: cannot drop type foo because other objects depend on it HINT: Use DROP ... CASCADE to drop the dependent objects too.
Ortwin Gentz

Tuhaf, benim için çalıştı. (TS yalnızca enum alanına bir değer eklemek istediğinde
DROP'u

1
Özellikle bir DROP yapmadım ama tam olarak prosedürünüzden sonra gittim. Navicat'ın DROP'u sahne arkasında yaptığını ve başarısız olduğunu varsayıyorum. Navicat 9.1.5 Lite kullanıyorum.
Ortwin Gentz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.