将'serial'添加到Postgres中的现有列


91

我的Postgres 9.0数据库中有一个小表(约30行),带有一个整数ID字段(主键),该字段当前包含从1开始的唯一连续整数,但不是使用'serial'关键字创建的。

如何更改此表,以便从现在开始对该表进行插入将导致此字段的行为就像它是使用“ serial”作为类型创建的一样?


5
仅供参考,SERIAL伪类型现在是旧式的,被Postgres 10及更高版本中SQL:2003中定义的新GENERATED … AS IDENTITY功能取代。参见说明
罗勒·布尔克

对于现代Postgres版本(> = 10),请参见以下问题:stackoverflow.com/questions/2944499
a_horse_with_no_name

Answers:


132

查看以下命令(尤其是注释的块)。

DROP TABLE foo;
DROP TABLE bar;

CREATE TABLE foo (a int, b text);
CREATE TABLE bar (a serial, b text);

INSERT INTO foo (a, b) SELECT i, 'foo ' || i::text FROM generate_series(1, 5) i;
INSERT INTO bar (b) SELECT 'bar ' || i::text FROM generate_series(1, 5) i;

-- blocks of commands to turn foo into bar
CREATE SEQUENCE foo_a_seq;
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq');
ALTER TABLE foo ALTER COLUMN a SET NOT NULL;
ALTER SEQUENCE foo_a_seq OWNED BY foo.a;    -- 8.2 or later

SELECT MAX(a) FROM foo;
SELECT setval('foo_a_seq', 5);  -- replace 5 by SELECT MAX result

INSERT INTO foo (b) VALUES('teste');
INSERT INTO bar (b) VALUES('teste');

SELECT * FROM foo;
SELECT * FROM bar;

由于您在OP中提到了主键,因此您可能还需要ALTER TABLE foo ADD PRIMARY KEY (a)
Skippy le Grand Gourou 2014年

SERIAL是语法糖,不会存储在数据库元数据中,因此上面的代码将100%等价。
DKroot

如果目标表有可能是由其他用户创建的,则需要ALTER TABLE foo OWNER TO current_user;首先进行操作。
DKroot

2
您不应该设置MAX(a)+1setval吗?SELECT MAX(a)+1 FROM foo; SELECT setval('foo_a_seq', 6);
SunnyPro

48

您也可以使用START WITH从特定点开始序列,尽管setval可以完成与Euler的回答相同的事情,例如,

SELECT MAX(a) + 1 FROM foo;
CREATE SEQUENCE foo_a_seq START WITH 12345; -- replace 12345 with max above
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq');

28

TL; DR

这是一个不需要人工读取值并自行输入值的版本。

CREATE SEQUENCE foo_a_seq OWNED BY foo.a;
SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo;
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq'); 

另一个选择是Function在此答案的末尾使用可重用的共享。


非交互式解决方案

只需添加其他两个答案,对于那些需要Sequence通过非交互式脚本创建这些的人,例如在修补一个实时数据库时。

也就是说,当您不想SELECT手动输入该值并将其自己键入到后续CREATE语句中时。

简而言之,您不能执行以下操作:

CREATE SEQUENCE foo_a_seq
    START WITH ( SELECT max(a) + 1 FROM foo );

...由于START [WITH]in中的子句CREATE SEQUENCE需要一个,而不是子查询。

注:作为一个经验法则,适用于所有非CRUD(:比其他任何东西INSERTSELECTUPDATEDELETE在报表)pgSQL的 AFAIK。

但是,setval()确实如此!因此,以下绝对正确:

SELECT setval('foo_a_seq', max(a)) FROM foo;

如果没有数据并且您不知道(想要),请使用coalesce()设置默认值:

SELECT setval('foo_a_seq', coalesce(max(a), 0)) FROM foo;
--                         ^      ^         ^
--                       defaults to:       0

但是,0如果不是非法的话,将当前序列值设置为笨拙。
使用的三参数形式setval会更合适:

--                                             vvv
SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo;
--                                                  ^   ^
--                                                is_called

将可选的第三个参数设置为setvalto false将防止next nextval在返回值之前推进序列,因此:

下一个nextval将精确返回指定的值,并且序列前进从以下开始nextval

—从文档中的此项开始

在不相关的注释上,您还可以Sequence直接通过来指定拥有的列CREATE,而不必在以后进行更改:

CREATE SEQUENCE foo_a_seq OWNED BY foo.a;

综上所述:

CREATE SEQUENCE foo_a_seq OWNED BY foo.a;
SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo;
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq'); 

用一个 Function

另外,如果您打算对多个列执行此操作,则可以选择使用real Function

CREATE OR REPLACE FUNCTION make_into_serial(table_name TEXT, column_name TEXT) RETURNS INTEGER AS $$
DECLARE
    start_with INTEGER;
    sequence_name TEXT;
BEGIN
    sequence_name := table_name || '_' || column_name || '_seq';
    EXECUTE 'SELECT coalesce(max(' || column_name || '), 0) + 1 FROM ' || table_name
            INTO start_with;
    EXECUTE 'CREATE SEQUENCE ' || sequence_name ||
            ' START WITH ' || start_with ||
            ' OWNED BY ' || table_name || '.' || column_name;
    EXECUTE 'ALTER TABLE ' || table_name || ' ALTER COLUMN ' || column_name ||
            ' SET DEFAULT nextVal(''' || sequence_name || ''')';
    RETURN start_with;
END;
$$ LANGUAGE plpgsql VOLATILE;

像这样使用它:

INSERT INTO foo (data) VALUES ('asdf');
-- ERROR: null value in column "a" violates not-null constraint

SELECT make_into_serial('foo', 'a');
INSERT INTO foo (data) VALUES ('asdf');
-- OK: 1 row(s) affected

好的答案,但是请记住,coalesce(max(a), 0))大部分时间都不会起作用,因为Ids通常从1开始。更正确的方法是coalesce(max(a), 1))
Amiko

1
感谢@Amiko的评论!该setval函数实际上只为序列设置当前的“最新使用值”。下一个可用值(第一个实际使用的值)将增加一个!使用setval(..., coalesce(max(a), 1))空柱将其设置为“开始”与2(下一个可用值),如所示的文件
ccjmne

1
@Amiko你说的对,但是我的代码有问题:currval绝对不能0,即使它不会反映在实际数据集中。使用的三参数形式setval会更合适:setval(..., coalesce(max(a), 0) + 1, false)。答案已相应更新!
ccjmne,

1
同意,我完全错过了。感谢您的回答,节省了我的时间。
阿美子
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.