การเพิ่ม 'serial' ในคอลัมน์ที่มีอยู่ใน Postgres


98

ฉันมีตารางขนาดเล็ก (~ 30 แถว) ในฐานข้อมูล Postgres 9.0 ของฉันพร้อมฟิลด์ ID จำนวนเต็ม (คีย์หลัก) ซึ่งปัจจุบันมีจำนวนเต็มตามลำดับที่ไม่ซ้ำกันเริ่มต้นที่ 1 แต่ไม่ได้สร้างโดยใช้คีย์เวิร์ด 'อนุกรม'

ฉันจะแก้ไขตารางนี้ได้อย่างไรที่นับจากนี้ไปแทรกในตารางนี้จะทำให้ฟิลด์นี้ทำงานราวกับว่ามันถูกสร้างขึ้นโดยมี 'อนุกรม' เป็นประเภท


5
FYI SERIALขณะนี้ประเภทหลอกเป็นแบบดั้งเดิมแทนที่ด้วยGENERATED … AS IDENTITYคุณลักษณะใหม่ที่กำหนดในSQL: 2003ใน Postgres 10 และใหม่กว่า ดูคำอธิบาย
Basil Bourque

สำหรับรุ่น Postgres สมัยใหม่ (> = 10) โปรดดูคำถามนี้: stackoverflow.com/questions/2944499
a_horse_with_no_name

คำตอบ:


135

ดูคำสั่งต่อไปนี้ (โดยเฉพาะบล็อกที่แสดงความคิดเห็น)

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

SERIAL คือน้ำตาลในรูปแบบวากยสัมพันธ์และไม่ได้เก็บไว้ในข้อมูลเมตาของ DB ดังนั้นโค้ดด้านบนจะเทียบเท่า 100%
DKroot

หากมีโอกาสที่ตารางเป้าหมายถูกสร้างขึ้นโดยผู้ใช้รายอื่นคุณจะต้องดำเนินการALTER TABLE foo OWNER TO current_user;ก่อน
DKroot

2
คุณไม่ควรตั้งค่าMAX(a)+1ใน setval? SELECT MAX(a)+1 FROM foo; SELECT setval('foo_a_seq', 6);
SunnyPro

50

คุณยังสามารถใช้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');

31

TL; ดร

นี่คือเวอร์ชันที่คุณไม่จำเป็นต้องใช้มนุษย์ในการอ่านค่าและพิมพ์ออกมาเอง

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]อนุประโยคCREATE SEQUENCEคาดว่าค่าไม่ใช่แบบสอบถามย่อย

หมายเหตุ: ตามกฎของหัวแม่มือที่นำไปใช้กับทุกคนที่ไม่ใช่ CRUD ( เช่น : อื่นใดนอกเหนือจากINSERT, SELECT, UPDATE, DELETE) งบใน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จะป้องกันไม่ให้ตัวเลือกถัดไปnextvalเลื่อนลำดับก่อนที่จะส่งคืนค่าดังนั้น:

ถัดไปnextvalจะกลับมาว่าค่าที่ระบุและ Commences 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

หรือหากคุณกำลังวางแผนที่จะทำสิ่งนี้สำหรับหลายคอลัมน์คุณสามารถเลือกใช้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))จะไม่ทำงานมากที่สุดของเวลาตั้งแต่หมายเลขมักจะเริ่มต้นจาก 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
เห็นด้วยฉันพลาดสิ่งนั้นโดยสิ้นเชิง ขอบคุณสำหรับคำตอบที่ช่วยประหยัดเวลาของฉัน
Amiko
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.