ฟังก์ชัน PostgreSQL สำหรับ ID ที่แทรกล่าสุด


321

ใน PostgreSQL ฉันจะแทรก id ล่าสุดลงในตารางได้อย่างไร

ใน MS SQL มี SCOPE_IDENTITY ()

โปรดอย่าแนะนำให้ฉันใช้สิ่งนี้:

select max(id) from table

ทำไมคุณถึงเกลียดฟังก์ชั่นสูงสุด? ฉันคิดว่ามันง่ายมาก มีปัญหาเช่นความปลอดภัยหรือไม่?
jeongmin.cha

9
@ jeongmin.cha มีปัญหาหากในระหว่างที่มีการดำเนินงานอื่น ๆ และการแทรกเพิ่มเติม (การดำเนินงานพร้อมกัน) หมายถึงการเปลี่ยนรหัสสูงสุดเว้นแต่และจนกว่าคุณจะใช้ล็อคอย่างชัดเจนและไม่ปล่อยมัน
rohanagarwal

หากกรณีการใช้งานที่ตั้งใจไว้คือการใช้ ID ที่แทรกล่าสุดเป็นส่วนหนึ่งของค่าของการแทรกที่ตามมาดูคำถามนี้
ฟลักซ์

คำตอบ:


606

( tl;dr: ตัวเลือก goto 3: INSERT พร้อมการส่งคืน)

จำได้ว่าใน postgresql ไม่มีแนวคิด "id" สำหรับตารางเพียงแค่ซีเควนซ์ (ซึ่งโดยทั่วไปแล้ว แต่ไม่จำเป็นต้องใช้เป็นค่าเริ่มต้นสำหรับคีย์หลักตัวแทนพร้อมกับประเภทหลอกอนุกรม )

หากคุณสนใจรับ id ของแถวที่เพิ่งแทรกใหม่มีหลายวิธี:


ตัวเลือกที่ CURRVAL(<sequence name>);1:

ตัวอย่างเช่น:

  INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
  SELECT currval('persons_id_seq');

ต้องทราบชื่อของลำดับนั้นโดยพลการจริงๆ ในตัวอย่างนี้เราสมมติว่าตารางpersonsมีidคอลัมน์ที่สร้างขึ้นด้วยSERIALประเภทหลอก เพื่อหลีกเลี่ยงการพึ่งพาสิ่งนี้และรู้สึกสะอาดมากขึ้นคุณสามารถใช้แทนpg_get_serial_sequence:

  INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
  SELECT currval(pg_get_serial_sequence('persons','id'));

ข้อแม้: currval()ทำงานเฉพาะหลังจากที่INSERT(ซึ่งได้ดำเนินการnextval()) ในช่วงเดียวกัน


ตัวเลือก 2: LASTVAL();

นี้คล้ายกับก่อนหน้านี้เฉพาะที่คุณไม่จำเป็นต้องระบุชื่อลำดับ: มันมองหาลำดับที่แก้ไขล่าสุด (มักจะอยู่ในเซสชั่นของคุณเสมอข้อแม้เหมือนข้างต้น)


ทั้งปลอดภัยCURRVALและLASTVALพร้อมกันทั้งหมด พฤติกรรมของลำดับใน PG ได้รับการออกแบบเพื่อให้เซสชันที่แตกต่างกันจะไม่รบกวนดังนั้นจึงไม่มีความเสี่ยงของสภาพการแข่งขัน (ถ้าเซสชันอื่นแทรกแถวอื่นระหว่าง INSERT และ SELECT ของฉันฉันยังคงได้รับค่าที่ถูกต้อง)

อย่างไรก็ตามพวกเขามีปัญหาที่อาจเกิดขึ้นได้ หากฐานข้อมูลมีTRIGGER (หรือ RULE) ที่แทรกลงในpersonsตารางทำให้มีการแทรกพิเศษในตารางอื่น ๆ ... จากนั้นLASTVALอาจจะให้ค่าที่ไม่ถูกต้องกับเรา ปัญหาอาจเกิดขึ้นได้CURRVALหากการแทรกพิเศษเสร็จสิ้นลงในpersonsตารางเดียวกัน(นี่เป็นเรื่องปกติน้อยกว่ามาก แต่ยังมีความเสี่ยงอยู่)


ตัวเลือก 3: INSERTด้วยRETURNING

INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John') RETURNING id;

นี่เป็นวิธีที่สะอาดมีประสิทธิภาพและปลอดภัยที่สุดในการรับรหัส มันไม่มีความเสี่ยงใด ๆ จากครั้งก่อน

ข้อเสีย? เกือบไม่มี: คุณอาจต้องแก้ไขวิธีที่คุณเรียกใช้คำสั่ง INSERT ของคุณ (ในกรณีที่เลวร้ายที่สุดบางทีเลเยอร์ API หรือ DB ของคุณอาจไม่คาดว่า INSERT จะส่งกลับค่า) ไม่ใช่ SQL มาตรฐาน (ใครสนใจ); มีให้ตั้งแต่ Postgresql 8.2 (ธ.ค. 2549 ... )


