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

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

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


当前回答

克劳斯的答案是最有用的,除了有一点遗漏:你 必须在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”。

其他回答

试着重建索引。

更新:正如评论中指出的,这是对最初问题的回答。

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

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,只能重置某个模式中的序列,等等。例如,如果你想调整"public"模式中的序列:

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)

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

最短最快的路

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

tbl_id是表tbl的串行列或IDENTITY列,取自序列tbl_tbl_id_seq(生成的默认名称)。看到的:

自动递增表列

如果你不知道附加序列的名称(不一定是默认形式),使用pg_get_serial_sequence()(也适用于IDENTITY):

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

这里没有差1的误差。手册:

双参数形式将序列的last_value字段设置为 值,并将其is_called字段设置为true Next 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模式足够强大。手册:

这种模式保护表不受并发数据更改的影响。

与ROW EXCLUSIVE模式冲突。

命令UPDATE、DELETE和INSERT在目标表上获得这种锁定模式。

SELECT setval('sequencename', COALESCE((SELECT MAX(id)+1 FROM tablename), 1), false);

把它们放在一起

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中是必要的)。