Thêm 'nối tiếp' vào cột hiện có trong Postgres


91

Tôi có một bảng nhỏ (~ 30 hàng) trong cơ sở dữ liệu Postgres 9.0 của mình với trường ID số nguyên (khóa chính) hiện chứa các số nguyên tuần tự duy nhất bắt đầu từ 1, nhưng không được tạo bằng từ khóa 'nối tiếp'.

Làm cách nào tôi có thể thay đổi bảng này sao cho từ bây giờ việc chèn vào bảng này sẽ khiến trường này hoạt động như thể nó đã được tạo với kiểu 'nối tiếp'?


5
FYI, kiểu SERIALgiả hiện là kế thừa , được thay thế bằng GENERATED … AS IDENTITYtính năng mới được xác định trong SQL: 2003 , trong Postgres 10 trở lên. Xem giải thích .
Basil Bourque vào

Đối với phiên bản Postgres hiện đại (> = 10), hãy xem câu hỏi này: stackoverflow.com/questions/2944499
a_horse_with_no_name

Câu trả lời:


132

Nhìn vào các lệnh sau (đặc biệt là khối đã nhận xét).

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;

Vì bạn đang đề cập đến các khóa chính trong OP của mình, nên bạn cũng có thể muốn ALTER TABLE foo ADD PRIMARY KEY (a).
Skippy le Grand Gourou

SERIAL là đường cú pháp và không được lưu trữ trong siêu dữ liệu DB, vì vậy đoạn mã trên sẽ tương đương 100%.
DKroot

Nếu có khả năng bảng mục tiêu được tạo bởi một người dùng khác, bạn cần phải làm ALTER TABLE foo OWNER TO current_user;trước.
DKroot

2
Bạn không nên thiết lập MAX(a)+1trong setval? SELECT MAX(a)+1 FROM foo; SELECT setval('foo_a_seq', 6);
SunnyPro

48

Bạn cũng có thể sử dụng START WITHđể bắt đầu một chuỗi từ một điểm cụ thể, mặc dù setval hoàn thành điều tương tự, như trong câu trả lời của Euler, ví dụ:

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

Đây là một phiên bản mà bạn không cần con người đọc giá trị và tự đánh ra giá trị đó.

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

Một lựa chọn khác là sử dụng phần có thể tái sử dụng Functionđược chia sẻ ở cuối câu trả lời này.


Một giải pháp không tương tác

Chỉ thêm vào hai câu trả lời còn lại, đối với những người trong chúng ta, những người cần Sequencetạo các câu trả lời này bằng một tập lệnh không tương tác , trong khi vá một DB trực tiếp chẳng hạn.

Đó là, khi bạn không muốn SELECTgiá trị theo cách thủ công và tự nhập giá trị đó vào một CREATEcâu lệnh tiếp theo .

Trong ngắn hạn, bạn không thể làm:

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

... vì START [WITH]mệnh đề trong CREATE SEQUENCEmong đợi một giá trị , không phải một truy vấn con.

Lưu ý: Theo nguyên tắc của ngón tay cái, áp dụng cho tất cả các phi CRUD ( ví dụ : bất cứ điều gì khác hơn INSERT, SELECT, UPDATE, DELETE) báo cáo trong pgSQL AFAIK.

Tuy nhiên, setval()không! Do đó, những điều sau đây hoàn toàn ổn:

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

Nếu không có dữ liệu và bạn không (muốn) biết về nó, hãy sử dụng coalesce()để đặt giá trị mặc định:

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

Tuy nhiên, việc đặt giá trị trình tự hiện tại 0là khá vụng về, nếu không muốn nói là bất hợp pháp.
Sử dụng dạng ba tham số của setvalsẽ thích hợp hơn:

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

Việc đặt tham số thứ ba tùy chọn của setvalto falsesẽ ngăn tham số tiếp theo nextvaltiến trình tự trước khi trả về giá trị và do đó:

nextvalgiá trị tiếp theo sẽ trả về chính xác giá trị được chỉ định và tiến trình trình tự bắt đầu với giá trị sau nextval.

- từ mục này trong tài liệu

Trên một ghi chú không liên quan, bạn cũng có thể chỉ định cột sở hữu Sequencetrực tiếp với CREATE, bạn không phải thay đổi nó sau này:

CREATE SEQUENCE foo_a_seq OWNED BY foo.a;

Tóm tắt:

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

Sử dụng một Function

Ngoài ra, nếu bạn định làm điều này cho nhiều cột, bạn có thể chọn sử dụng một cột thực 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;

Sử dụng nó như vậy:

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

Great câu trả lời, nhưng phải ghi nhớ coalesce(max(a), 0))sẽ không làm việc hầu hết thời gian, vì Id thường bắt đầu từ 1. cách More đúng sẽ làcoalesce(max(a), 1))
Amiko

1
Cảm ơn @Amiko đã nhận xét! Các setvalchức năng thực sự chỉ đặt ra "giá trị sử dụng mới nhất" hiện tại cho chuỗi. Giá trị có sẵn tiếp theo (giá trị đầu tiên được sử dụng thực sự) sẽ là một giá trị nữa! Sử dụng setval(..., coalesce(max(a), 1))trên một cột trống sẽ thiết lập cột đó thành "bắt đầu" với 2(giá trị có sẵn tiếp theo), như được minh họa trong tài liệu .
ccjmne

1
@Amiko Bạn nói đúng khi nói rằng có vấn đề trong mã của tôi: điều currvalkhông nên xảy ra 0, ngay cả khi nó không được phản ánh trong tập dữ liệu thực tế. Sử dụng hình thức ba tham số của setvalsẽ thích hợp hơn: setval(..., coalesce(max(a), 0) + 1, false). Câu trả lời được cập nhật cho phù hợp!
ccjmne

1
Đồng ý, tôi hoàn toàn bỏ lỡ điều đó. Cảm ơn câu trả lời đã tiết kiệm thời gian của tôi.
Amiko
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.