Zunächst sind Lücken in einer Sequenz zu erwarten. Fragen Sie sich, ob Sie sie wirklich entfernen müssen. Ihr Leben wird einfacher, wenn Sie nur damit leben. Um lückenlose Zahlen zu erhalten, ist die (oft bessere) Alternative die Verwendung eines VIEW
with row_number()
. Beispiel in dieser verwandten Antwort:
Hier sind einige Rezepte, um Lücken zu schließen.
1. Neuer, makelloser Tisch
Vermeidet Komplikationen mit einzigartigen Verstößen und Aufblähen und ist schnell . Nur für einfache Fälle, in denen Sie nicht an FK-Referenzen, Ansichten in der Tabelle oder andere abhängige Objekte oder an gleichzeitigen Zugriff gebunden sind. Machen Sie es in einer Transaktion, um Unfälle zu vermeiden:
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)
kopiert die Struktur inkl. Einschränkungen und Standardeinstellungen aus der ursprünglichen Tabelle. Machen Sie dann die neue Tabellenspalte zu einer eigenen Sequenz:
Und setzen Sie es auf das neue Maximum zurück:
Dies hat den Vorteil, dass die neue Tabelle frei von Blähungen und Clustern ist id
.
2. UPDATE
an Ort und Stelle
Dies erzeugt viele tote Zeilen und erfordert (automatisch) VACUUM
später.
Wenn die serial
Spalte auch die ist PRIMARY KEY
(wie in Ihrem Fall) oder eine UNIQUE
Einschränkung aufweist, müssen Sie dabei eindeutige Verstöße vermeiden . Die (billigere) Standardeinstellung für PK / UNIQUE-Einschränkungen lautet: Dies NOT DEFERRABLE
erzwingt eine Überprüfung nach jeder einzelnen Zeile. Alle Details unter dieser verwandten Frage zu SO:
Sie können Ihre Einschränkung als definieren DEFERRABLE
(was sie teurer macht).
Oder Sie können die Einschränkung löschen und wieder hinzufügen, wenn Sie fertig sind:
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;
Beides ist nicht möglich, wenn SieFOREIGN KEY
Einschränkungenhaben,die auf die Spalte (n) verweisen, weil ( gemäß Dokumentation ):
Die referenzierten Spalten müssen die Spalten einer nicht aufschiebbaren eindeutigen oder Primärschlüsseleinschränkung in der referenzierten Tabelle sein.
Sie müssten (alle beteiligten Tabellen sperren und) FK-Einschränkungen löschen / neu erstellen und alle FK-Werte manuell aktualisieren (siehe Option 3 ). Oder Sie müssen Werte innerhalb einer Sekunde aus dem Weg räumen UPDATE
, um Konflikte zu vermeiden. Angenommen, Sie haben keine negativen Zahlen:
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;
Nachteile wie oben erwähnt.
3. Temp Tabelle TRUNCATE
,INSERT
Eine weitere Option, wenn Sie viel RAM haben. Dies kombiniert einige der Vorteile der ersten beiden Möglichkeiten. Fast so schnell wie Option 1. und Sie eine unberührten bekommen, neue Tabelle ohne aufblasen , aber halten alle Zwänge und Abhängigkeiten in Ort wie in Option 2
jedoch , pro Dokumentation:
TRUNCATE
kann nicht für eine Tabelle verwendet werden, die Fremdschlüsselreferenzen
aus anderen Tabellen enthält, es sei denn, alle diese Tabellen werden im selben Befehl ebenfalls abgeschnitten. Die Überprüfung der Gültigkeit in solchen Fällen würde Tabellenscans erfordern, und der springende Punkt ist, keine durchzuführen.
Meine kühne Betonung.
Sie können FK-Einschränkungen vorübergehend löschen und datenmodifizierende CTEs verwenden, um alle FK-Spalten zu aktualisieren:
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;
Verwandte, mit mehr Details:
FOREIGN KEYS
sind,CASCADE
können Sie nicht einfach die alten Primärschlüssel durchlaufen und ihre Werte direkt aktualisieren (vom alten auf den neuen Wert)? Im Wesentlichen ist dies Option 3 ohneTRUNCATE tbl
, durchINSERT
ein zu ersetzenUPDATE
und ohne dass Fremdschlüssel manuell aktualisiert werden müssen.