Prima di tutto, sono attesi vuoti in una sequenza. Chiediti se hai davvero bisogno di rimuoverli. La tua vita diventa più semplice se vivi con essa. Per ottenere numeri senza gap, l'alternativa (spesso migliore) è usare un VIEW
con row_number()
. Esempio in questa risposta correlata:
Ecco alcune ricette per rimuovere le lacune.
1. Tabella nuova e incontaminata
Evita le complicazioni con violazioni uniche e gonfiore del tavolo ed è veloce . Solo per casi semplici in cui non sei vincolato da riferimenti FK, viste sulla tabella o altri oggetti dipendenti o da accesso simultaneo. Fallo in una transazione per evitare incidenti:
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)
copia la struttura incl. vincoli e valori predefiniti dalla tabella originale. Quindi rendere la nuova colonna della tabella propria della sequenza:
E ripristinalo al nuovo massimo:
Questo porta il vantaggio che la nuova tabella è esente da gonfiori e raggruppata id
.
2. UPDATE
in atto
Questo produce molte file morte e richiede (auto-) in VACUUM
seguito.
Se la serial
colonna è anche PRIMARY KEY
(come nel tuo caso) o presenta un UNIQUE
vincolo, devi evitare violazioni uniche nel processo. L'impostazione predefinita (più economica) per i vincoli PK / UNIQUE è NOT DEFERRABLE
, che forza un controllo dopo ogni singola riga. Tutti i dettagli in questa domanda correlata su SO:
È possibile definire il vincolo come DEFERRABLE
(che lo rende più costoso).
Oppure puoi eliminare il vincolo e aggiungerlo nuovamente al termine:
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;
Nessuno dei due è possibile mentre si hannoFOREIGN KEY
vincoli che fanno riferimento alle colonne perché ( per documentazione ):
Le colonne di riferimento devono essere le colonne di un vincolo di chiave unica o primaria non differibile nella tabella di riferimento.
Dovresti (bloccare tutte le tabelle coinvolte e) eliminare / ricreare i vincoli FK e aggiornare tutti i valori FK manualmente (vedi opzione 3. ). Oppure devi spostare i valori di mezzo con un secondo UPDATE
per evitare conflitti. Ad esempio, supponendo che non ci siano numeri negativi:
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;
Svantaggi come menzionato sopra.
3. tabella temporanea TRUNCATE
,INSERT
Un'altra opzione se hai molta RAM. Questo combina alcuni dei vantaggi dei primi due modi. Quasi veloce come l'opzione 1. e ottieni una nuova tabella incontaminata senza gonfiare, ma mantieni tutti i vincoli e le dipendenze in atto come nell'opzione 2.
Tuttavia , per documentazione:
TRUNCATE
non può essere utilizzato su una tabella che ha riferimenti a chiave esterna
da altre tabelle, a meno che anche tutte queste tabelle vengano troncate nello stesso comando. Il controllo della validità in questi casi richiederebbe scansioni di tabelle e l'intero punto non è quello di eseguirne una.
Enorme enfasi sulla mia.
È possibile eliminare temporaneamente i vincoli FK e utilizzare CTE che modificano i dati per aggiornare tutte le colonne 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;
Correlato, con maggiori dettagli:
FOREIGN KEYS
sono impostati suCASCADE
non potresti semplicemente passare in rassegna le vecchie chiavi primarie e aggiornare i loro valori sul posto (dal vecchio valore a quello nuovo)? In sostanza, questa è l'opzione 3 senzaTRUNCATE tbl
, sostituendoINSERT
con unaUPDATE
e senza la necessità di aggiornare manualmente le chiavi esterne.