当前位置:  开发笔记 > 后端 > 正文

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

如何解决《如何在postgres不同步时重置postgres的主键序列?》经验,为你挑选了15个好方法。

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

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

这似乎是由导入/恢复不能正确维护序列引起的.



1> meleyal..:
-- 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;

来源 - Ruby论坛


所有问题都解决并组合成一个查询:`SELECT setval('your_seq',(SELECT GREATEST(MAX(your_id)+ 1,nextval('your_seq')) - 1 FROM your_table))`
无论如何,将MAX(id)加1会在ID中留下一个数字间隙,因为setval集是序列的最后一个值,而不是下一个值.
@Valery:但是为了避免@mikl上面提到的两个注释,你需要`SELECT setval('your_table_id_seq',coalesce((从your_table中选择max(id)+1),1),false);`
如果您的应用程序关注序列中的间隙,那么您的应用程序就会被破坏 序列中的间隙是正常的,并且可能由于计划外数据库关闭,错误后的事务回滚等原因而发生.
如果表中没有行,则示例将不起作用.所以下面给出的SQL更安全:SELECT setval('your_table_id_seq',coalesce((从your_table中选择max(id)+1),1),true);
差距实际上有什么不同吗?
@Frunsi提交的查询进行了一些调整:`SELECT setval(pg_get_serial_sequence('table_name','id'),COALESCE(MAX(id),1),MAX(id)不为null)FROM table_name`(注意使用引号)

2> tardate..:

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)为null,并且你也不能setval 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作为第3个参数调用,允许我们指定"下一个要使用的值":

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.

3> Erwin Brands..:

最短,最快的方法:

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

tbl_id作为serial表的列tbl,从序列中绘制tbl_tbl_id_seq(这是默认的自动名称).

如果您不知道所附序列的名称(不必是默认格式),请使用pg_get_serial_sequence():

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

这里没有一个错误的错误.每个文件:

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

大胆强调我的.

并发

但是,在上述查询中,并没有防止并发序列活动或写入表.如果这是相关的,您可以将表锁定为独占模式.当您尝试同步时,它可以防止并发事务写入更高的数字.(它还会暂时阻止无害的写入而不会弄乱最大数量.)

但它没有考虑到客户端可能提前获取序列号而没有主表上的任何锁定(但可能发生).为了实现这一点,也只增加序列的当前值,而不是减少它.它可能看起来很偏执,但这符合序列的本质和防范并发问题.

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


第二个查询对我来说就像一个魅力!

4> djsnowsill..:

这将重置所有公共序列,不对表名或列名进行假设.在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%';


请注意,这将失败,序列名称包含单引号,或者名称中包含大写字母,空格等的表名称.应该真正使用`quote_literal`和`quote_ident`函数,或者最好是`format`函数.

5> 小智..:

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);

6> Haider Ali W..:

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

ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;

代替零,您可以放置​​要重新启动序列的任何数字.

默认序列名称将"TableName_FieldName_seq".例如,如果您的表名是,"MyTable"并且您的字段名称是"MyID",那么您的序列名称将是"MyTable_MyID_seq".

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



7> EB...:

重置所有序列,没有关于名称的假设,除了每个表的主键是"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%';



8> alvherre..:

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

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)

请注意,除了:: regclass强制转换之外,该default子句中的nextval()调用还有一个:: text强制转换.我认为这是因为数据库是旧的PostgreSQL版本的pg_dump.会发生的是上面的函数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)

一旦修复了该函数,该函数就可以正常运行此表以及可能使用相同序列的所有其他函数.



9> 小智..:

从公共重置所有序列

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';



10> Vao Tsun..:

另一个plpgsql - 仅在重置时重置 max(att) > 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给出列表,而不是实际重置该值



11> 小智..:

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

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;



12> Ian Bytchek..:

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

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

此外,如果您需要处理区分大小写的序列名称,那么您就是这样做的:

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



13> Pietro..:

我建议在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

原始文章(也有序列所有权的修复)在这里



14> 小智..:

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

我通过创建在种子数据库后运行的脚本来解决该问题:

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$



15> Antony Hatch..:

把它们放在一起

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).

推荐阅读
我我檬檬我我186
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有