En primer lugar, se deben esperar huecos en una secuencia. Pregúntese si realmente necesita eliminarlos. Tu vida se vuelve más simple si solo vives con ella. Para obtener números sin espacios, la alternativa (a menudo mejor) es usar un VIEW
con row_number()
. Ejemplo en esta respuesta relacionada:
Aquí hay algunas recetas para eliminar huecos.
1. Nueva mesa prístina
Evita complicaciones con infracciones únicas e hinchazón de la mesa y es rápido . Solo para casos simples en los que no está vinculado por referencias FK, vistas en la tabla u otros objetos dependientes, o por acceso concurrente. Hazlo en una transacción para evitar accidentes:
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 estructura incl. restricciones y valores predeterminados de la tabla original. Luego, haga que la nueva columna de la tabla posea la secuencia:
Y restablecerlo al nuevo máximo:
Esto conlleva la ventaja de que la nueva tabla está libre de hinchazón y agrupada id
.
2. UPDATE
en su lugar
Esto produce muchas filas muertas y requiere (auto) VACUUM
más tarde.
Si la serial
columna también es PRIMARY KEY
(como en su caso) o tiene una UNIQUE
restricción, debe evitar infracciones únicas en el proceso. El valor predeterminado (más barato) para las restricciones PK / UNIQUE es ser NOT DEFERRABLE
, lo que obliga a una comprobación después de cada fila individual. Todos los detalles bajo esta pregunta relacionada sobre SO:
Puede definir su restricción como DEFERRABLE
(lo que lo hace más costoso).
O puede soltar la restricción y volver a agregarla cuando haya terminado:
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;
Tampoco es posible mientras tengaFOREIGN KEY
restriccionesquehagan referencia a la (s) columna (s) porque ( según la documentación ):
Las columnas a las que se hace referencia deben ser las columnas de una restricción de clave primaria o única no diferible en la tabla de referencia.
Debería (bloquear todas las tablas involucradas y) soltar / recrear restricciones FK y actualizar todos los valores FK manualmente (ver opción 3. ). O tiene que mover valores fuera del camino con un segundo UPDATE
para evitar conflictos. Por ejemplo, suponiendo que no tiene números negativos:
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;
Inconvenientes como se mencionó anteriormente.
3. Mesa Temp TRUNCATE
,INSERT
Una opción más si tienes mucha RAM. Esto combina algunas de las ventajas de las dos primeras formas. Casi tan rápido como la opción 1. y obtienes una tabla nueva e impecable sin hinchazón, pero mantienes todas las restricciones y dependencias como en la opción 2.
Sin embargo , según la documentación:
TRUNCATE
no se puede usar en una tabla que tenga referencias de clave externa
de otras tablas, a menos que todas esas tablas también se trunquen en el mismo comando. Verificar la validez en tales casos requeriría escaneos de tabla, y el punto no es hacer uno.
El énfasis audaz es mío.
Puede eliminar las restricciones FK temporalmente y usar CTE modificadores de datos para actualizar todas las columnas 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;
Relacionado, con más detalles:
FOREIGN KEYS
está configurado paraCASCADE
, ¿no podría simplemente recorrer las claves primarias antiguas y actualizar sus valores en el lugar (del valor antiguo al nuevo)? Esencialmente, esta es la opción 3 sinTRUNCATE tbl
, reemplazandoINSERT
por unaUPDATE
, y sin necesidad de actualizar las claves externas manualmente.