如何在postgres中重置序列并用新数据填充id列?

How to reset sequence in postgres and fill id column with new data?

提问人:sennin 提问时间:1/13/2011 更新时间:3/17/2023 访问量:367463

问:

我有一个超过一百万行的表。我需要重置序列并使用新值(1、2、3、4 等)重新分配 id 列。有什么简单的方法可以做到这一点吗?

PostgreSQL 序列

评论

8赞 1/14/2011
真正的问题是:你到底为什么要这样做?据推测,ID 是主键,因此更改主键没有任何好处。主键是一个无意义的(在你的例子中是人为的)值。“重新编号”在关系数据库中没有任何合理的目的。
2赞 x-yuri 3/30/2015
最初,我让应用程序在本地运行,然后将数据复制到生产环境中。但是那里不是从 1 开始的。所以排序结果如下:150、151...、300、1、2......我想,如果我没有重新编号 id,它最终会导致重复的 id 错误。此外,排序依据通常优于排序依据。这就是对我有用的东西ididcreated_at
0赞 Ben Wilson 6/25/2019
这样做的目的是为了继续使用常规 int 而不是 bigint 作为数据库中的主键,该主键会继续递增顺序键,但会不断接收新数据。您将很快遇到有符号整数限制,如果保留现有 ID 并不重要,此过程将带您回到可管理的 ID 号。
1赞 Safa Alai 5/15/2020
另一个用途是测试。您希望在开始每个测试之前将表重置为已知状态,这需要重置 ID。

答:

320赞 Michael Krelin - hacker 1/13/2011 #1

如果您不想保留 ID 的顺序,那么您可以

ALTER SEQUENCE seq RESTART WITH 1;
UPDATE t SET idcolumn=nextval('seq');

我怀疑有一种简单的方法可以按照您选择的顺序执行此操作,而无需重新创建整个表。

评论

5赞 Lars Haugseth 7/12/2013
那不应该是吗?ALTER SEQUENCE seq RESTART WITH 1;
13赞 tahagh 11/28/2013
这可能会导致重复的 ID。为了防止这种情况,您可以先将 all 设置为非常高的值: UPDATE t SET idcolumn=1000000+ nextval('seq');然后运行上面的脚本。
9赞 Vasilen Donchev 9/22/2015
SELECT setval('seq', 1, FALSE)应该做同样的事情(这里,第三个参数 FALSE 发挥了作用,因为它表明必须是 1 而不是 2)nextval
0赞 Michael Krelin - hacker 9/23/2015
@VassilenDontchev,绝对。
0赞 Sabrina Leggett 9/22/2022
对于 postgres,如果您收到有关以下方面的错误,可以添加Unsafe query: 'Update' statement without 'where' updates all table rows at onceWHERE idcolumn > 0
54赞 Frank Heikens 1/13/2011 #2

重置序列:

SELECT setval('sequence_name', 0);

更新当前记录:

UPDATE foo SET id = DEFAULT;

评论

