压缩或重新编号所有表的 ID,并将序列重置为 max(id)?
Posted
技术标签:
【中文标题】压缩或重新编号所有表的 ID,并将序列重置为 max(id)?【英文标题】:Compact or renumber IDs for all tables, and reset sequences to max(id)? 【发布时间】:2011-10-17 04:10:34 【问题描述】:跑了半天,id字段的洞越来越多。一些表的 id 是 int32,并且 id 序列已达到最大值。一些 Java 源代码是只读的,所以我不能简单地将 id 列类型从 int32
更改为 long
,这会破坏 API。
我想重新编号。这可能不是好的做法,但这个问题不关心好坏。我想重新编号,尤其是那些非常长的 ID,例如“61789238”、“548273826529524324”。我不知道为什么它们这么长,但较短的 ID 也更容易手动处理。
但由于引用和约束,手动压缩 ID 并不容易。
PostgreSQL 本身是否支持 ID 重新编号?或者这项工作是否有任何插件或维护实用程序?
也许我可以写一些存储过程?那太好了,所以我可以每年安排一次。
【问题讨论】:
【参考方案1】:假设您的 id 是从 bignum
序列生成的,只需 RESTART
序列并使用 idcolumn = DEFAULT
更新表。
CAVEAT:如果此 id
列被其他表用作外键,请确保您已打开 on update cascade
修饰符。
例如:
创建表,放入一些数据,去掉一个中间值:
db=# create sequence xseq;
CREATE SEQUENCE
db=# create table foo ( id bigint default nextval('xseq') not null, data text );
CREATE TABLE
db=# insert into foo (data) values ('hello'), ('world'), ('how'), ('are'), ('you');
INSERT 0 5
db=# delete from foo where data = 'how';
DELETE 1
db=# select * from foo;
id | data
----+-------
1 | hello
2 | world
4 | are
5 | you
(4 rows)
重置您的序列:
db=# ALTER SEQUENCE xseq RESTART;
ALTER SEQUENCE
更新您的数据:
db=# update foo set id = DEFAULT;
UPDATE 4
db=# select * from foo;
id | data
----+-------
1 | hello
2 | world
3 | are
4 | you
(4 rows)
【讨论】:
对于大多数用例来说,这不会像预期的那样工作。考虑added answer中的详细信息。 一个小技巧可以使这个答案在所有情况下都有效:您只需将 id 重新编号为一些独特的更高数字,这些数字肯定不会干扰新的紧凑 id。因此,在运行上述答案之前,只需执行以下操作:UPDATE foo SET id = id + (SELECT max(id) FROM foo);
【参考方案2】:
这个问题很老,但在尝试应用此处建议的内容后,我们在 dba.SE 上收到了一个绝望的用户提出的新问题。通过那里有更多详细信息和解释找到答案:
Compacting a sequence in PostgreSQLcurrently accepted answer在大多数情况下会失败。
通常,您在 id
列上具有 PRIMARY KEY
或 UNIQUE
约束,默认情况下为 NOT DEFERRABLE
。 (OP 提到了references and constraints
。)在每一行之后都会检查此类约束,因此您很可能在尝试时遇到唯一违规错误。详情:
通常,人们希望在缩小间隙的同时保留原始的行顺序。但是行的更新顺序是任意,导致任意数字。演示的示例似乎保留了原始顺序,因为物理存储仍然与所需的顺序一致(刚才按所需顺序插入的行),这在现实世界的应用程序中几乎从未出现过,并且完全不可靠。
李>事情比起初看起来要复杂得多。 一个解决方案(除其他外),如果您有能力暂时移除 PK / UNIQUE 约束(和相关的 FK 约束):
BEGIN;
LOCK tbl;
-- remove all FK constraints to the column
ALTER TABLE tbl DROP CONSTRAINT tbl_pkey; -- remove PK
-- for the simple case without FK references - or see below:
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;
-- Update referencing value in FK columns at the same time (if any)
SELECT setval('tbl_id_seq', max(id)) FROM tbl; -- reset sequence
ALTER TABLE tbl ADD CONSTRAINT tbl_pkey PRIMARY KEY(id); -- add PK back
-- add all FK constraints to the column back
COMMIT;
这对于大表也快得多,因为检查每一行的 PK(和 FK)约束比删除约束并添加它(它们)要多得多返回。
如果其他表中存在引用 tbl.id
的 FK 列,请使用 data-modifying CTEs 更新所有列。
表格fk_tbl
和FK 列fk_id
的示例:
WITH u1 AS (
UPDATE tbl t
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
RETURNING t.id, t1.new_id -- return old and new ID
)
UPDATE fk_tbl f
SET fk_id = u1.new_id -- set to new ID
FROM u1
WHERE f.fk_id = u1.id; -- match on old ID
更多内容请关注referenced answer on dba.SE。
【讨论】:
还有另一种方法:重命名id
列,添加一个新 序列id
列;引用 FK 也一样,然后使用 oldid, newid 更新引用的 FK,然后删除 oldid, oldFK 重命名的顺序可以变化;在极端情况下,新旧 id 和 FK 共存,允许旧方案在工作进行时仍然存在。我应该详细说明吗?
@joop:您可以在此处添加另一个带有详细信息的答案,或者更好的是,在new question on dba.SE with a much more substantial answer 下。
我在那里没有帐户(什么?没有单点登录?)所以我会在这里发布。
@joop:您可以使用现有的 stackexchange 帐户在 dba.se“注册”。【参考方案3】:
新的 id 列和外键,而旧的仍在使用中。通过一些(快速)重命名,应用程序不必知道。 (但在最后的重命名步骤中应用程序应该处于非活动状态)
\i tmp.sql
-- the test tables
CREATE TABLE one (
id serial NOT NULL PRIMARY KEY
, payload text
);
CREATE TABLE two (
id serial NOT NULL PRIMARY KEY
, the_fk INTEGER REFERENCES one(id)
ON UPDATE CASCADE ON DELETE CASCADE
);
-- And the supporting index for the FK ...
CREATE INDEX ON two(the_fk);
-- populate
INSERT INTO one(payload)
SELECT x::text FROM generate_series(1,1000) x;
INSERT INTO two(the_fk)
SELECT id FROM one WHERE random() < 0.3;
-- make some gaps
DELETE FROM one WHERE id % 13 > 0;
-- SELECT * FROM two;
-- Add new keycolumns to one and two
ALTER TABLE one
ADD COLUMN new_id SERIAL NOT NULL UNIQUE
;
-- UPDATE:
-- This could need DEFERRABLE
-- Note since the update is only a permutation of the
-- existing values, we dont need to reset the sequence.
UPDATE one SET new_id = self.new_id
FROM ( SELECT id, row_number() OVER(ORDER BY id) AS new_id FROM one ) self
WHERE one.id = self.id;
ALTER TABLE two
ADD COLUMN new_fk INTEGER REFERENCES one(new_id)
;
-- update the new FK
UPDATE two t
SET new_fk = o.new_id
FROM one o
WHERE t.the_fk = o.id
;
SELECT * FROM two;
-- The crucial part: the final renaming
-- (at this point it would be better not to allow other sessions
-- messing with the one,two tables ...
-- --------------------------------------------------------------
ALTER TABLE one DROP COLUMN id CASCADE;
ALTER TABLE one rename COLUMN new_id TO id;
ALTER TABLE one ADD PRIMARY KEY(id);
ALTER TABLE two DROP COLUMN the_fk CASCADE;
ALTER TABLE two rename COLUMN new_fk TO the_fk;
CREATE INDEX ON two(the_fk);
-- Some checks.
-- (the automatically generated names for the indexes
-- and the sequence still contain the "new" names.)
SELECT * FROM two;
\d one
\d two
更新:添加了 new_id 的排列(在将其创建为序列之后) 有趣的是:它似乎不需要'DEFERRABLE'。
【讨论】:
一些细节:1: 通常,人们希望在缩小差距的同时保留原始订单。ADD COLUMN new_id SERIAL NOT NULL UNIQUE
不这样做 - 就像 currently accepted answer 一样。 2: 新的 FK 约束应该 CASCADE
像旧的一样。 3: 不需要CASCADE
和DROP COLUMN the_fk
。
0) 它的主要目的是作为一个 PoC。 1)您对顺序是正确的,我认为没有人会对键值的顺序感兴趣...... 2)没有 CASCADE,drop 列在这里不起作用(9.3.5) 3)同上。 2+3 可以很容易地修复(可能需要一些额外的步骤) 1 有点难;之后至少需要一个 row_number() 加上一个 set_val()。
添加的语句使用不可延迟的约束,因为它碰巧按顺序更新行。窗口函数row_number()
产生一个有序集,Postgres 只是在 UPDATE 中使用它,所以不会出现冲突。但是,这是一个没有记录的实现细节,也不能保证在所有实现中都可以工作或在 Postgres 版本中继续工作。当前接受的答案以任意顺序更新,几乎肯定会失败。要验证我的解释,请将ORDER BY random()
添加到 UPDATE 的子查询中,您将得到一个唯一的违规错误。
[我相信这是由一个实现细节引起的,但是] 我希望在一个接一个地排列一组 N 个键值(到它们自己)时,触摸第一个(或其中任何一个) ) 已经创建了一个(时间)副本。因此,出于某种原因,PG 能够将部分检查(在这种特殊情况下)推迟到操作的稍后时间点(我们可以称之为“半推迟”/-)再想一想,这可能是一个副作用行版本控制过程。顺便说一句:将 UNIQUE 约束添加到 new_id 推迟到操作的后期阶段是微不足道的。
Postgres 不会推迟检查,这是明确记录的。我们详细讨论了under this related question。我还将链接添加到我的答案中。一步一步地完成它。如果按顺序更新,则没有更新的行违反唯一 (PK) 约束。【参考方案4】:
*此脚本适用于 postgresql
这是适用于所有情况的通用解决方案
此查询查找任何数据库中所有表的字段的描述。
WITH description_bd AS (select colum.schemaname,coalesce(table_name,relname) as table_name , column_name, ordinal_position, column_default, data_type, is_nullable, character_maximum_length, is_updatable,description from
( SELECT columns.table_schema as schemaname,columns.table_name, columns.column_name, columns.ordinal_position, columns.column_default, columns.data_type, columns.is_nullable, columns.character_maximum_length, columns.character_octet_length, columns.is_updatable, columns.udt_name
FROM information_schema.columns
) colum
full join (SELECT schemaname, relid, relname,objoid, objsubid, description
FROM pg_statio_all_tables ,pg_description where pg_statio_all_tables.relid= pg_description.objoid ) descre
on descre.relname = colum.table_name and descre.objsubid=colum.ordinal_position and descre.schemaname=colum.schemaname )
这个查询提出了一个解决所有数据库表顺序的解决方案(这会在 req 字段中生成一个查询来固定不同表的顺序)。
它找到表的记录数,然后将该数字加一。
SELECT table_name, column_name, ordinal_position,column_default,
data_type, is_nullable, character_maximum_length, is_updatable,
description,'SELECT setval('''||schemaname||'.'|| replace(replace(column_default,'''::regclass)',''),'nextval(''','')||''', (select max( '||column_name ||')+1 from '|| table_name ||' ), true);' as req
FROM description_bd where column_default like '%nextva%'
【讨论】:
您好@Mesbah Gueffaf,如果您更好地格式化 SQL 语句(我的建议是更短的行和一致的缩进),这将有助于您的答案的可读性,并准确解释语句的作用以及它们的原因工作。 感谢@NielsAbildgaard 的评论。我们为答案添加了更多解释。我们希望这项贡献可以帮助社区。span> 【参考方案5】:因为我不喜欢这些答案,所以我在 PL/pgSQL 中编写了一个函数来完成这项工作。 它是这样称呼的:
=> SELECT resequence('port','id','port_id_seq');
resequence
--------------
5090 -> 3919
取 3 个参数
-
表名
SERIAL 列的名称
SERIAL 使用的序列名称
该函数返回一个关于它所做的事情的简短报告,包括序列的先前值和新值。
函数循环遍历按命名列排序的表,并对每一行进行更新。然后为序列设置新值。就是这样。
-
保留值的顺序。
不涉及临时列或表的添加和删除。
无需删除和添加约束和外键。
当然,您最好为这些外键设置 ON UPDATE CASCADE。
代码:
CREATE OR REPLACE FUNCTION resequence(_tbl TEXT, _clm TEXT, _seq TEXT) RETURNS TEXT AS $FUNC$
DECLARE
_old BIGINT;_new BIGINT := 0;
BEGIN
FOR _old IN EXECUTE 'SELECT '||_clm||' FROM '||_tbl||' ORDER BY '||_clm LOOP
_new=_new+1;
EXECUTE 'UPDATE '||_tbl||' SET '||_clm||'='||_new||' WHERE '||_clm||'='||_old;
END LOOP;
RETURN (nextval(_seq::regclass)-1)||' -> '||setval(_seq::regclass,_new);
END $FUNC$ LANGUAGE plpgsql;
【讨论】:
以上是关于压缩或重新编号所有表的 ID,并将序列重置为 max(id)?的主要内容,如果未能解决你的问题,请参考以下文章