13

跑了半天,id字段的洞越来越多。一些表的 id 是 int32,并且 id 序列已达到最大值。一些 Java 源是只读的,所以我不能简单地将 id 列类型从 更改int32long,这会破坏 API。

我想重新编号。这可能不是好的做法,但这个问题不关心好坏。我想重新编号,尤其是那些非常长的 ID,例如“61789238”、“548273826529524324”。我不知道为什么它们这么长,但较短的 ID 也更容易手动处理。

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

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

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

4

5 回答 5

17

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

大多数情况下,当前接受的答案 将失败。

  • 通常,您对列有PRIMARY KEYorUNIQUE约束id,这是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)约束比删除约束并将其(它们)添加回来要多得多。

如果其他表中存在引用 的 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 的参考答案

于 2015-08-24T14:14:11.047 回答
15

假设您的 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)
于 2011-08-01T06:49:25.277 回答
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'。

于 2015-08-24T16:42:20.327 回答
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%' 
于 2016-01-19T09:10:55.620 回答
1

因为我不喜欢这些答案,所以我在 PL/pgSQL 中编写了一个函数来完成这项工作。它是这样称呼的:

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

接受 3 个参数

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

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

该函数在按命名列排序的表上循环,并对每一行进行更新。然后为序列设置新值。就是这样。

  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;
于 2019-11-28T20:29:03.433 回答