我遇到了一个问题,我的主键序列与我的表行不同步。
也就是说,当我插入一个新行时,我得到一个重复的键错误,因为在串行数据类型中隐含的序列返回一个已经存在的数字。
这似乎是由于导入/恢复没有正确地维护序列造成的。
我遇到了一个问题,我的主键序列与我的表行不同步。
也就是说,当我插入一个新行时,我得到一个重复的键错误,因为在串行数据类型中隐含的序列返回一个已经存在的数字。
这似乎是由于导入/恢复没有正确地维护序列造成的。
当前回答
当序列名、列名、表名或模式名中有空格、标点符号等有趣的字符时,这些函数充满了危险。我这样写:
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在目标表上获得这种锁定模式。
试着重建索引。
更新:正如评论中指出的,这是对最初问题的回答。
所以我可以告诉你,在这个帖子里没有足够的意见或重新发明的轮子,所以我决定让事情变得有趣。
下面是一个程序:
关注(仅影响)与表相关的序列 适用于串行和生成的AS身份列 适用于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
;
预览:
调用pg_reset_all_table_sequences ();
提交:
调用pg_reset_all_table_sequences(真正的);
只指定你的目标表:
调用pg_reset_all_table_sequences(“schema.table.column”);
此命令仅用于更改postgresql中自动生成的键序列值
ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;
在零的地方,你可以放任何你想重新启动序列的数字。
默认序列名为“TableName_FieldName_seq”。例如,如果您的表名是“MyTable”,字段名是“MyID”,那么序列名将是“MyTable_MyID_seq”。
这个答案与@murugesanponappan的答案相同,但在他的解决方案中有一个语法错误。在alter命令中不能使用sub query (select max()…)所以要么你必须使用固定的数值,要么你需要使用变量来代替子查询。
重置所有序列,除了每个表的主键是“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%';