压缩或重新编号所有表的 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 PostgreSQL

currently accepted answer在大多数情况下会失败

通常,您在 id 列上具有 PRIMARY KEYUNIQUE 约束,默认情况下为 NOT DEFERRABLE。 (OP 提到了references and constraints。)在每一行之后都会检查此类约束,因此您很可能在尝试时遇到唯一违规错误。详情:

Constraint defined DEFERRABLE INITIALLY IMMEDIATE is still DEFERRED?

通常,人们希望在缩小间隙的同时保留原始的行顺序。但是行的更新顺序是任意,导致任意数字。演示的示例似乎保留了原始顺序,因为物理存储仍然与所需的顺序一致(刚才按所需顺序插入的行),这在现实世界的应用程序中几乎从未出现过,并且完全不可靠。

李>

事情比起初看起来要复杂得多。 一个解决方案(除其他外),如果您有能力暂时移除 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: 不需要CASCADEDROP 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)?的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 从表中删除所有行并将 ID 重置为零

将 PostgreSQL 主键重置为 1

在压缩/修复期间防止自动编号重置 (MS Access)

删除条目后,重置所有“entryno”属性的编号顺序

修行之路~欧喷扎职3.9-3339

S:List