Menambahkan 'serial' ke kolom yang ada di Postgres

98

Saya memiliki tabel kecil (~ 30 baris) di database Postgres 9.0 saya dengan bidang ID integer (kunci utama) yang saat ini berisi bilangan bulat berurutan unik mulai dari 1, tetapi tidak dibuat menggunakan kata kunci 'serial'.

Bagaimana cara mengubah tabel ini sehingga mulai sekarang, penyisipan ke tabel ini akan menyebabkan bidang ini berperilaku seolah-olah telah dibuat dengan 'serial' sebagai jenis?

nicolaskruchten.dll
sumber
5
FYI, SERIALpseudo-type sekarang sudah lama , digantikan oleh GENERATED … AS IDENTITYfitur baru yang didefinisikan di SQL: 2003 , di Postgres 10 dan yang lebih baru. Lihat penjelasannya .
Basil Bourque
Untuk versi Postgres modern (> = 10) lihat pertanyaan ini: stackoverflow.com/questions/2944499
a_horse_with_no_name

Jawaban:

135

Lihat perintah berikut (terutama blok yang diberi komentar).

DROP TABLE foo;
DROP TABLE bar;

CREATE TABLE foo (a int, b text);
CREATE TABLE bar (a serial, b text);

INSERT INTO foo (a, b) SELECT i, 'foo ' || i::text FROM generate_series(1, 5) i;
INSERT INTO bar (b) SELECT 'bar ' || i::text FROM generate_series(1, 5) i;

-- blocks of commands to turn foo into bar
CREATE SEQUENCE foo_a_seq;
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq');
ALTER TABLE foo ALTER COLUMN a SET NOT NULL;
ALTER SEQUENCE foo_a_seq OWNED BY foo.a;    -- 8.2 or later

SELECT MAX(a) FROM foo;
SELECT setval('foo_a_seq', 5);  -- replace 5 by SELECT MAX result

INSERT INTO foo (b) VALUES('teste');
INSERT INTO bar (b) VALUES('teste');

SELECT * FROM foo;
SELECT * FROM bar;
Euler Taveira
sumber
Karena Anda menyebutkan kunci utama di OP Anda, Anda mungkin juga menginginkannya ALTER TABLE foo ADD PRIMARY KEY (a).
Skippy le Grand Gourou
SERIAL adalah gula sintaksis dan tidak disimpan dalam metadata DB, jadi kode di atas akan setara 100%.
DKroot
Jika ada kemungkinan tabel target dibuat oleh pengguna lain, Anda harus melakukannya ALTER TABLE foo OWNER TO current_user;terlebih dahulu.
DKroot
2
Bukankah Anda seharusnya menyetel MAX(a)+1di setval? SELECT MAX(a)+1 FROM foo; SELECT setval('foo_a_seq', 6);
SunnyPro
50

Anda juga dapat menggunakan START WITHuntuk memulai urutan dari titik tertentu, meskipun setval menyelesaikan hal yang sama, seperti dalam jawaban Euler, misalnya,

SELECT MAX(a) + 1 FROM foo;
CREATE SEQUENCE foo_a_seq START WITH 12345; -- replace 12345 with max above
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq');
John Powell
sumber
31

TL; DR

Ini adalah versi di mana Anda tidak memerlukan manusia untuk membaca nilai dan mengetiknya sendiri.

CREATE SEQUENCE foo_a_seq OWNED BY foo.a;
SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo;
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq'); 

Pilihan lain adalah menggunakan yang dapat digunakan kembali Functionbersama di akhir jawaban ini.


Solusi non-interaktif

Hanya menambahkan ke dua jawaban lainnya, bagi kita yang membutuhkan ini Sequencedibuat oleh skrip non-interaktif , sambil menambal DB live-ish misalnya.

Artinya, saat Anda tidak menginginkan SELECTnilainya secara manual dan mengetiknya sendiri ke dalam CREATEpernyataan berikutnya .

Singkatnya, Anda tidak dapat melakukan:

CREATE SEQUENCE foo_a_seq
    START WITH ( SELECT max(a) + 1 FROM foo );

... karena START [WITH]klausa dalam CREATE SEQUENCEmengharapkan nilai , bukan subkueri.

