在 Postgres 中的现有列中添加“串行”

Posted

技术标签:

【中文标题】在 Postgres 中的现有列中添加“串行”【英文标题】:Adding 'serial' to existing column in Postgres 【发布时间】:2012-03-18 09:37:59 【问题描述】:

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

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

【问题讨论】:

仅供参考,SERIAL 伪类型现在是 legacy,在 Postgres 10 及更高版本中被 SQL:2003 中的新 GENERATED … AS IDENTITY 功能 defined 取代。见explanation。 对于现代 Postgres 版本 (>= 10),请参阅此问题:***.com/questions/2944499 【参考方案1】:

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

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) SERIAL 是语法糖,不存储在数据库元数据中,所以上面的代码是 100% 等效的。 如果目标表可能是由其他用户创建的,您需要先执行ALTER TABLE foo OWNER TO current_user; 你不应该在 setval 中设置MAX(a)+1 吗? SELECT MAX(a)+1 FROM foo; SELECT setval('foo_a_seq', 6);【参考方案2】:

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

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

【讨论】:

【参考方案3】:

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


非交互式解决方案

只是添加到其他两个答案,对于我们这些需要通过非交互式脚本创建这些Sequences,同时修补实时数据库的人。

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

简而言之,您可以这样做:

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

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

注意:根据经验,这适用于所有非 CRUD(ie:除INSERTSELECTUPDATEDELETE)之外的任何语句strong>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

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

下一个nextval 将准确返回指定的值,序列推进从以下nextval 开始。

——来自this entry in the documentation

在不相关的注释中,您也可以直接使用CREATE 指定拥有Sequence 的列,以后不必更改它:

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

或者,如果您打算为多个列执行此操作,您可以选择使用实际的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)) 在大多数情况下都不起作用,因为 ID 通常从 1 开始。更正确的方法是 coalesce(max(a), 1)) 感谢@Amiko 的评论! setval 函数实际上只设置序列的当前“最新使用值”。下一个 available 值(第一个实际使用的值)将是另外一个!在空列上使用setval(..., coalesce(max(a), 1)) 会将其设置为以2(下一个可用值)“开始”,如the documentation 中所示。 @Amiko 你说得对,不过我的代码有问题:currval 永远不应该是0,即使它不会反映在实际数据集中。使用setval 的三参数形式会更合适:setval(..., coalesce(max(a), 0) + 1, false)。答案已相应更新! 同意,我完全错过了。感谢您的回答节省了我的时间。【参考方案4】:

PostgreSQL 12为我工作。

它将我现有的 int 列转换为串行列。

CREATE SEQUENCE table_name_id_seq;
ALTER TABLE table_name ALTER COLUMN id SET DEFAULT nextval('table_name_id_seq');
ALTER TABLE table_name ALTER COLUMN id SET NOT NULL;
ALTER SEQUENCE table_name_id_seq OWNED BY table_name.id;  
SELECT setval('table_name_id_seq', (SELECT max(id) FROM table_name));

【讨论】:

这将导致永无止境的执行

以上是关于在 Postgres 中的现有列中添加“串行”的主要内容,如果未能解决你的问题,请参考以下文章

将 :default => true 添加到现有 Rails 列中的布尔值

Laravel:添加到列中的现有字段数据

下一个键/值对在尝试使用新键添加对时覆盖散列中的现有对

Postgres - 将列中的各种名称替换为相应的唯一标识符

如何使用 CSVHelper 更新现有 CSV 文件中特定列中的值?

通过使用php将表单中的值添加到表列中的现有值来更新数据库中的值的问题