不同步时如何重置postgres的主键序列?

Posted

技术标签:

【中文标题】不同步时如何重置postgres的主键序列?【英文标题】:How to reset postgres' primary key sequence when it falls out of sync? 【发布时间】:2010-09-19 15:27:27 【问题描述】:

我遇到了主键序列与表行不同步的问题。

也就是说,当我插入一个新行时,我得到一个重复键错误,因为串行数据类型中隐含的序列返回一个已经存在的数字。

这似乎是由于导入/恢复没有正确维护顺序造成的。

【问题讨论】:

我很好奇..你是在恢复之前删除数据库吗?我对这件事有一个模糊的回忆,但我可能是错的:P PostgreSQL wiki 在Fixing Sequences 上有一个页面。 只是为了帮助 googleability,这里抛出的错误消息是:“重复键值违反唯一约束......” 这就是 Django 中的 sqlsequencereset 的做法: SELECT setval(pg_get_serial_sequence("",'id'), coalesce(max("id"), 1), max("id" ) 不为空) FROM ""; 的第一个实例需要用单引号括起来以使 pg_get_serioal_sequence 函数起作用: SELECT setval(pg_get_serial_sequence('','id'), coalesce(max ("id"), 1), max("id") IS NOT null) FROM ""
【参考方案1】:

试试reindex。

更新:正如 cmets 中所指出的,这是对原始问题的答复。

【讨论】:

重新索引不起作用,它似乎只是将索引增加 1 reindex 不起作用,因为它回答了您最初的问题,关于数据库索引,而不是序列【参考方案2】:
-- Login to psql and run the following

-- What is the result?
SELECT MAX(id) FROM your_table;

-- Then run...
-- This should be higher than the last result.
SELECT nextval('your_table_id_seq');

-- If it's not higher... run this set the sequence last to your highest id. 
-- (wise to run a quick pg_dump first...)

BEGIN;
-- protect against concurrent inserts while you update the counter
LOCK TABLE your_table IN EXCLUSIVE MODE;
-- Update the sequence
SELECT setval('your_table_id_seq', COALESCE((SELECT MAX(id)+1 FROM your_table), 1), false);
COMMIT;

Source - Ruby Forum

【讨论】:

无论如何,将 1 加到 MAX(id) 会在您的 ID 中留下一个数字间隙,因为 setval 设置的是序列的最后一个值,而不是下一个。 如果表中没有行,您的示例将不起作用。所以下面给出的 SQL 更安全: SELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), true); @Valery:但是为了避免上面@mikl 两个cmets提到的差距,你需要SELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), false); 所有问题都已解决并合并到一个查询中:SELECT setval('your_seq',(SELECT GREATEST(MAX(your_id)+1,nextval('your_seq'))-1 FROM your_table)) 如果您的应用程序关心序列中的间隙,那么您的应用程序就会损坏。序列中的间隙是正常的,并且可能由于计划外的数据库关闭、错误后的事务回滚等原因而发生。【参考方案3】:

ALTER SEQUENCE sequence_name RESTART WITH (SELECT max(id) FROM table_name); 没用。

复制自@tardate 答案:

SELECT setval(pg_get_serial_sequence('table_name', 'id'), MAX(id)) FROM table_name;

【讨论】:

