我遇到了我的主键序列与我的表行不同步的问题.
也就是说,当我插入一个新行时,我得到一个重复的键错误,因为串行数据类型中隐含的序列返回一个已经存在的数字.
这似乎是由导入/恢复不能正确维护序列引起的.
-- 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;
但是这个表单无法正确处理空表,因为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;
在最短,最快的方法:
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;
这将重置所有公共序列,不对表名或列名进行假设.在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;
此命令仅用于更改postgresql中自动生成的键序列值
ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;
代替零,您可以放置要重新启动序列的任何数字.
默认序列名称将"TableName_FieldName_seq"
.例如,如果您的表名是,"MyTable"
并且您的字段名称是"MyID"
,那么您的序列名称将是"MyTable_MyID_seq"
.
答案与@ murugesanponappan的答案相同,但他的解决方案中存在语法错误.你不能(select max()...)
在alter
命令中使用子查询.因此,您必须使用固定数值,或者需要使用变量代替子查询.
重置所有序列,没有关于名称的假设,除了每个表的主键是"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%';
当序列名,列名,表名或模式名具有空格,标点符号等有趣字符时,这些函数充满了危险.我写了这个:
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)
一旦修复了该函数,该函数就可以正常运行此表以及可能使用相同序列的所有其他函数.
从公共重置所有序列
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';
另一个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
给出列表,而不是实际重置该值
我的版本使用第一个,有一些错误检查...
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;
这里有一些非常硬核的答案,我假设在这个问题的时候它曾经非常糟糕,因为这里的很多答案都不适用于9.3版本.自8.0版以来的文档提供了这个问题的答案:
SELECT setval('serial', max(id)) FROM distributors;
此外,如果您需要处理区分大小写的序列名称,那么您就是这样做的:
SELECT setval('"Serial"', max(id)) FROM distributors;
我建议在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
原始文章(也有序列所有权的修复)在这里
使用实体框架创建数据库,然后使用初始数据为数据库播种时,会发生此问题,这会使序列不匹配。
我通过创建在种子数据库后运行的脚本来解决该问题:
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$
把它们放在一起
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).