บทสรุป: ถ้าคุณสามารถไปได้สำหรับตัวเลือก 3 ที่อื่นชอบ 1

หมายเหตุ: วิธีการทั้งหมดนี้ไม่มีประโยชน์หากคุณต้องการรับรหัสที่แทรกล่าสุดทั่วโลก (ไม่จำเป็นต้องใช้ในเซสชันของคุณ) สำหรับสิ่งนี้คุณต้องหันไปใช้SELECT max(id) FROM table(แน่นอนสิ่งนี้จะไม่อ่านส่วนแทรกที่ไม่ได้รับการยอมรับจากการทำธุรกรรมอื่น ๆ )

ในทางกลับกันคุณไม่ควรใช้SELECT max(id) FROM tableตัวเลือกใดตัวเลือกหนึ่งในสามตัวเลือกข้างต้นเพื่อให้ได้รหัสที่เพิ่งสร้างขึ้นโดยINSERTคำสั่งของคุณเพราะ (นอกเหนือจากประสิทธิภาพ) สิ่งนี้ไม่ปลอดภัยพร้อมกัน: ระหว่างINSERTคุณSELECTกับเซสชันอื่นของคุณ


25
LASTVAL () อาจชั่วร้ายมากในกรณีที่คุณเพิ่มทริกเกอร์ / กฎการแทรกแถวในตัวเองลงในตารางอื่น
Kouber Saparev

3
SELECT max(id)ขออภัยที่ไม่ได้ทำงานทันทีที่คุณเริ่มลบแถว
Simon A. Eugster

2
@ leonbloy เว้นแต่ฉันจะพลาดบางสิ่งบางอย่างถ้าคุณมีแถวที่มี ID 1,2,3,4,5และลบแถวที่ 4 และ 5 ID ที่แทรกล่าสุดยังคงเป็น 5 แต่max()จะส่งคืน 3
Simon A. Eugster

9
ตัวอย่างของวิธีใช้ RETURNING id;ในการแทรกสิ่งนี้ในตารางอื่นจะได้รับการต้อนรับ!
Olivier Pons

8
ฉันจะใช้RETURNING idภายในสคริปต์ SQL ที่ส่งไปยังpsqlเครื่องมือบรรทัดคำสั่งได้อย่างไร
อะมี

82

ดูส่วนคำสั่งการส่งคืนของคำสั่งINSERT โดยพื้นฐานแล้ว INSERT จะเพิ่มการสืบค้นเป็นสองเท่าและให้คุณค่าที่แทรกกลับมา


4
ใช้งานได้กับเวอร์ชั่น 8.2 และเป็นทางออกที่ดีที่สุดและเร็วที่สุด
Frank Heikens

9
อาจเป็นคำอธิบายอย่างย่อเกี่ยวกับวิธีใช้ id ที่ส่งคืนหรือไม่
แอนดรู

@ ดึงฉันไม่แน่ใจว่าฉันเข้าใจคำถาม คุณไม่รู้วิธีดึงผลลัพธ์จากแบบสอบถามหรือไม่ นั่นขึ้นอยู่กับภาษา / ไลบรารีและควรทำงานเหมือนกันโดยไม่คำนึงว่าคุณกำลังทำการเลือกหรือการแทรกกลับ การตีความอื่น ๆ ที่ฉันสามารถทำได้คือคุณได้รับ ID จากการโทรสำเร็จแล้วและไม่รู้ว่ามันมีไว้เพื่ออะไร ... ในกรณีนี้ทำไมคุณถึงดึงข้อมูลกลับมา?
kwatford

8
@Andrew, ถ้าคุณเรียกใช้จากบรรทัดคำสั่ง psqlสิ่งนี้: insert into names (firstname, lastname) values ('john', 'smith') returning id;, มันจะแสดงผล id เพียงแค่คุณรันselect id from names where id=$lastidโดยตรง หากคุณต้องการบันทึกการคืนค่าลงในตัวแปร aaดังนั้นinsert into names (firstname, lastname) values ('john', 'smith') returning id into other_variable;หากคำสั่งที่มีการส่งคืนเป็นคำสั่งสุดท้ายในฟังก์ชั่นดังนั้น id ที่เป็นreturning'ed จะถูกส่งกลับโดยฟังก์ชันโดยรวม
Alexander Bird

32

คุณสามารถใช้คำสั่ง RETURNING ในคำสั่ง INSERT เหมือนดังต่อไปนี้

wgzhao=# create table foo(id int,name text);
CREATE TABLE
wgzhao=# insert into foo values(1,'wgzhao') returning id;
 id 
----
  1
(1 row)

INSERT 0 1
wgzhao=# insert into foo values(3,'wgzhao') returning id;
 id 
----
  3
(1 row)

INSERT 0 1

wgzhao=# create table bar(id serial,name text);
CREATE TABLE
wgzhao=# insert into bar(name) values('wgzhao') returning id;
 id 
----
  1
(1 row)

