Primeiro, são esperadas lacunas em uma sequência. Pergunte a si mesmo se você realmente precisa removê-los. Sua vida fica mais simples se você apenas viver com ela. Para obter números sem falhas, a alternativa (geralmente melhor) é usar a VIEW
com row_number()
. Exemplo nesta resposta relacionada:
Aqui estão algumas receitas para remover as lacunas.
1. Mesa nova e original
Evita complicações com violações únicas e inchaço na mesa e é rápido . Somente para casos simples em que você não está vinculado por referências do FK, visualizações na tabela ou outros objetos dependentes ou pelo acesso simultâneo. Faça isso em uma transação para evitar acidentes:
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 a estrutura incl. restrições e padrões da tabela original. Em seguida, torne a nova coluna da tabela a própria sequência:
E redefina-o para o novo máximo:
Isso traz a vantagem de que a nova tabela está livre de inchaço e agrupada id
.
2. UPDATE
no lugar
Isso produz muitas linhas mortas e requer (automático) VACUUM
posteriormente.
Se a serial
coluna também for PRIMARY KEY
(como no seu caso) ou tiver uma UNIQUE
restrição, você deverá evitar violações exclusivas no processo. O padrão (mais barato) para as restrições PK / UNIQUE deve ser NOT DEFERRABLE
, o que força uma verificação após cada linha. Todos os detalhes nesta pergunta relacionada ao SO:
Você pode definir sua restrição como DEFERRABLE
(o que a torna mais cara).
Ou você pode eliminar a restrição e adicioná-la novamente quando terminar:
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;
Também não é possível enquanto você tiverFOREIGN KEY
restrições referentes à (s) coluna (s) porque ( por documentação ):
As colunas referenciadas devem ser as colunas de uma restrição de chave primária ou única não diferida na tabela referenciada.
Você precisaria (bloquear todas as tabelas envolvidas e) soltar / recriar restrições de FK e atualizar todos os valores de FK manualmente (consulte a opção 3. ). Ou você precisa mover valores fora do caminho com um segundo UPDATE
para evitar conflitos. Por exemplo, supondo que você não tenha 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;
Desvantagens como mencionado acima.
3. tabela Temp, TRUNCATE
,INSERT
Mais uma opção se você tiver bastante RAM. Isso combina algumas das vantagens das duas primeiras maneiras. Quase tão rápido quanto a opção 1. e você obtém uma nova tabela intocada sem inchaço, mas mantém todas as restrições e dependências no lugar, como na opção 2.
No entanto , por documentação:
TRUNCATE
não pode ser usado em uma tabela que possui referências de chave estrangeira
de outras tabelas, a menos que todas essas tabelas também sejam truncadas no mesmo comando. Verificar a validade nesses casos exigiria varreduras de tabela, e o ponto principal é não fazer uma.
Negrito ênfase minha.
Você pode eliminar as restrições do FK temporariamente e usar CTEs modificadores de dados para atualizar todas as colunas do 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, com mais detalhes:
FOREIGN KEYS
estiver definido comoCASCADE
você não pode simplesmente fazer um loop sobre as chaves primárias antigas e atualizar seus valores no local (do valor antigo para o novo)? Essencialmente, essa é a opção 3 semTRUNCATE tbl
, substituindoINSERT
porUPDATE
, e sem a necessidade de atualizar chaves estrangeiras manualmente.