首先,序列中的空白是可以预期的。问问自己是否真的需要删除它们。只要与之同住,生活就会变得更加简单。要获得无间隙的数字,(通常更好)的替代方法是使用VIEW
with row_number()
。此相关答案中的示例:
这里有一些消除空白的方法。
1.全新的原始桌子
避免因独特的违规和餐桌膨胀而引起的并发症,而且速度很快。仅在简单情况下,您不受FK引用,表或其他相关对象的视图或并发访问的约束。这样做在一个事务中避免事故的发生:
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)
复制包含结构。原始表的约束和默认值。然后使新表列拥有序列:
并将其重置为新的最大值:
这样做的好处是,新表是无肿胀的,并且群集在上id
。
2. UPDATE
就位
这会产生很多死行,并且需要VACUUM
稍后(自动)。
如果该serial
列也是PRIMARY KEY
(如您的情况)列或具有UNIQUE
约束,则必须避免该过程中的唯一违规。PK / UNIQUE约束的(更便宜)默认值是NOT DEFERRABLE
,这将强制在每个单行之后进行检查。有关SO的此相关问题下的所有详细信息:
您可以将约束定义为DEFERRABLE
(这样会使约束更加昂贵)。
或者,您可以删除约束并在完成后将其添加回去:
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;
当您有FOREIGN KEY
约束条件引用列时,这都是不可能的,因为(根据文档):
引用的列必须是引用表中不可延迟的唯一或主键约束的列。
您将需要(锁定所有涉及的表并)删除/重新创建FK约束并手动更新所有FK值(请参阅选项3。)。或者,您必须花一秒钟的时间UPDATE
来移动值以避免冲突。例如,假设您没有负数:
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;
如上所述的缺点。
3. Temp表TRUNCATE
,INSERT
如果您有足够的RAM,则是另一种选择。这结合了前两种方式的一些优点。几乎与选项1一样快,您会得到一个原始的,没有膨胀的新表,但可以像选项2一样将所有约束和依赖项保留在适当的位置。
但是,根据文档:
TRUNCATE
不能在具有
来自其他表的外键引用的表上使用,除非所有这些表也在同一命令中被截断。在这种情况下,检查有效性将需要进行表扫描,而整个过程都不是一回事。
大胆强调我的。
您可以暂时删除FK约束,并使用修改数据的CTE更新所有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;
相关,有更多详细信息:
FOREIGN KEYS
都设置为CASCADE
不能,则不能简单地循环遍历旧的主键并就地更新它们的值(从旧值到新值)?本质上,这是选择三无TRUNCATE tbl
,替换INSERT
用UPDATE
,并且无需手动更新外键。