INSERT 0 1
wgzhao=# insert into bar(name) values('wgzhao') returning id;
 id 
----
  2
(1 row)

INSERT 0 

28

คำตอบของ Leonbloyค่อนข้างสมบูรณ์ ฉันจะเพิ่มเฉพาะกรณีพิเศษที่หนึ่งต้องได้รับมูลค่าแทรกล่าสุดจากภายในฟังก์ชั่น PL / pgSQL ที่ OPTION 3 ไม่พอดี

ตัวอย่างเช่นถ้าเรามีตารางต่อไปนี้:

CREATE TABLE person(
   id serial,
   lastname character varying (50),
   firstname character varying (50),
   CONSTRAINT person_pk PRIMARY KEY (id)
);

CREATE TABLE client (
    id integer,
   CONSTRAINT client_pk PRIMARY KEY (id),
   CONSTRAINT fk_client_person FOREIGN KEY (id)
       REFERENCES person (id) MATCH SIMPLE
);

หากเราจำเป็นต้องแทรกบันทึกลูกค้าเราจะต้องอ้างถึงบันทึกบุคคล แต่สมมติว่าเราต้องการประดิษฐ์ฟังก์ชัน PL / pgSQL ที่แทรกเร็กคอร์ดใหม่ลงในไคลเอนต์ แต่ยังดูแลการแทรกเร็กคอร์ดบุคคลใหม่ ด้วยเหตุนี้เราต้องใช้รูปแบบที่แตกต่างเล็กน้อยของตัวเลือกของ leonbloy 3:

INSERT INTO person(lastname, firstname) 
VALUES (lastn, firstn) 
RETURNING id INTO [new_variable];

โปรดทราบว่ามีสองข้อเป็น ดังนั้นฟังก์ชั่น PL / pgSQL จะถูกกำหนดเช่น:

CREATE OR REPLACE FUNCTION new_client(lastn character varying, firstn character varying)
  RETURNS integer AS
$BODY$
DECLARE
   v_id integer;
BEGIN
   -- Inserts the new person record and retrieves the last inserted id
   INSERT INTO person(lastname, firstname)
   VALUES (lastn, firstn)
   RETURNING id INTO v_id;

   -- Inserts the new client and references the inserted person
   INSERT INTO client(id) VALUES (v_id);

   -- Return the new id so we can use it in a select clause or return the new id into the user application
    RETURN v_id;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE;

ตอนนี้เราสามารถแทรกข้อมูลใหม่โดยใช้:

SELECT new_client('Smith', 'John');

หรือ

SELECT * FROM new_client('Smith', 'John');

และเราได้รับ ID ที่สร้างขึ้นใหม่

new_client
integer
----------
         1

9

สำหรับผู้ที่ต้องการบันทึกข้อมูลทั้งหมดคุณสามารถเพิ่ม

returning *

ท้ายแบบสอบถามของคุณเพื่อรับวัตถุทั้งหมดรวมถึงรหัส


8

ดูตัวอย่างด้านล่าง

CREATE TABLE users (
    -- make the "id" column a primary key; this also creates
    -- a UNIQUE constraint and a b+-tree index on the column
    id    SERIAL PRIMARY KEY,
    name  TEXT,
    age   INT4
);

INSERT INTO users (name, age) VALUES ('Mozart', 20);

จากนั้นสำหรับการแทรก id ล่าสุดใช้สำหรับตาราง "ผู้ใช้" ชื่อคอลัมน์คอลัมน์ "id"

SELECT currval(pg_get_serial_sequence('users', 'id'));


1

ลองสิ่งนี้:

select nextval('my_seq_name');  // Returns next value

หากสิ่งนี้คืนค่า 1 (หรืออะไรก็ตามที่เป็น start_value สำหรับลำดับของคุณ) ให้รีเซ็ตลำดับกลับเป็นค่าดั้งเดิมผ่านค่าสถานะเท็จ:

select setval('my_seq_name', 1, false);

มิฉะนั้น,

select setval('my_seq_name', nextValue - 1, true);

สิ่งนี้จะคืนค่าลำดับไปสู่สถานะเดิมและ "setval" จะกลับมาพร้อมกับค่าลำดับที่คุณกำลังมองหา


1

Postgres มีกลไกแบบ inbuilt เหมือนกันซึ่งในแบบสอบถามเดียวกันส่งคืนรหัสหรือสิ่งที่คุณต้องการให้แบบสอบถามส่งคืน นี่คือตัวอย่าง พิจารณาว่าคุณมีตารางที่มีคอลัมน์ 2 คอลัมน์ 1 และคอลัมน์ 2 และคุณต้องการให้คอลัมน์ 1 ถูกส่งคืนหลังจากแทรกทุกครั้ง

# create table users_table(id serial not null primary key, name character varying);
CREATE TABLE
#insert into users_table(name) VALUES ('Jon Snow') RETURNING id;
 id 
----
  1
(1 row)

# insert into users_table(name) VALUES ('Arya Stark') RETURNING id;
 id 
----
  2
(1 row)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.