Catatan: Sebagai aturan praktis, yang berlaku untuk semua non-CRUD ( yaitu : apa pun selain INSERT, SELECT, UPDATE, DELETE) pernyataan dalam pgsql AFAIK.

Namun, setval()lakukan! Jadi, berikut ini baik-baik saja:

SELECT setval('foo_a_seq', max(a)) FROM foo;

Jika tidak ada data dan Anda tidak (ingin) mengetahuinya, gunakan coalesce()untuk menyetel nilai default:

SELECT setval('foo_a_seq', coalesce(max(a), 0)) FROM foo;
--                         ^      ^         ^
--                       defaults to:       0

Namun, menyetel nilai urutan saat ini ke 0adalah kikuk, jika tidak ilegal.
Menggunakan bentuk tiga parameter dari setvalakan lebih tepat:

--                                             vvv
SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo;
--                                                  ^   ^
--                                                is_called

Menyetel parameter opsional ketiga dari setvalmenjadi falseakan mencegah yang berikutnya nextvalmemajukan urutan sebelum mengembalikan nilai, dan dengan demikian:

next nextvalakan mengembalikan nilai yang ditentukan secara tepat, dan peningkatan urutan dimulai dengan yang berikut ini nextval.

- dari entri ini di dokumentasi

Pada catatan yang tidak terkait, Anda juga dapat menentukan kolom yang memiliki Sequencelangsung dengan CREATE, Anda tidak perlu mengubahnya nanti:

CREATE SEQUENCE foo_a_seq OWNED BY foo.a;

Singkatnya:

CREATE SEQUENCE foo_a_seq OWNED BY foo.a;
SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo;
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq'); 

Menggunakan sebuah Function

Alternatifnya, jika Anda berencana melakukan ini untuk beberapa kolom, Anda dapat memilih untuk menggunakan aktual Function.

CREATE OR REPLACE FUNCTION make_into_serial(table_name TEXT, column_name TEXT) RETURNS INTEGER AS $$
DECLARE
    start_with INTEGER;
    sequence_name TEXT;
BEGIN
    sequence_name := table_name || '_' || column_name || '_seq';
    EXECUTE 'SELECT coalesce(max(' || column_name || '), 0) + 1 FROM ' || table_name
            INTO start_with;
    EXECUTE 'CREATE SEQUENCE ' || sequence_name ||
            ' START WITH ' || start_with ||
            ' OWNED BY ' || table_name || '.' || column_name;
    EXECUTE 'ALTER TABLE ' || table_name || ' ALTER COLUMN ' || column_name ||
            ' SET DEFAULT nextVal(''' || sequence_name || ''')';
    RETURN start_with;
END;
$$ LANGUAGE plpgsql VOLATILE;

Gunakan seperti ini:

INSERT INTO foo (data) VALUES ('asdf');
-- ERROR: null value in column "a" violates not-null constraint

SELECT make_into_serial('foo', 'a');
INSERT INTO foo (data) VALUES ('asdf');
-- OK: 1 row(s) affected
ccjmne.dll
sumber
Jawaban yang bagus, tetapi perlu diingat coalesce(max(a), 0))tidak akan berfungsi sebagian besar waktu, karena Id biasanya mulai dari 1. Cara yang lebih tepat adalahcoalesce(max(a), 1))
Amiko
1
Terima kasih @Amiko atas komentarnya! The setvalFungsi sebenarnya hanya menetapkan saat "Nilai bekas terbaru" untuk urutan. Nilai berikutnya yang tersedia (yang pertama benar-benar digunakan) adalah satu lagi! Menggunakan setval(..., coalesce(max(a), 1))pada kolom kosong akan menyiapkannya untuk "dimulai" dengan 2(nilai berikutnya yang tersedia), seperti yang diilustrasikan dalam dokumentasi .
ccjmne
1
@Amiko Anda benar dengan mengatakan ada masalah dalam kode saya, meskipun: currvalseharusnya tidak pernah 0, bahkan jika itu tidak akan tercermin dalam dataset yang sebenarnya. Menggunakan bentuk tiga parameter setvalakan lebih tepat: setval(..., coalesce(max(a), 0) + 1, false). Jawaban diperbarui sesuai!
ccjmne
1
Setuju, saya sangat merindukan itu. Terima kasih atas jawabannya menghemat waktu saya.
Amiko