Pertama, celah dalam suatu urutan harus diharapkan. Tanyakan pada diri Anda apakah Anda benar-benar perlu menghapusnya. Hidup Anda menjadi lebih sederhana jika Anda hanya hidup dengannya. Untuk mendapatkan angka tanpa celah, alternatif (seringkali lebih baik) adalah menggunakan VIEW
dengan row_number()
. Contoh dalam jawaban terkait ini:
Berikut adalah beberapa resep untuk menghilangkan celah.
1. Baru, meja murni
Hindari komplikasi dengan pelanggaran unik dan mengasapi meja dan cepat . Hanya untuk kasus sederhana di mana Anda tidak terikat oleh referensi FK, tampilan di atas meja atau objek bergantung lainnya, atau dengan akses bersamaan. Lakukan dalam satu transaksi untuk menghindari kecelakaan:
BEGIN;
LOCK tbl;
CREATE TABLE tbl_new (LIKE tbl INCLUDING ALL);
INSERT INTO tbl_new -- no target list in this case
SELECT row_number() OVER (ORDER BY id), data -- all columns in default order
FROM tbl;
ALTER SEQUENCE tbl_id_seq OWNED BY tbl_new.id; -- make new table own sequence
DROP TABLE tbl;
ALTER TABLE tbl_new RENAME TO tbl;
SELECT setval('tbl_id_seq', max(id)) FROM tbl; -- reset sequence
COMMIT;
CREATE TABLE tbl_new (LIKE tbl INCLUDING ALL)
menyalin struktur termasuk kendala dan default dari tabel asli. Kemudian buat kolom tabel baru memiliki urutan:
Dan reset ke maksimum baru:
Ini membawa keuntungan bahwa meja baru ini bebas dari bloat dan bergerombol id
.
2. UPDATE
di tempat
Ini menghasilkan banyak baris mati dan membutuhkan (otomatis) VACUUM
nanti.
Jika serial
kolom juga merupakan PRIMARY KEY
(seperti dalam kasus Anda) atau memiliki UNIQUE
kendala, Anda harus menghindari pelanggaran unik dalam proses. Default (lebih murah) untuk batasan PK / UNIQUE adalah NOT DEFERRABLE
, yang memaksa cek setelah setiap baris tunggal. Semua detail di bawah pertanyaan terkait ini di SO:
Anda dapat mendefinisikan batasan Anda sebagai DEFERRABLE
(yang membuatnya lebih mahal).
Atau Anda dapat menghilangkan batasan dan menambahkannya kembali setelah Anda selesai:
BEGIN;
LOCK tbl;
ALTER TABLE tbl DROP CONSTRAINT tbl_pkey; -- remove PK
UPDATE tbl t -- intermediate unique violations are ignored now
SET id = t1.new_id
FROM (SELECT id, row_number() OVER (ORDER BY id) AS new_id FROM tbl) t1
WHERE t.id = t1.id;
SELECT setval('tbl_id_seq', max(id)) FROM tbl; -- reset sequence
ALTER TABLE tbl ADD CONSTRAINT tbl_pkey PRIMARY KEY(id); -- add PK back
COMMIT;
Tidak ada yang mungkin saat Anda memilikiFOREIGN KEY
kendala referensi kolom karena ( per dokumentasi ):
Kolom yang direferensikan harus berupa kolom dari batasan kunci primer atau unik yang tidak dapat ditangguhkan dalam tabel yang direferensikan.
Anda perlu (mengunci semua tabel yang terlibat dan) menjatuhkan / membuat ulang batasan FK dan memperbarui semua nilai FK secara manual (lihat opsi 3. ). Atau Anda harus memindahkan nilai dari satu detik dengan yang lain UPDATE
untuk menghindari konflik. Misalnya, dengan asumsi Anda tidak memiliki angka negatif:
BEGIN;
LOCK tbl;
UPDATE tbl SET id = id * -1; -- avoid conflicts
UPDATE tbl t
SET id = t1.new_id
FROM (SELECT id, row_number() OVER (ORDER BY id DESC) AS new_id FROM tbl) t1
WHERE t.id = t1.id;
SELECT setval('tbl_id_seq', max(id)) FROM tbl; -- reset sequence
COMMIT;
Kekurangannya seperti disebutkan di atas.
3. Temp table TRUNCATE
,,INSERT
Satu lagi opsi jika Anda memiliki banyak RAM. Ini menggabungkan beberapa keunggulan dari dua cara pertama. Hampir secepat opsi 1. dan Anda mendapatkan tabel baru yang murni tanpa mengasapi tetapi tetap menggunakan semua kendala dan dependensi seperti pada opsi 2.
Namun , per dokumentasi:
TRUNCATE
tidak dapat digunakan pada tabel yang memiliki referensi kunci asing
dari tabel lain, kecuali semua tabel tersebut juga terpotong dalam perintah yang sama. Memeriksa validitas dalam kasus-kasus seperti itu akan membutuhkan pemindaian tabel, dan intinya bukan untuk melakukan satu.
Penekanan berani saya.
Anda bisa menghilangkan batasan FK untuk sementara dan menggunakan CTE pemodifikasi data untuk memperbarui semua kolom FK:
SET temp_buffers = 500MB; -- example value, see 1st link below
BEGIN;
CREATE TEMP TABLE tbl_tmp AS
SELECT row_number() OVER (ORDER BY id) AS new_id, *
FROM tbl
ORDER BY id; -- order here to use index (if one exists)
-- drop FK constraints in other tables referencing this one
-- which takes out an exclusive lock on those tables
TRUNCATE tbl;
INSERT INTO tbl
SELECT new_id, data -- list all columns in order
FROM tbl_tmp; -- rely on established order in tbl_tmp
-- ORDER BY id; -- only to be absolutely sure (not necessary)
-- example for table "fk_tbl" with FK column "fk_id"
UPDATE fk_tbl f
SET fk_id = t.new_id -- set to new ID
FROM tbl_tmp t
WHERE f.fk_id = t.id; -- match on old ID
-- add FK constraints in other tables back
COMMIT;
Terkait, dengan rincian lebih lanjut:
FOREIGN KEYS
diatur keCASCADE
tidak bisa Anda hanya mengulang kunci utama lama dan memperbarui nilainya di tempat (dari nilai lama ke yang baru)? Pada dasarnya ini adalah opsi 3 tanpaTRUNCATE tbl
, menggantiINSERT
denganUPDATE
, dan tanpa perlu memperbarui kunci asing secara manual.