Tout d'abord, des lacunes dans une séquence sont à prévoir. Demandez-vous si vous devez vraiment les supprimer. Votre vie devient plus simple si vous vivez avec. Pour obtenir des nombres sans écart, l'alternative (souvent meilleure) est d'utiliser un VIEW
avec row_number()
. Exemple dans cette réponse connexe:
Voici quelques recettes pour éliminer les lacunes.
1. Nouvelle table vierge
Évite les complications avec violations uniques et ballonnement de table et est rapide . Uniquement pour les cas simples où vous n'êtes pas lié par des références FK, des vues sur la table ou d'autres objets dépendants, ou par un accès simultané. Faites-le en une seule transaction pour éviter les accidents:
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)
copie la structure incl. contraintes et valeurs par défaut de la table d'origine. Faites ensuite en sorte que la nouvelle colonne du tableau soit propriétaire de la séquence:
Et réinitialisez-le au nouveau maximum:
Cela présente l'avantage que la nouvelle table est sans ballonnement et en cluster id
.
2. UPDATE
en place
Cela produit beaucoup de lignes mortes et nécessite (auto-) VACUUM
plus tard.
Si la serial
colonne est également la PRIMARY KEY
(comme dans votre cas) ou a une UNIQUE
contrainte, vous devez éviter les violations uniques dans le processus. La valeur par défaut (la moins chère) pour les contraintes PK / UNIQUE doit être NOT DEFERRABLE
, ce qui force une vérification après chaque ligne unique. Tous les détails sous cette question connexe sur SO:
Vous pouvez définir votre contrainte comme DEFERRABLE
(ce qui la rend plus chère).
Ou vous pouvez supprimer la contrainte et l'ajouter à nouveau lorsque vous avez terminé:
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;
Cela n'est pas possible tant que vous avez desFOREIGN KEY
contraintes référençant la ou les colonnes car ( selon la documentation ):
Les colonnes référencées doivent être les colonnes d'une contrainte de clé unique ou primaire non reportable dans la table référencée.
Vous devez (verrouiller toutes les tables impliquées et) supprimer / recréer les contraintes FK et mettre à jour toutes les valeurs FK manuellement (voir option 3. ). Ou vous devez déplacer les valeurs avec une seconde UPDATE
pour éviter les conflits. Par exemple, en supposant que vous n'avez pas de nombres négatifs:
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;
Inconvénients comme mentionné ci-dessus.
3. Tableau de Temp, TRUNCATE
,INSERT
Une option de plus si vous avez beaucoup de RAM. Cela combine certains des avantages des deux premières façons. Presque aussi vite que l'option 1. et vous obtenez une nouvelle table vierge sans ballonnement mais gardez toutes les contraintes et dépendances en place comme dans l'option 2.
Cependant , selon la documentation:
TRUNCATE
ne peut pas être utilisé sur une table qui a des références
de clé étrangère provenant d'autres tables, sauf si toutes ces tables sont également tronquées dans la même commande. La vérification de la validité dans de tels cas nécessiterait des analyses de table, et le but n'est pas d'en faire une.
Accentuation mienne.
Vous pouvez supprimer temporairement les contraintes FK et utiliser des CTE de modification des données pour mettre à jour toutes les colonnes 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;
Connexes, avec plus de détails:
FOREIGN KEYS
sont définis surCASCADE
Impossible, vous pouvez simplement parcourir les anciennes clés primaires et mettre à jour leurs valeurs sur place (de l'ancienne valeur à la nouvelle)? Il s'agit essentiellement de l'option 3 sansTRUNCATE tbl
, remplaçantINSERT
par unUPDATE
, et sans avoir besoin de mettre à jour les clés étrangères manuellement.UPDATE
commande distincte pour chaque ligne. Voir explication en ② ou essayez de voir par vous-même.