压缩或重新编号所有表的ID,并将序列重置为max(id)?

时间:2011-08-01 04:13:10

标签: postgresql auto-increment identity compact-database

运行了很长时间后,我在id字段中出现了越来越多的漏洞。一些表的id是int32,id序列达到了它的最大值。某些Java源代码是只读的,因此我不能简单地将id列类型从int32更改为long,这会破坏API。

我想重新编号。这可能不是一个好的做法,但好或坏并不关心这个问题。我想重新编号,特别是那些很长的ID,如“61789238”,“548273826529524324”。我不知道它们为何如此长,但更短的ID也更容易手动处理。

但由于引用和约束,手动压缩ID并不容易。

PostgreSQL本身是否支持ID重新编号?或者是否有任何插件或维护工具?

也许我可以写一些存储过程?这将非常好,所以我可以每年安排一次。

5 个答案:

答案 0 :(得分:10)

假设您的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)

答案 1 :(得分:10)

这个问题已经过时了,但是在尝试应用此处建议的内容后,我们从dba.SE上的绝望用户那里得到了一个新问题。通过更多详情和解释

找到答案

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

  • 通常,PRIMARY KEY列上有UNIQUEid约束,默认情况下为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中的更多内容。

答案 2 :(得分:2)

新的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'。

答案 3 :(得分:1)

*此脚本适用于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%' 

答案 4 :(得分:1)

由于我不喜欢这些答案,因此我在PL / pgSQL中编写了一个函数来完成这项工作。 这样称呼:

=> SELECT resequence('port','id','port_id_seq');
 resequence   
--------------
 5090 -> 3919

采用3个参数

  1. 表名
  2. SERIAL列的名称
  3. SERIAL使用的序列名称

该函数返回已完成操作的简短报告,其中包含序列的先前值和新值。

该函数在由命名列排序的表上遍历,并为每一行进行UPDATE。然后为序列设置新值。就是这样。

  1. 保留值的顺序。
  2. 不涉及临时列或表的添加和删除。
  3. 无需删除和添加约束和外键。
  4. 当然,您最好为那些外键使用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;