7赞 kbridge4096 4/11/2018
序列的最小值可以大于 0。(类型使用的默认最小值为 1!serialCREATE SEQUENCE
23赞 ivy 7/13/2012 #3

两者都提供的解决方案对我不起作用;

> SELECT setval('seq', 0);
ERROR:  setval: value 0 is out of bounds for sequence "seq" (1..9223372036854775807)

setval('seq', 1)以 2 开始编号,也以 2 开始编号,因为 seq.is_called 为 true(Postgres 版本 9.0.4)ALTER SEQUENCE seq START 1

对我有用的解决方案是:

> ALTER SEQUENCE seq RESTART WITH 1;
> UPDATE foo SET id = DEFAULT;

评论

1赞 PeqNP 12/14/2012
同样的问题在这里。您的解决方案也适用于 PostgreSQL 8.3.10。
137赞 Oliver 12/11/2012 #4

使用 PostgreSQL 8.4 或更高版本,无需再指定 。将使用记录者或上次设置的起始值(很可能是 1)。WITH 1CREATE SEQUENCEALTER SEQUENCE START WITH

重置序列:

ALTER SEQUENCE seq RESTART;

然后更新表的 ID 列:

UPDATE foo SET id = DEFAULT;

资料来源:PostgreSQL Docs

评论

7赞 sheepdog 10/2/2018
这似乎是最好的答案,因为它避免了对序列的起始值做出假设。
2赞 Javi 5/4/2020
我的情况也是最好的答案。我将这个答案与这个答案结合起来,它解释了 ALTER SEQUENCE 命令......所以我将“seq”更改为mytable_id_seq其中“mytable”是我的表名,“id”是我的序列列的名称
0赞 Alexander.Iljushkin 10/21/2022
有趣的是,setval('seq', 1) 将 nextval 结果为 2,但重新启动序列将 nextval 为 1。和 setval('seq', 0) 给出异常
2赞 Dinesh Patil 11/8/2013 #5

如果您使用的是 pgAdmin3,请展开“序列”,右键单击序列,转到“属性”,然后在“定义”选项卡中将“当前值”更改为您想要的任何值。无需查询。

评论

3赞 11101101b 2/19/2014
如果你至少不告诉我们你正在使用什么工具,你的答案就没有价值。
3赞 MvcCmsJon 5/8/2015
这是最简单的方法,显然我认为他说的是pg admin 3
5赞 Stone 5/21/2014 #6

仅供参考:如果需要在 ID 范围(例如 256 - 10000000)之间指定新的起始值:

SELECT setval('"Sequence_Name"', 
       (SELECT coalesce(MAX("ID"),255) 
           FROM "Table_Name" 
           WHERE "ID" < 10000000 and "ID" >= 256)+1
       ); 
8赞 alexkovelsky 4/20/2015 #7

要保留行的顺序,请执行以下操作:

UPDATE thetable SET rowid=col_serial FROM 
(SELECT rowid, row_number() OVER ( ORDER BY lngid) AS col_serial FROM thetable ORDER BY lngid) AS t1 
WHERE thetable.rowid=t1.rowid;
5赞 Frank 6/20/2016 #8

仅重置序列并更新所有行可能会导致重复的 ID 错误。在许多情况下,您必须更新所有行两次。首先使用更高的 ID 以避免重复,然后使用您实际想要的 ID。

请避免为所有 ID 添加固定金额(如其他评论中建议的那样)。如果行数超过此固定数量,会发生什么情况?假设序列的下一个值高于现有行的所有 ID(您只想填补空白),我会这样做:

UPDATE table SET id = DEFAULT;
ALTER SEQUENCE seq RESTART;
UPDATE table SET id = DEFAULT;
1赞 Sean Leather 7/6/2016 #9

受到此处其他答案的启发,我创建了一个 SQL 函数来执行序列迁移。该函数将主键序列移动到新的连续序列,从现有序列范围内或外的任意值 (>= 1) 开始。

我在这里解释了我如何使用这个函数将两个具有相同架构但值不同的数据库迁移到一个数据库中。

首先,函数(它打印生成的 SQL 命令,使其 清楚实际发生了什么):

CREATE OR REPLACE FUNCTION migrate_pkey_sequence
  ( arg_table      text
  , arg_column     text
  , arg_sequence   text
  , arg_next_value bigint  -- Must be >= 1
  )
RETURNS int AS $$
DECLARE
  result int;
  curr_value bigint = arg_next_value - 1;
  update_column1 text := format
    ( 'UPDATE %I SET %I = nextval(%L) + %s'
    , arg_table
    , arg_column
    , arg_sequence
    , curr_value
    );
  alter_sequence text := format
    ( 'ALTER SEQUENCE %I RESTART WITH %s'
    , arg_sequence
    , arg_next_value
    );
  update_column2 text := format
    ( 'UPDATE %I SET %I = DEFAULT'
    , arg_table
    , arg_column
    );
  select_max_column text := format
    ( 'SELECT coalesce(max(%I), %s) + 1 AS nextval FROM %I'
    , arg_column
    , curr_value
    , arg_table
    );
BEGIN
  -- Print the SQL command before executing it.
  RAISE INFO '%', update_column1;
  EXECUTE update_column1;
  RAISE INFO '%', alter_sequence;
  EXECUTE alter_sequence;
  RAISE INFO '%', update_column2;
  EXECUTE update_column2;
  EXECUTE select_max_column INTO result;
  RETURN result;
END $$ LANGUAGE plpgsql;

该函数采用以下参数:migrate_pkey_sequence

  1. arg_table:表名(例如'example')
  2. arg_column:主键列名(例如'id')
  3. arg_sequence:序列名称(例如'example_id_seq')
  4. arg_next_value:迁移后列的下一个值

它执行以下操作:

  1. 将主键值移动到自由范围。我假设接下来是序列 与 1.这也处理了以下情况。nextval('example_id_seq')max(id)arg_next_value > max(id)
  2. 将主键值移动到以 开头的连续范围。键值的顺序保持不变,但 范围不保留。arg_next_value
  3. 打印序列中将遵循的下一个值。这在以下情况下很有用 您想要迁移另一个表的列并与此表合并。

为了演示,我们使用如下定义的序列和表(例如,使用):psql

# CREATE SEQUENCE example_id_seq
  START WITH 1
  INCREMENT BY 1
  NO MINVALUE
  NO MAXVALUE
  CACHE 1;
# CREATE TABLE example
  ( id bigint NOT NULL DEFAULT nextval('example_id_seq'::regclass)
  );

然后,我们插入一些值(例如,从 3 开始):

# ALTER SEQUENCE example_id_seq RESTART WITH 3;
# INSERT INTO example VALUES (DEFAULT), (DEFAULT), (DEFAULT);
-- id: 3, 4, 5

最后,我们将值迁移到以 1 开头。example.id

# SELECT migrate_pkey_sequence('example', 'id', 'example_id_seq', 1);
INFO:  00000: UPDATE example SET id = nextval('example_id_seq') + 0
INFO:  00000: ALTER SEQUENCE example_id_seq RESTART WITH 1
INFO:  00000: UPDATE example SET id = DEFAULT
 migrate_pkey_sequence
-----------------------
                     4
(1 row)

结果:

# SELECT * FROM example;
 id
----
  1
  2
  3
(3 rows)
40赞 Ali Raza Bhayani 8/23/2016 #10

只是为了简化和阐明 ALTER SEQUENCESELECT setval 的正确用法来重置序列:

ALTER SEQUENCE sequence_name RESTART WITH 1;

相当于

SELECT setval('sequence_name', 1, FALSE);

这些语句中的任何一个都可用于重置序列,您可以通过 nextval('sequence_name') 获取下一个值,如下所述

nextval('sequence_name')

评论

1赞 DavidHyogo 3/7/2018
谢谢阿里。我刚刚注意到使用 setval 函数将第三个参数设置为 false 至关重要
5赞 Diego Santa Cruz Mendezú 8/23/2017 #11

就我而言,我通过以下方式实现了这一点:

ALTER SEQUENCE table_tabl_id_seq RESTART WITH 6;

我的表被命名为 table 的位置

评论

2赞 Brylie Christopher Oxley 2/26/2018
感谢您在表名中包含一个具体示例。其他答案有点太模棱两可了。
42赞 jahmed31 12/7/2017 #12

重置序列以从数字 1 开始的最佳方法是执行以下命令:

ALTER SEQUENCE <tablename>_<id>_seq RESTART WITH 1

因此,例如,对于 users 表,它将是:

ALTER SEQUENCE users_id_seq RESTART WITH 1
1赞 Yordan Georgiev 12/7/2018 #13

甚至自动增量列也不是 PK(在本例中称为 seq - aka sequence),您可以使用触发器实现:

如果存在,则删除表 devops_guide CASCADE;

SELECT 'create the "devops_guide" table'
;
   CREATE TABLE devops_guide (
      guid           UUID NOT NULL DEFAULT gen_random_uuid()
    , level          integer NULL
    , seq            integer NOT NULL DEFAULT 1
    , name           varchar (200) NOT NULL DEFAULT 'name ...'
    , description    text NULL
    , CONSTRAINT pk_devops_guide_guid PRIMARY KEY (guid)
    ) WITH (
      OIDS=FALSE
    );

-- START trg_devops_guide_set_all_seq
CREATE OR REPLACE FUNCTION fnc_devops_guide_set_all_seq()
    RETURNS TRIGGER
    AS $$
       BEGIN
         UPDATE devops_guide SET seq=col_serial FROM
         (SELECT guid, row_number() OVER ( ORDER BY seq) AS col_serial FROM devops_guide ORDER BY seq) AS tmp_devops_guide
         WHERE devops_guide.guid=tmp_devops_guide.guid;

         RETURN NEW;
       END;
    $$ LANGUAGE plpgsql;

 CREATE TRIGGER trg_devops_guide_set_all_seq
  AFTER UPDATE OR DELETE ON devops_guide
  FOR EACH ROW
  WHEN (pg_trigger_depth() < 1)
  EXECUTE PROCEDURE fnc_devops_guide_set_all_seq();
4赞 Jumshud 11/28/2021 #14

在我的情况下,导入错误的 sql 文件后,所有表中的序列都已损坏。 返回的值小于列的最大值。 因此,我创建了sql脚本来恢复每个表的所有序列:SELECT nextval('table_name_id_seq');id

DO
$$
DECLARE
   rec  record;
   table_seq text;
BEGIN
   FOR rec IN
      SELECT *
      FROM   pg_tables
      WHERE  tablename NOT LIKE 'pg\_%'
      ORDER  BY tablename
   LOOP
      table_seq := rec.tablename || '_id_seq';
     
      RAISE NOTICE '%', table_seq;
      EXECUTE format(E'SELECT setval(\'%I\', COALESCE((SELECT MAX(id)+1 FROM %I), 1), false);',
            table_seq, rec.tablename);
      
   END LOOP;
END
$$;

注意:如果任何表上都没有该列,则可以更新逻辑或根据上述逻辑单独处理它们。id

8赞 Muhammed_G 4/25/2022 #15
SELECT SETVAL('seq_my_table_pk_id', (SELECT MAX(my_table_pk_id) + 1 FROM my_table));
4赞 Jolev 1/15/2023 #16

例如,若要使用表“TAB_B”的“FIELD_ID”字段的最大值更新序列“SEQ_A”的值,可以使用以下命令:

SELECT setval('SEQ_A', (SELECT max(FIELD_ID) FROM TAB_B));

此命令选择表“TAB_B”的“FIELD_ID”字段的最大值,并将其设置为序列“SEQ_A”的下一个值。

评论

0赞 Christian Severin 1/17/2023
谢谢你的回答,@sennin。虽然你的答案是绝对正确的,但它主要重复了几年前已经回答过的事情。