运行了很长时间后,我在id字段中出现了越来越多的漏洞。一些表的id是int32,id序列达到了它的最大值。某些Java源代码是只读的,因此我不能简单地将id列类型从int32
更改为long
,这会破坏API。
我想重新编号。这可能不是一个好的做法,但好或坏并不关心这个问题。我想重新编号,特别是那些很长的ID,如“61789238”,“548273826529524324”。我不知道它们为何如此长,但更短的ID也更容易手动处理。
但由于引用和约束,手动压缩ID并不容易。
PostgreSQL本身是否支持ID重新编号?或者是否有任何插件或维护工具?
也许我可以写一些存储过程?这将非常好,所以我可以每年安排一次。
答案 0 :(得分:10)
假设您的ID是从bignum
序列生成的,只需RESTART
序列,并使用idcolumn = DEFAULT
更新表格。
CAVEAT :如果此id
列被其他表用作外键,请确保已启用on update cascade
修饰符。
例如:
创建表格,放入一些数据,然后删除中间值:
db=# create sequence xseq;
CREATE SEQUENCE
db=# create table foo ( id bigint default nextval('xseq') not null, data text );
CREATE TABLE
db=# insert into foo (data) values ('hello'), ('world'), ('how'), ('are'), ('you');
INSERT 0 5
db=# delete from foo where data = 'how';
DELETE 1
db=# select * from foo;
id | data
----+-------
1 | hello
2 | world
4 | are
5 | you
(4 rows)
重置序列:
db=# ALTER SEQUENCE xseq RESTART;
ALTER SEQUENCE
更新您的数据:
db=# update foo set id = DEFAULT;
UPDATE 4
db=# select * from foo;
id | data
----+-------
1 | hello
2 | world
3 | are
4 | you
(4 rows)
答案 1 :(得分:10)
这个问题已经过时了,但是在尝试应用此处建议的内容后,我们从dba.SE上的绝望用户那里得到了一个新问题。通过更多详情和解释:
找到答案currently accepted answer 在大多数情况下都会失败。
通常,PRIMARY KEY
列上有UNIQUE
或id
约束,默认情况下为NOT DEFERRABLE
。 (OP提及references and constraints
。)在每行之后检查此类约束,因此您很可能会尝试唯一违规错误。详细说明:
通常,人们希望在缩小差距时保留原来的行数。但行更新的顺序是任意,导致任意数字。演示的示例似乎保留了原始序列,因为物理存储仍然与所需的顺序一致(在前一刻按所需顺序插入行),这在现实世界的应用程序中几乎不是这种情况,并且完全不可靠。
事情比起初看起来更复杂。 一个 解决方案(以及其他)如果您能够负担得起暂时删除PK / UNIQUE约束(以及相关的FK约束):
BEGIN;
LOCK tbl;
-- remove all FK constraints to the column
ALTER TABLE tbl DROP CONSTRAINT tbl_pkey; -- remove PK
-- for the simple case without FK references - or see below:
UPDATE tbl t -- intermediate unique violations are ignored now
SET id = t1.new_id
FROM (SELECT id, row_number() OVER (ORDER BY id) AS new_id FROM tbl) t1
WHERE t.id = t1.id;
-- Update referencing value in FK columns at the same time (if any)
SELECT setval('tbl_id_seq', max(id)) FROM tbl; -- reset sequence
ALTER TABLE tbl ADD CONSTRAINT tbl_pkey PRIMARY KEY(id); -- add PK back
-- add all FK constraints to the column back
COMMIT;
对于大表,这也多更快,因为检查每行的PK(和FK)约束比删除约束并添加它们(它们)要花费更多回来。
如果引用tbl.id
的其他表中有FK列,请使用data-modifying CTEs更新所有列。
表fk_tbl
和FK列fk_id
的示例:
WITH u1 AS (
UPDATE tbl t
SET id = t1.new_id
FROM (SELECT id, row_number() OVER (ORDER BY id) AS new_id FROM tbl) t1
WHERE t.id = t1.id
RETURNING t.id, t1.new_id -- return old and new ID
)
UPDATE fk_tbl f
SET fk_id = u1.new_id -- set to new ID
FROM u1
WHERE f.fk_id = u1.id; -- match on old ID
referenced answer on dba.SE中的更多内容。
答案 2 :(得分:2)
新的id列和外键,而旧的列仍在使用中。通过一些(快速)重命名,应用程序无需了解。 (但应用程序在最终重命名步骤中应处于非活动状态)
\i tmp.sql
-- the test tables
CREATE TABLE one (
id serial NOT NULL PRIMARY KEY
, payload text
);
CREATE TABLE two (
id serial NOT NULL PRIMARY KEY
, the_fk INTEGER REFERENCES one(id)
ON UPDATE CASCADE ON DELETE CASCADE
);
-- And the supporting index for the FK ...
CREATE INDEX ON two(the_fk);
-- populate
INSERT INTO one(payload)
SELECT x::text FROM generate_series(1,1000) x;
INSERT INTO two(the_fk)
SELECT id FROM one WHERE random() < 0.3;
-- make some gaps
DELETE FROM one WHERE id % 13 > 0;
-- SELECT * FROM two;
-- Add new keycolumns to one and two
ALTER TABLE one
ADD COLUMN new_id SERIAL NOT NULL UNIQUE
;
-- UPDATE:
-- This could need DEFERRABLE
-- Note since the update is only a permutation of the
-- existing values, we dont need to reset the sequence.
UPDATE one SET new_id = self.new_id
FROM ( SELECT id, row_number() OVER(ORDER BY id) AS new_id FROM one ) self
WHERE one.id = self.id;
ALTER TABLE two
ADD COLUMN new_fk INTEGER REFERENCES one(new_id)
;
-- update the new FK
UPDATE two t
SET new_fk = o.new_id
FROM one o
WHERE t.the_fk = o.id
;
SELECT * FROM two;
-- The crucial part: the final renaming
-- (at this point it would be better not to allow other sessions
-- messing with the {one,two} tables ...
-- --------------------------------------------------------------
ALTER TABLE one DROP COLUMN id CASCADE;
ALTER TABLE one rename COLUMN new_id TO id;
ALTER TABLE one ADD PRIMARY KEY(id);
ALTER TABLE two DROP COLUMN the_fk CASCADE;
ALTER TABLE two rename COLUMN new_fk TO the_fk;
CREATE INDEX ON two(the_fk);
-- Some checks.
-- (the automatically generated names for the indexes
-- and the sequence still contain the "new" names.)
SELECT * FROM two;
\d one
\d two
更新:添加new_id的排列(在创建为序列后) 有趣的是:它似乎不需要'DEFERRABLE'。
答案 3 :(得分:1)
*此脚本适用于postgresql
这是适用于所有情况的通用解决方案
此查询查找来自任何数据库的所有表的字段的说明。
WITH description_bd AS (select colum.schemaname,coalesce(table_name,relname) as table_name , column_name, ordinal_position, column_default, data_type, is_nullable, character_maximum_length, is_updatable,description from
( SELECT columns.table_schema as schemaname,columns.table_name, columns.column_name, columns.ordinal_position, columns.column_default, columns.data_type, columns.is_nullable, columns.character_maximum_length, columns.character_octet_length, columns.is_updatable, columns.udt_name
FROM information_schema.columns
) colum
full join (SELECT schemaname, relid, relname,objoid, objsubid, description
FROM pg_statio_all_tables ,pg_description where pg_statio_all_tables.relid= pg_description.objoid ) descre
on descre.relname = colum.table_name and descre.objsubid=colum.ordinal_position and descre.schemaname=colum.schemaname )
此查询提出了一个解决方案来修复所有数据库表的序列(这会在req字段中生成一个查询,用于修复不同表的序列)。
找到表的记录数,然后将该数字增加一个。
SELECT table_name, column_name, ordinal_position,column_default,
data_type, is_nullable, character_maximum_length, is_updatable,
description,'SELECT setval('''||schemaname||'.'|| replace(replace(column_default,'''::regclass)',''),'nextval(''','')||''', (select max( '||column_name ||')+1 from '|| table_name ||' ), true);' as req
FROM description_bd where column_default like '%nextva%'
答案 4 :(得分:1)
由于我不喜欢这些答案,因此我在PL / pgSQL中编写了一个函数来完成这项工作。 这样称呼:
=> SELECT resequence('port','id','port_id_seq');
resequence
--------------
5090 -> 3919
采用3个参数
该函数返回已完成操作的简短报告,其中包含序列的先前值和新值。
该函数在由命名列排序的表上遍历,并为每一行进行UPDATE。然后为序列设置新值。就是这样。
代码:
CREATE OR REPLACE FUNCTION resequence(_tbl TEXT, _clm TEXT, _seq TEXT) RETURNS TEXT AS $FUNC$
DECLARE
_old BIGINT;_new BIGINT := 0;
BEGIN
FOR _old IN EXECUTE 'SELECT '||_clm||' FROM '||_tbl||' ORDER BY '||_clm LOOP
_new=_new+1;
EXECUTE 'UPDATE '||_tbl||' SET '||_clm||'='||_new||' WHERE '||_clm||'='||_old;
END LOOP;
RETURN (nextval(_seq::regclass)-1)||' -> '||setval(_seq::regclass,_new);
END $FUNC$ LANGUAGE plpgsql;