如何重置postgres的主键序列,当它不同步?

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

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

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

-- 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论坛

可以使用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; 

然而,这种forms不能正确处理空表,因为max(id)是null,你也不能设置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的用途有限,因为序列名称和重启值不能是expression式。

看来最好的解决scheme是使用false作为第三个参数来调用setval ,从而允许我们指定“下一个使用的值”:

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

这将打勾我的所有箱子:

  1. 避免了硬编码实际的序列名称
  2. 正确处理空表
  3. 处理具有现有数据的表格,并且不会在序列中留下空洞

最后,请注意, pg_get_serial_sequence只有在序列属于列的情况下才有效。 如果递增列被定义为serialtypes,则会出现这种情况,但是如果手动添加序列,则需要确保ALTER SEQUENCE .. OWNED BY也被执行。

即如果serialtypes用于表创build,这应该都工作:

 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; 

这将重置来自公众的所有序列,不对表或列名称做出任何假设。 testing版本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%'; 

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; 

最短和最快的方式:

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

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

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

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

这里没有错误的错误。 每个文档:

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

大胆重视我的。

重置所有序列,除了每个表的主键都是“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%'; 

这个命令只能在postgresql中改变自动生成的键序列值

 ALTER SEQUENCE "your_sequence_name" RESTART WITH 0; 

代替零,你可以把你想要重新启动顺序的任何数字。

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

这与@ murugesanponappan的答案是一样的,但是在他的解决scheme中有一个语法错误。 你不能在alter命令中使用子查询(select max()...) 。 所以,要么你必须使用固定的数值,或者你需要使用一个variables来代替子查询。

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

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

使用不同的质量,只能重置特定模式中的顺序,等等。 例如,如果您想调整“公共”模式中的序列:

 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) 

请注意,该default子句中的nextval()调用除了:: regclass cast之外还有一个:: 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) 

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

重置公开的所有序列

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

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

 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; 

在我还没有尝试过的代码之前:在下面我发布的Klaus和user457226解决scheme的工作在我的电脑[Postgres 8.3]的SQL代码的版本,只有一些Klaus一个和我的版本的一些调整为user457226之一。

克劳斯解决scheme:

 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解决scheme:

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

把它放在一起

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

我build议在postgres wiki上find这个解决scheme。 它更新你的表的所有序列。

 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 

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

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

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

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

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

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

 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; 

要重新启动所有序列为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'; 

克劳斯的答案是最有用的,可能会遗漏一点:你必须在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解决scheme的一个扩展,用于某些感兴趣的列名不是“ID”的情况。

丑陋的黑客修复它使用一些壳魔法,不是一个很好的解决scheme,但可能会激发其他类似的问题:)

 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 - 

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

而不是写作:

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

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

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

这使得Postgres序列保持同步!

尝试重新索引 。

更新:正如评论中指出的那样,这是对原来的问题的答复。

SELECT setval...使JDBC bork,所以这是一个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;'; 

这个答案是从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(); 

我花了一个小时的时间试图让djsnowsill的答案与使用Mixed Case表和列的数据库一起工作,然后由于Manuel Darveau的评论而终于偶然发现了这个解决scheme,但是我想我可以让每个人都更清楚一点:

 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需要string来解决你指的是什么,所以如果你这样做:

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

这是通过在格式string中使用''%1$I''来实现的, ''使得撇号1$表示第一个参数,而I意思是用引号

又一个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将给列表,而不是实际重置值