【问题标题】:Compact or renumber IDs for all tables, and reset sequences to max(id)?压缩或重新编号所有表的 ID,并将序列重置为 max(id)?
【发布时间】:2011-10-17 04:10:34
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: postgresql auto-increment identity compact-database


    【解决方案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 上收到了一个绝望的用户提出的新问题。通过那里有更多详细信息和解释找到答案:

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

    • 通常,您在 id 列上具有 PRIMARY KEYUNIQUE 约束,默认情况下为 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: 不需要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 个参数

    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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-09-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多