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

发布于 2024-11-27 14:51:58 字数 404 浏览 3 评论 0原文

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

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

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

PostgreSQL 本身支持 ID 重新编号吗?或者是否有任何插件或维护实用程序可以完成这项工作?

也许我可以写一些存储过程?那太好了,这样我就可以每年安排一次。

After running for a long time, I get more and more holes in the id field. Some tables' id are int32, and the id sequence is reaching its maximum value. Some of the Java sources are read-only, so I cannot simply change the id column type from int32 to long, which would break the API.

I'd like to renumber them all. This may be not good practice, but good or bad is not concerned in this question. I want to renumber, especially, those very long IDs like "61789238", "548273826529524324". I don't know why they are so long, but shorter IDs are also easier to handle manually.

But it's not easy to compact IDs by hand because of references and constraints.

Does PostgreSQL itself support of ID renumbering? Or is there any plugin or maintaining utility for this job?

Maybe I can write some stored procedures? That would be very nice so I can schedule it once a year.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

一片旧的回忆 2024-12-04 14:51:59

由于我不喜欢这些答案,因此我在 PL/pgSQL 中编写了一个函数来完成这项工作。
它的调用方式如下:

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

采用 3 个参数

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

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

该函数循环遍历按指定列排序的表,并对每一行进行更新。然后设置序列的新值。就是这样。

  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;

Since I didn't like the answers, I wrote a function in PL/pgSQL to do the job.
It is called like this :

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

Takes 3 parameters

  1. name of table
  2. name of column that is SERIAL
  3. name of sequence that the SERIAL uses

The function returns a short report of what it has done, with the previous value of the sequence and the new value.

The function LOOPs over the table ORDERed by the named column and makes an UPDATE for each row. Then sets the new value for the sequence. That's it.

  1. The order of the values is preserved.
  2. No ADDing and DROPing of temporary columns or tables involved.
  3. No DROPing and ADDing of constraints and foreign keys needed.
  4. Of course You better have ON UPDATE CASCADE for those foreign keys.

The code :

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;
廻憶裏菂餘溫 2024-12-04 14:51:58

这个问题很老了,但在尝试应用此处建议的内容后,我们从 dba.SE 上的绝望用户那里得到了一个新问题。在那里找到更多详细信息和解释的答案:

当前接受的答案 在大多数情况下都会失败

  • 通常,您对 id 列有一个 PRIMARY KEYUNIQUE 约束,该约束是 NOT DEFERRABLE默认情况下。 (OP 提到了引用和约束。)此类约束会在每行之后进行检查,因此您很可能会遇到唯一违规错误。详情:

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

事情比乍一看更复杂。 一个解决方案(除其他外)如果您有能力暂时删除 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)约束比删除约束并将其添加回来要花费更多。

如果其他表中有 FK 列引用 tbl.id,请使用 数据修改 CTE 以更新所有这些。

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

更多信息请参见 参考 dba.SE 上的答案

The question is old, but we got a new question from a desperate user on dba.SE after trying to apply what is suggested here. Find an answer with more details and explanation over there:

The currently accepted answer will fail for most cases.

  • Typically, you have a PRIMARY KEY or UNIQUE constraint on an id column, which is NOT DEFERRABLE by default. (OP mentions references and constraints.) Such constraints are checked after each row, so you most likely get unique violation errors trying. Details:

  • Typically, one wants to retain the original order of rows while closing gaps. But the order in which rows are updated is arbitrary, leading to arbitrary numbers. The demonstrated example seems to retain the original sequence because physical storage still coincides with the desired order (inserted rows in desired order just a moment earlier), which is almost never the case in real world applications and completely unreliable.

The matter is more complicated than it might seem at first. One solution (among others) if you can afford to remove the PK / UNIQUE constraint (and related FK constraints) temporarily:

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;

This is also much faster for big tables, because checking PK (and FK) constraint(s) for every row costs a lot more than removing the constraint(s) and adding it (them) back.

If there are FK columns in other tables referencing tbl.id, use data-modifying CTEs to update all of them.

Example for a table fk_tbl and a FK column 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

More in the referenced answer on dba.SE.

掩饰不了的爱 2024-12-04 14:51:58

假设您的 id 是从 bignum 序列生成的,只需RESTART 该序列并使用 idcolumn = DEFAULT 更新表格。

警告:如果此 id 列被其他表用作外键,请确保您已打开 on updatecascade 修饰符。

例如:

创建表,放入一些数据,然后删除中间值:

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)

Assuming your ids are generated from a bignum sequence, just RESTART the sequence and update the table with idcolumn = DEFAULT.

CAVEAT: If this id column is used as a foreign key by other tables, make sure you have the on update cascade modifier turned on.

For example:

Create the table, put some data in, and remove a middle value:

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)

Reset your sequence:

db=# ALTER SEQUENCE xseq RESTART;
ALTER SEQUENCE

Update your data:

db=# update foo set id = DEFAULT;
UPDATE 4
db=# select * from foo;
 id | data  
----+-------
  1 | hello
  2 | world
  3 | are
  4 | you
(4 rows)
九局 2024-12-04 14:51:58

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

new id column and Foreign Key(s) while the old ones are still in use. With some (quick) renaming, applications do not have to be aware. (But applications should be inactive during the final renaming step)

\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

UPDATE: added the permutation of new_id (after creating it as a serial)
Funny thing is: it doesn't seem to need 'DEFERRABLE'.

时光病人 2024-12-04 14:51:58

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

*This script will work for postgresql

This is a generic solution that works for all cases

This query find the desciption of the fields of all tables from any database.

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 )

This query propose a solution to fix the sequence of all database tables (this generates a query in the req field which fixes the sequence of the different tables).

It finds the number of records of the table and then increment this number by one.

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%' 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文