这对我来说是 8.4 中的语法错误(在 ^(SELECT... )。RESTART WITH 似乎只接受一个序数值。但这有效: SELECT setval(pg_get_serial_sequence('table_name', ' id'), (SELECT MAX(id) FROM table_name)+1); Muruges 的解决方案在 9.4 中也不起作用。不明白为什么对这个答案有这么多的赞成票。 ALTER SEQUENCE 不允许子查询。 @tardate 的解决方案非常有效。编辑答案以删除不正确的数据。 ALTER SEQUENCE 非常适合我。我曾使用 COPY 来引入一些数据,并且主键中存在间隙,并且 INSERT 正在引发重复键异常。设置顺序就可以了。 9.4 你也可以在需要的时候指定模式名:pg_get_serial_sequence('schema_name.table_name', 'id')【参考方案4】:

pg_get_serial_sequence 可用于避免对序列名称的任何错误假设。这会一次性重置序列:

SELECT pg_catalog.setval(pg_get_serial_sequence('table_name', 'id'), (SELECT MAX(id) FROM table_name)+1);

或者更简洁:

SELECT pg_catalog.setval(pg_get_serial_sequence('table_name', 'id'), MAX(id)) FROM table_name;

但是这个表单不能正确处理空表,因为 max(id) 为空,你也不能设置 0 值,因为它超出了序列的范围。一种解决方法是使用ALTER SEQUENCE 语法,即

ALTER SEQUENCE table_name_id_seq RESTART WITH 1;
ALTER SEQUENCE table_name_id_seq RESTART; -- 8.4 or higher

ALTER SEQUENCE 的用途有限,因为序列名称和重启值不能是表达式。

似乎最好的通用解决方案是调用 setval 并使用 false 作为第三个参数,允许我们指定“下一个要使用的值”:

SELECT setval(pg_get_serial_sequence('t1', 'id'), coalesce(max(id),0) + 1, false) FROM t1;

这符合我的所有要求:

    避免对实际序列名称进行硬编码 正确处理空表 使用现有数据处理表,并且不留下 序列中的漏洞

最后,请注意pg_get_serial_sequence 仅在序列归列所有时才有效。如果递增列被定义为serial 类型,就会出现这种情况,但是如果手动添加序列,则必须确保ALTER SEQUENCE .. OWNED BY 也被执行。

即如果 serial 类型用于创建表,这应该都可以工作:

CREATE TABLE t1 (
  id serial,
  name varchar(20)
);

SELECT pg_get_serial_sequence('t1', 'id'); -- returns 't1_id_seq'

-- reset the sequence, regardless whether table has rows or not:
SELECT setval(pg_get_serial_sequence('t1', 'id'), coalesce(max(id),0) + 1, false) FROM t1;

但如果序列是手动添加的:

CREATE TABLE t2 (
  id integer NOT NULL,
  name varchar(20)
);

CREATE SEQUENCE t2_custom_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;

ALTER TABLE t2 ALTER COLUMN id SET DEFAULT nextval('t2_custom_id_seq'::regclass);

ALTER SEQUENCE t2_custom_id_seq OWNED BY t2.id; -- required for pg_get_serial_sequence

SELECT pg_get_serial_sequence('t2', 'id'); -- returns 't2_custom_id_seq'

-- reset the sequence, regardless whether table has rows or not:
SELECT setval(pg_get_serial_sequence('t2', 'id'), coalesce(max(id),0) + 1, false) FROM t1;

【讨论】:

查询中的'+1'不需要,setval()设置当前值,nextval()已经返回当前值+1。 包装这个方法的函数需要一个参数 - table_name - 在我下面的答案中:***.com/a/13308052/237105 @AntonyHatchkins 欢呼。刚刚看到 +1 错误的另一个重复,所以最后我希望永远拍打它 你也可以在需要的时候指定模式名:pg_get_serial_sequence('schema_name.table_name', 'id') 最后一行有错别字,最后应该是t2【参考方案5】:

从公共重置所有序列

CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text) RETURNS "pg_catalog"."void" AS 
$body$  
  DECLARE 
  BEGIN 
  EXECUTE 'SELECT setval( ''' 
  || tablename  
  || '_id_seq'', ' 
  || '(SELECT id + 1 FROM "' 
  || tablename  
  || '" ORDER BY id DESC LIMIT 1), false)';  
  END;  
$body$  LANGUAGE 'plpgsql';

select sequence_name, reset_sequence(split_part(sequence_name, '_id_seq',1)) from information_schema.sequences
        where sequence_schema='public';

【讨论】:

看来这种方法对列名和表名做了假设,所以它对我不起作用 这不会损坏数据库中的数据吗?【参考方案6】:

这将重置所有公开的序列,不对表或列名做出任何假设。在 8.4 版本上测试

CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text, columnname text, sequence_name text) 
    RETURNS "pg_catalog"."void" AS 
    
    $body$  
      DECLARE 
      BEGIN 
    
      EXECUTE 'SELECT setval( ''' || sequence_name  || ''', ' || '(SELECT MAX(' || columnname || 
          ') FROM ' || tablename || ')' || '+1)';
    
      END;  
    
    $body$  LANGUAGE 'plpgsql';
    
    
SELECT table_name || '_' || column_name || '_seq', 
    reset_sequence(table_name, column_name, table_name || '_' || column_name || '_seq') 
FROM information_schema.columns where column_default like 'nextval%';

【讨论】:

+1 很有用的功能!我们的序列名称与表名称不完全匹配,因此我使用substring(column_default, '''(.*)''') 而不是table_name || '_' || column_name || '_seq'。完美运行。 请注意,如果序列名称包含单引号,或者名称中包含大写字母、空格等的表名称,这将失败。 quote_literalquote_ident 函数,或者最好是 format 函数,应该在这里真正使用。 希望我能给这个多于一票...干得好,先生。至少对我来说,在 Postgres 9.1 上也能很好地工作。 这很棒。我使用substring(column_default from 'nextval\(''(.+)''::regclass\)') 显式获取序列名称。像魅力一样工作。 我一直在寻找这个解决方案超过一天,非常感谢,即使我使用了@ChrisLercher 建议的方法来替换文本substring(column_default, '''(.*)''') instead of table_name || '_' || column_name || '_seq'【参考方案7】:

克劳斯的答案是最有用的,除了一点点想念:你 必须在 select 语句中添加 DISTINCT。

但是,如果您确定没有表+列名可以等效 对于两个不同的表,您还可以使用:

select sequence_name, --PG_CLASS.relname, PG_ATTRIBUTE.attname
       reset_sequence(split_part(sequence_name, '_id_seq',1))
from PG_CLASS
join PG_ATTRIBUTE on PG_ATTRIBUTE.attrelid = PG_CLASS.oid
join information_schema.sequences
     on information_schema.sequences.sequence_name = PG_CLASS.relname || '_' || PG_ATTRIBUTE.attname
where sequence_schema='public';

这是针对以下情况的 user457226 解决方案的扩展 一些感兴趣的列名不是“ID”。

【讨论】:

...当然,还需要更改“reset_sequence”,即添加“columnname”参数,以代替“id”。【参考方案8】:

在我还没有尝试过代码之前:在下面我发布 Klaus 和 user457226 解决方案的 sql 代码版本 它在我的电脑 [Postgres 8.3] 上工作,只是做了一些小的调整 对于 Klaus 和我的 user457226 版本。

克劳斯解决方案:

drop function IF EXISTS rebuilt_sequences() RESTRICT;
CREATE OR REPLACE FUNCTION  rebuilt_sequences() RETURNS integer as
$body$
  DECLARE sequencedefs RECORD; c integer ;
  BEGIN
    FOR sequencedefs IN Select
      constraint_column_usage.table_name as tablename,
      constraint_column_usage.table_name as tablename, 
      constraint_column_usage.column_name as columnname,
      replace(replace(columns.column_default,'''::regclass)',''),'nextval(''','') as sequencename
      from information_schema.constraint_column_usage, information_schema.columns
      where constraint_column_usage.table_schema ='public' AND 
      columns.table_schema = 'public' AND columns.table_name=constraint_column_usage.table_name
      AND constraint_column_usage.column_name = columns.column_name
      AND columns.column_default is not null
   LOOP    
      EXECUTE 'select max('||sequencedefs.columnname||') from ' || sequencedefs.tablename INTO c;
      IF c is null THEN c = 0; END IF;
      IF c is not null THEN c = c+ 1; END IF;
      EXECUTE 'alter sequence ' || sequencedefs.sequencename ||' restart  with ' || c;
   END LOOP;

   RETURN 1; END;
$body$ LANGUAGE plpgsql;

select rebuilt_sequences();

user457226 解决方案:

--drop function IF EXISTS reset_sequence (text,text) RESTRICT;
CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text,columnname text) RETURNS bigint --"pg_catalog"."void"
AS
$body$
  DECLARE seqname character varying;
          c integer;
  BEGIN
    select tablename || '_' || columnname || '_seq' into seqname;
    EXECUTE 'SELECT max("' || columnname || '") FROM "' || tablename || '"' into c;
    if c is null then c = 0; end if;
    c = c+1; --because of substitution of setval with "alter sequence"
    --EXECUTE 'SELECT setval( "' || seqname || '", ' || cast(c as character varying) || ', false)'; DOES NOT WORK!!!
    EXECUTE 'alter sequence ' || seqname ||' restart with ' || cast(c as character varying);
    RETURN nextval(seqname)-1;
  END;
$body$ LANGUAGE 'plpgsql';

select sequence_name, PG_CLASS.relname, PG_ATTRIBUTE.attname,
       reset_sequence(PG_CLASS.relname,PG_ATTRIBUTE.attname)
from PG_CLASS
join PG_ATTRIBUTE on PG_ATTRIBUTE.attrelid = PG_CLASS.oid
join information_schema.sequences
     on information_schema.sequences.sequence_name = PG_CLASS.relname || '_' || PG_ATTRIBUTE.attname || '_seq'
where sequence_schema='public';

【讨论】:

【参考方案9】:

当序列名、列名、表名或模式名包含有趣的字符(如空格、标点符号等)时,这些函数充满危险。我写了这个:

CREATE OR REPLACE FUNCTION sequence_max_value(oid) RETURNS bigint
VOLATILE STRICT LANGUAGE plpgsql AS  $$
DECLARE
 tabrelid oid;
 colname name;
 r record;
 newmax bigint;
BEGIN
 FOR tabrelid, colname IN SELECT attrelid, attname
               FROM pg_attribute
              WHERE (attrelid, attnum) IN (
                      SELECT adrelid::regclass,adnum
                        FROM pg_attrdef
                       WHERE oid IN (SELECT objid
                                       FROM pg_depend
                                      WHERE refobjid = $1
                                            AND classid = 'pg_attrdef'::regclass
                                    )
          ) LOOP
      FOR r IN EXECUTE 'SELECT max(' || quote_ident(colname) || ') FROM ' || tabrelid::regclass LOOP
          IF newmax IS NULL OR r.max > newmax THEN
              newmax := r.max;
          END IF;
      END LOOP;
  END LOOP;
  RETURN newmax;
END; $$ ;

您可以通过将 OID 传递给单个序列来调用它,它将返回具有默认序列的任何表使用的最大数字;或者您可以使用这样的查询运行它,以重置数据库中的所有序列:

 select relname, setval(oid, sequence_max_value(oid))
   from pg_class
  where relkind = 'S';

使用不同的 qual,您只能重置特定模式中的序列,依此类推。例如,如果您想调整“公共”模式中的序列:

select relname, setval(pg_class.oid, sequence_max_value(pg_class.oid))
  from pg_class, pg_namespace
 where pg_class.relnamespace = pg_namespace.oid and
       nspname = 'public' and
       relkind = 'S';

请注意,由于 setval() 的工作原理,您不需要在结果中加 1。

作为结束说明,我必须警告一些数据库似乎有默认链接到序列的方式,不让系统目录具有它们的完整信息。当你在 psql 的 \d:

中看到这样的东西时,就会发生这种情况
alvherre=# \d baz
                     Tabla «public.baz»
 Columna |  Tipo   |                 Modificadores                  
---------+---------+------------------------------------------------
 a       | integer | default nextval(('foo_a_seq'::text)::regclass)

请注意,该默认子句中的 nextval() 调用除了 ::regclass 强制转换外还有 ::text 强制转换。我认为这是由于旧 PostgreSQL 版本的数据库被 pg_dump'ed。将会发生的是上面的函数 sequence_max_value() 将忽略这样的表。要解决此问题,您可以重新定义 DEFAULT 子句以直接引用序列而不使用强制转换:

alvherre=# alter table baz alter a set default nextval('foo_a_seq');
ALTER TABLE

然后psql正确显示:

alvherre=# \d baz
                     Tabla «public.baz»
 Columna |  Tipo   |             Modificadores              
---------+---------+----------------------------------------
 a       | integer | default nextval('foo_a_seq'::regclass)

一旦您解决了这个问题,该函数就可以正常工作于该表以及所有其他可能使用相同序列的表。

【讨论】:

这太棒了thanx!应该注意的是,我需要在赋值(函数代码中的第 21 行)中添加一个演员,如下所示:newmax := r.max::bigint; 以使其正常工作。 也必须更改此设置:'SELECT max(' || quote_ident(colname) || ') FROM ' => 'SELECT max(' || quote_ident(colname) || '::bigint) FROM ' 请注意在动态构建查询中添加了::bigint【参考方案10】:

我的版本使用第一个,带有一些错误检查...

BEGIN;
CREATE OR REPLACE FUNCTION reset_sequence(_table_schema text, _tablename text, _columnname text, _sequence_name text)
RETURNS pg_catalog.void AS
$BODY$
DECLARE
BEGIN
 PERFORM 1
 FROM information_schema.sequences
 WHERE
  sequence_schema = _table_schema AND
  sequence_name = _sequence_name;
 IF FOUND THEN
  EXECUTE 'SELECT setval( ''' || _table_schema || '.' || _sequence_name  || ''', ' || '(SELECT MAX(' || _columnname || ') FROM ' || _table_schema || '.' || _tablename || ')' || '+1)';
 ELSE
  RAISE WARNING 'SEQUENCE NOT UPDATED ON %.%', _tablename, _columnname;
 END IF;
END; 
$BODY$
 LANGUAGE 'plpgsql';

SELECT reset_sequence(table_schema, table_name, column_name, table_name || '_' || column_name || '_seq')
FROM information_schema.columns
WHERE column_default LIKE 'nextval%';

DROP FUNCTION reset_sequence(_table_schema text, _tablename text, _columnname text, _sequence_name text) ;
COMMIT;

【讨论】:

感谢您的错误检查!非常感谢表/列名称如果太长会被截断,这是您的 RAISE WARNING 为我识别的。【参考方案11】:

把它们放在一起

CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text) 
RETURNS "pg_catalog"."void" AS
$body$
DECLARE
BEGIN
  EXECUTE 'SELECT setval( pg_get_serial_sequence(''' || tablename || ''', ''id''),
  (SELECT COALESCE(MAX(id)+1,1) FROM ' || tablename || '), false)';
END;
$body$  LANGUAGE 'plpgsql';

将修复给定表的 'id' 序列(例如,django 通常需要)。

【讨论】:

【参考方案12】:

重置所有序列,除了每个表的主键是“id”之外,没有关于名称的假设:

CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text, columnname text)
RETURNS "pg_catalog"."void" AS
$body$
DECLARE
BEGIN
    EXECUTE 'SELECT setval( pg_get_serial_sequence(''' || tablename || ''', ''' || columnname || '''),
    (SELECT COALESCE(MAX(id)+1,1) FROM ' || tablename || '), false)';
END;
$body$  LANGUAGE 'plpgsql';

select table_name || '_' || column_name || '_seq', reset_sequence(table_name, column_name) from information_schema.columns where column_default like 'nextval%';

【讨论】:

在我的 9.1 版本上完美运行 如果表格包含大写,则需要添加引号:pg_get_serial_sequence(''"' || tablename || '"'' 这是最好的功能!您可以使用格式避免引用问题(并提高优雅度),例如EXECUTE format( 'SELECT setval(pg_get_serial_sequence(%L, %L), coalesce(max(id),0) + 1, false) FROM %I;', $1,$2,$1 );【参考方案13】:

使用一些 shell 魔法来修复它,这不是一个很好的解决方案,但可能会激发其他有类似问题的人 :)

pg_dump -s <DATABASE> | grep 'CREATE TABLE' | awk 'print "SELECT setval(#" $3 "_id_seq#, (SELECT MAX(id) FROM " $3 "));"' | sed "s/#/'/g" | psql <DATABASE> -f -

【讨论】:

【参考方案14】:

最短且最快的方式:

SELECT setval('tbl_tbl_id_seq', max(tbl_id)) FROM tbl;

tbl_id 是表tblserial 列,取自序列tbl_tbl_id_seq(这是默认的自动名称)。

如果您不知道附加序列的名称(不一定是默认形式),请使用pg_get_serial_sequence()

SELECT setval(pg_get_serial_sequence('tbl', 'tbl_id'), max(tbl_id)) FROM tbl;

这里没有一个错误。 The manual:

双参数形式将序列的last_value 字段设置为 指定值并将其 is_called 字段设置为 true,这意味着 下一个nextval 将在返回值之前推进序列

我的大胆强调。

如果表格可以为空并且在这种情况下实际上从 1 开始:

SELECT setval(pg_get_serial_sequence('tbl', 'tbl_id')
            , COALESCE(max(tbl_id) + 1, 1)
            , false)
FROM tbl;

我们不能只使用 2 参数形式并以 0 开头,因为序列的下限默认为 1(除非自定义)。

并发

为了防止并发序列活动或在上述查询中写入表,锁定表SHARE 模式。它可以防止并发事务写入更高的数字(或任何东西)。

还考虑到客户端可能已经提前获取了序列号,而主表上没有任何锁,但是(可能在某些设置中发生),只增加序列的当前值,永远不要减少它。这可能看起来很偏执,但这符合序列的性质和防御并发问题。

BEGIN;

LOCK TABLE tbl IN SHARE MODE;

SELECT setval('tbl_tbl_id_seq', max(tbl_id))
FROM   tbl
HAVING max(tbl_id) > (SELECT last_value FROM tbl_tbl_id_seq); -- prevent lower number

COMMIT;

SHARE 模式足够强大。 The manual:

此模式可保护表免受并发数据更改的影响。

它与ROW EXCLUSIVE 模式冲突。

命令UPDATEDELETEINSERT在目标表上获取此锁定模式

【讨论】:

“标准社区基本功能库”在哪里? EXECUTE format()(如@EB.'s)中此答案的第二个选择子句是一个基本功能!如何在 PostgreSQL 中解决这个缺少标准库???? 如果有一个off-by-one,那并不重要。序列中的间隙是正常的。如果您的应用无法应对,那么您的应用就会崩溃,因为事务回滚、意外服务器关闭等也可能会出现间隙。 @Craig:我解决的一个错误(并且不存在)很重要,因为否则我们会冒重复键错误的风险。与您的考虑相反的方向;似乎是个误会。 啊,有道理。 很好的答案!需要注意的是,这些示例令人困惑,因为表名和列名彼此非常相似……这是表“角色”和序列列“id”的更新示例:SELECT setval('roles_id_seq', max(id)) FROM roles;【参考方案15】:

如果您在加载自定义 SQL 数据以进行初始化时看到此错误,另一种避免这种情况的方法是:

而不是写:

INSERT INTO book (id, name, price) VALUES (1 , 'Alchemist' , 10),

从初始数据中删除id(主键)

INSERT INTO book (name, price) VALUES ('Alchemist' , 10),

这使 Postgres 序列保持同步!

【讨论】:

【参考方案16】:

此命令仅用于更改 postgresql 中自动生成的键序列值

ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;

你可以用任何数字代替零来重新开始序列。

默认序列名称为"TableName_FieldName_seq"。例如,如果你的表名是"MyTable",你的字段名是"MyID",那么你的序列名就是"MyTable_MyID_seq"

此答案与@murugesanponappan 的答案相同,但他的解决方案存在语法错误。您不能在alter 命令中使用子查询(select max()...)。所以要么你必须使用固定的数值,要么你需要使用一个变量来代替子查询。

【讨论】:

这是完美的解决方案,非常感谢先生。但就我而言,我遇到了一个错误,所以我不得不将其更改为 ALTER SEQUENCE "your_sequence_name" RESTART WITH 1;【参考方案17】:

这里有一些非常核心的答案,我假设在被问到这个问题的时候它曾经非常糟糕,因为这里的很多答案不适用于 9.3 版。自 8.0 版以来的documentation 为这个问题提供了答案:

SELECT setval('serial', max(id)) FROM distributors;

另外,如果您需要注意区分大小写的序列名称,您可以这样做:

SELECT setval('"Serial"', max(id)) FROM distributors;

【讨论】:

【参考方案18】:

SELECT setval... 使 JDBC 出错,所以这里有一种与 Java 兼容的方法:

-- work around JDBC 'A result was returned when none was expected.'
-- fix broken nextval due to poorly written 20140320100000_CreateAdminUserRoleTables.sql
DO 'BEGIN PERFORM setval(pg_get_serial_sequence(''admin_user_role_groups'', ''id''), 1 + COALESCE(MAX(id), 0), FALSE) FROM admin_user_role_groups; END;';

【讨论】:

【参考方案19】:

重新检查公共模式函数中的所有序列

CREATE OR REPLACE FUNCTION public.recheck_sequence (
)
RETURNS void AS
$body$
DECLARE
  _table_name VARCHAR;
  _column_name VARCHAR;  
  _sequence_name VARCHAR;
BEGIN
  FOR _table_name IN SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'public' LOOP
    FOR _column_name IN SELECT column_name FROM information_schema.columns WHERE table_name = _table_name LOOP
        SELECT pg_get_serial_sequence(_table_name, _column_name) INTO _sequence_name;
        IF _sequence_name IS NOT NULL THEN 
            EXECUTE 'SELECT setval('''||_sequence_name||''', COALESCE((SELECT MAX('||quote_ident(_column_name)||')+1 FROM '||quote_ident(_table_name)||'), 1), FALSE);';
        END IF;
    END LOOP;   
  END LOOP;
END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100;

【讨论】:

【参考方案20】:

这个答案是来自 mauro 的副本。

drop function IF EXISTS rebuilt_sequences() RESTRICT;
CREATE OR REPLACE FUNCTION  rebuilt_sequences() RETURNS integer as
$body$
  DECLARE sequencedefs RECORD; c integer ;
  BEGIN
    FOR sequencedefs IN Select
      DISTINCT(constraint_column_usage.table_name) as tablename,
      constraint_column_usage.column_name as columnname,
      replace(replace(columns.column_default,'''::regclass)',''),'nextval(''','') as sequencename
      from information_schema.constraint_column_usage, information_schema.columns
      where constraint_column_usage.table_schema ='public' AND 
      columns.table_schema = 'public' AND columns.table_name=constraint_column_usage.table_name
      AND constraint_column_usage.column_name = columns.column_name
      AND columns.column_default is not null 
      ORDER BY sequencename
   LOOP    
      EXECUTE 'select max('||sequencedefs.columnname||') from ' || sequencedefs.tablename INTO c;
      IF c is null THEN c = 0; END IF;
      IF c is not null THEN c = c+ 1; END IF;
      EXECUTE 'alter sequence ' || sequencedefs.sequencename ||' minvalue '||c ||' start ' || c ||' restart  with ' || c;
   END LOOP;

   RETURN 1; END;
$body$ LANGUAGE plpgsql;

select rebuilt_sequences();

【讨论】:

这可以作为一个单遍脚本来修复数据库中的所有序列【参考方案21】:

我建议在 postgres wiki 上找到这个解决方案。它会更新表格的所有序列。

SELECT 'SELECT SETVAL(' ||
       quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
       ', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
       quote_ident(PGT.schemaname)|| '.'||quote_ident(T.relname)|| ';'
FROM pg_class AS S,
     pg_depend AS D,
     pg_class AS T,
     pg_attribute AS C,
     pg_tables AS PGT
WHERE S.relkind = 'S'
    AND S.oid = D.objid
    AND D.refobjid = T.oid
    AND D.refobjid = C.attrelid
    AND D.refobjsubid = C.attnum
    AND T.relname = PGT.tablename
ORDER BY S.relname;

如何使用(来自 postgres wiki):

将此保存到文件中,例如“reset.sql” 运行文件并以不包含通常标题的方式保存其输出,然后运行该输出。示例:

例子:

psql -Atq -f reset.sql -o temp
psql -f temp
rm temp

原创文章(也修复了序列所有权)here

【讨论】:

哦,这是一个愚蠢的错误,在我的案例中,数据已迁移到 postgres DB 而不是 sentry。我希望它会帮助别人【参考方案22】:

要将所有序列重新启动为 1,请使用:

-- Create Function
CREATE OR REPLACE FUNCTION "sy_restart_seq_to_1" (
    relname TEXT
)
RETURNS "pg_catalog"."void" AS
$BODY$

DECLARE

BEGIN
    EXECUTE 'ALTER SEQUENCE '||relname||' RESTART WITH 1;';
END;
$BODY$

LANGUAGE 'plpgsql';

-- Use Function
SELECT 
    relname
    ,sy_restart_seq_to_1(relname)
FROM pg_class
WHERE relkind = 'S';

【讨论】:

【参考方案23】:

另一个 plpgsql - 仅在 max(att) &gt; then lastval 时重置

do --check seq not in sync
$$
declare
 _r record;
 _i bigint;
 _m bigint;
begin
  for _r in (
    SELECT relname,nspname,d.refobjid::regclass, a.attname, refobjid
    FROM   pg_depend    d
    JOIN   pg_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid
    JOIN pg_class r on r.oid = objid
    JOIN pg_namespace n on n.oid = relnamespace
    WHERE  d.refobjsubid > 0 and  relkind = 'S'
   ) loop
    execute format('select last_value from %I.%I',_r.nspname,_r.relname) into _i;
    execute format('select max(%I) from %s',_r.attname,_r.refobjid) into _m;
    if coalesce(_m,0) > _i then
      raise info '%',concat('changed: ',_r.nspname,'.',_r.relname,' from:',_i,' to:',_m);
      execute format('alter sequence %I.%I restart with %s',_r.nspname,_r.relname,_m+1);
    end if;
  end loop;

end;
$$
;

还注释--execute format('alter sequence 行将给出列表,而不是实际重置值

【讨论】:

【参考方案24】:

我花了一个小时试图让 djsnowsill 回答使用混合大小写表和列使用数据库,然后由于 Manuel Darveau 的评论终于偶然发现了解决方案,但我认为我可以让每个人都更清楚:

CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text, columnname text)
RETURNS "pg_catalog"."void" AS
$body$
DECLARE
BEGIN
EXECUTE format('SELECT setval(pg_get_serial_sequence(''%1$I'', %2$L),
        (SELECT COALESCE(MAX(%2$I)+1,1) FROM %1$I), false)',tablename,columnname);
END;
$body$  LANGUAGE 'plpgsql';

SELECT format('%s_%s_seq',table_name,column_name), reset_sequence(table_name,column_name) 
FROM information_schema.columns WHERE column_default like 'nextval%';

这样做的好处是:

不假设 ID 列以特定方式拼写。 不假设所有表都有一个序列。 用于混合大小写表/列名称。 使用更简洁的格式。

解释一下,问题在于pg_get_serial_sequence 需要字符串来计算出您所指的内容,所以如果您这样做:

"TableName" --it thinks it's a table or column
'TableName' --it thinks it's a string, but makes it lower case
'"TableName"' --it works!

这是在格式字符串中使用''%1$I'' 来实现的,'' 是撇号,1$ 表示第一个参数,I 表示引号

【讨论】:

【参考方案25】:

我在使用实体框架创建数据库然后使用初始数据为数据库播种时发生此问题,这会导致序列不匹配。

我通过在为数据库播种后创建要运行的脚本解决了这个问题:

DO
$do$
DECLARE tablename text;
BEGIN
    -- change the where statments to include or exclude whatever tables you need
    FOR tablename IN SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE' AND table_name != '__EFMigrationsHistory'
        LOOP
            EXECUTE format('SELECT setval(pg_get_serial_sequence(''"%s"'', ''Id''), (SELECT MAX("Id") + 1 from "%s"))', tablename, tablename);
    END LOOP;
END
$do$

【讨论】:

为什么 MAX("Id") + 1 在序列 = 最大时最适合我。 哪里需要运行这个脚本?我的意思是 pgAdmin 或命令行? 在 Talend 作业成功后如何运行此脚本?【参考方案26】:
select 'SELECT SETVAL(' || seq [ 1] || ', COALESCE(MAX('||column_name||')+1, 1) ) FROM '||table_name||';'
from (
       SELECT table_name, column_name, column_default, regexp_match(column_default, '''.*''') as seq
       from information_schema.columns
       where column_default ilike 'nextval%'
     ) as sequense_query

【讨论】:

虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。【参考方案27】:

一种更新架构中用作 ID 的所有序列的方法:

DO $$ DECLARE
  r RECORD;
BEGIN
FOR r IN (SELECT tablename, pg_get_serial_sequence(tablename, 'id') as sequencename
          FROM pg_catalog.pg_tables
          WHERE schemaname='YOUR_SCHEMA'
          AND tablename IN (SELECT table_name 
                            FROM information_schema.columns 
                            WHERE table_name=tablename and column_name='id')
          order by tablename)
LOOP
EXECUTE
        'SELECT setval(''' || r.sequencename || ''', COALESCE(MAX(id), 1), MAX(id) IS NOT null)
         FROM ' || r.tablename || ';';
END LOOP;
END $$;

【讨论】:

【参考方案28】:

只需运行以下命令:

SELECT setval('my_table_seq', (SELECT max(id) FROM my_table));

【讨论】:

【参考方案29】:

这里有很多很好的答案。重新加载我的 Django 数据库后,我也有同样的需求。

但我需要:

多合一功能 一次可以修复一个或多个架构 一次可以修复所有或仅修复一张桌子 还想要一个很好的方法来查看到底发生了什么变化,或者没有发生变化

这似乎与最初的要求非常相似。 感谢 Baldiry 和 Mauro 让我走上了正轨。

drop function IF EXISTS reset_sequences(text[], text) RESTRICT;
CREATE OR REPLACE FUNCTION reset_sequences(
    in_schema_name_list text[] = '"django", "dbaas", "metrics", "monitor", "runner", "db_counts"',
    in_table_name text = '%') RETURNS text[] as
$body$
  DECLARE changed_seqs text[];
  DECLARE sequence_defs RECORD; c integer ;
  BEGIN
    FOR sequence_defs IN
        select
          DISTINCT(ccu.table_name) as table_name,
          ccu.column_name as column_name,
          replace(replace(c.column_default,'''::regclass)',''),'nextval(''','') as sequence_name
          from information_schema.constraint_column_usage ccu,
               information_schema.columns c
          where ccu.table_schema = ANY(in_schema_name_list)
            and ccu.table_schema = c.table_schema
            AND c.table_name = ccu.table_name
            and c.table_name like in_table_name
            AND ccu.column_name = c.column_name
            AND c.column_default is not null
          ORDER BY sequence_name
   LOOP
      EXECUTE 'select max(' || sequence_defs.column_name || ') from ' || sequence_defs.table_name INTO c;
      IF c is null THEN c = 1; else c = c + 1; END IF;
      EXECUTE 'alter sequence ' || sequence_defs.sequence_name || ' restart  with ' || c;
      changed_seqs = array_append(changed_seqs, 'alter sequence ' || sequence_defs.sequence_name || ' restart with ' || c);
   END LOOP;
   changed_seqs = array_append(changed_seqs, 'Done');

   RETURN changed_seqs;
END
$body$ LANGUAGE plpgsql;

然后执行并查看更改运行:

select *
from unnest(reset_sequences('"django", "dbaas", "metrics", "monitor", "runner", "db_counts"'));

返回

activity_id_seq                          restart at 22
api_connection_info_id_seq               restart at 4
api_user_id_seq                          restart at 1
application_contact_id_seq               restart at 20

【讨论】:

【参考方案30】:

所以我可以看出这个帖子中没有足够的意见或重新发明的***,所以我决定增加一些趣味。

以下是一个过程:

专注于(仅影响)与表格关联的序列 适用于 SERIAL 和 GENERATED AS IDENTITY 列 适用于 good_column_names 和“BAD_column_123”名称 如果表为空,则自动分配各个序列的定义起始值 允许仅影响特定序列(以 schema.table.column 表示法) 有预览模式
CREATE OR REPLACE PROCEDURE pg_reset_all_table_sequences(
    IN commit_mode BOOLEAN DEFAULT FALSE
,   IN mask_in TEXT DEFAULT NULL
) AS
$$
DECLARE
    sql_reset TEXT;
    each_sec RECORD;
    new_val TEXT;
BEGIN

sql_reset :=
$sql$
SELECT setval(pg_get_serial_sequence('%1$s.%2$s', '%3$s'), coalesce(max("%3$s"), %4$s), false) FROM %1$s.%2$s;
$sql$
;

FOR each_sec IN (

    SELECT
        quote_ident(table_schema) as table_schema
    ,   quote_ident(table_name) as table_name
    ,   column_name
    ,   coalesce(identity_start::INT, seqstart) as min_val
    FROM information_schema.columns
    JOIN pg_sequence ON seqrelid = pg_get_serial_sequence(quote_ident(table_schema)||'.'||quote_ident(table_name) , column_name)::regclass
    WHERE
        (is_identity::boolean OR column_default LIKE 'nextval%') -- catches both SERIAL and IDENTITY sequences

    -- mask on column address (schema.table.column) if supplied
    AND coalesce( table_schema||'.'||table_name||'.'||column_name = mask_in, TRUE )
)
LOOP

IF commit_mode THEN
    EXECUTE format(sql_reset, each_sec.table_schema, each_sec.table_name, each_sec.column_name, each_sec.min_val) INTO new_val;
    RAISE INFO 'Resetting sequence for: %.% (%) to %'
        ,   each_sec.table_schema
        ,   each_sec.table_name
        ,   each_sec.column_name
        ,   new_val
    ;
ELSE
    RAISE INFO 'Sequence found for resetting: %.% (%)'
        ,   each_sec.table_schema
        ,   each_sec.table_name
        ,   each_sec.column_name
    ;
END IF
;

END LOOP;

END
$$
LANGUAGE plpgsql
;

预览:

call pg_reset_all_table_sequences();

提交:

call pg_reset_all_table_sequences(true);

仅指定您的目标表:

call pg_reset_all_table_sequences('schema.table.column');

【讨论】:

以上是关于不同步时如何重置postgres的主键序列?的主要内容,如果未能解决你的问题,请参考以下文章

Postgres:更新所有表的主键序列

在 postgres 中更新具有大量删除的表的主键序列

关于oracle中 根据一个表的主键数据同步更新另一个关联表的字段。

Postgres ON CONFLICT 缺少我声明支持唯一索引的主键冲突

记录一次随意操作数据库,插入新数据,导致与程序添加新数据时,引起的主键值重复问题。More than one row with the given identifier was found: 1690

Postgres 更新不使用主键索引