Chức năng PostgreSQL cho ID được chèn cuối cùng


321

Trong PostgreSQL, làm cách nào để lấy id cuối cùng được chèn vào bảng?

Trong MS SQL có SCOPE_IDENTITY ().

Xin đừng khuyên tôi sử dụng một cái gì đó như thế này:

select max(id) from table

Tại sao bạn ghét chức năng tối đa? Tôi nghĩ nó rất đơn giản. Có bất kỳ vấn đề như bảo mật?
jeongmin.cha

9
@ jeongmin.cha có vấn đề nếu ở giữa có các hoạt động khác và thêm phần bổ sung (hoạt động đồng thời), có nghĩa là max id đã thay đổi, trừ khi và cho đến khi bạn khóa một cách rõ ràng và không phát hành nó
rohanagarwal

Nếu trường hợp sử dụng dự định là sử dụng ID được chèn cuối cùng như một phần của giá trị của lần chèn tiếp theo, hãy xem câu hỏi này .
Flux

Câu trả lời:


606

( tl;dr: tùy chọn goto 3: XÁC NHẬN với RETURNING)

Hãy nhớ lại rằng trong postgresql không có khái niệm "id" cho các bảng, chỉ là các chuỗi (thường không nhất thiết được sử dụng làm giá trị mặc định cho các khóa chính thay thế, với kiểu giả SERIAL ).

Nếu bạn quan tâm đến việc lấy id của một hàng mới được chèn, có một số cách:


Tùy chọn 1 : CURRVAL(<sequence name>);.

Ví dụ:

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

Tên của chuỗi phải được biết, nó thực sự tùy ý; trong ví dụ này, chúng tôi giả sử rằng bảng personscó một idcột được tạo bằng kiểu SERIALgiả. Để tránh phụ thuộc vào điều này và cảm thấy sạch sẽ hơn, bạn có thể sử dụng thay thế pg_get_serial_sequence:

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

Hãy cẩn thận: currval()chỉ hoạt động sau khi một INSERT(đã thực hiện nextval()), trong cùng một phiên .


Lựa chọn 2: LASTVAL();

Điều này tương tự như trước đây, chỉ là bạn không cần chỉ định tên trình tự: nó tìm chuỗi được sửa đổi gần đây nhất (luôn bên trong phiên của bạn, cùng cảnh báo như trên).


Cả hai CURRVALLASTVALhoàn toàn đồng thời an toàn. Hành vi của chuỗi trong PG được thiết kế sao cho các phiên khác nhau sẽ không can thiệp, do đó không có rủi ro về điều kiện chủng tộc (nếu một phiên khác chèn một hàng khác giữa INSERT và CHỌN của tôi, tôi vẫn nhận được giá trị chính xác của mình).

Tuy nhiên, họ có một vấn đề tiềm năng tinh tế. Nếu cơ sở dữ liệu có một số TRIGGER (hoặc RULE) mà khi chèn vào personsbảng sẽ tạo thêm một số phần bổ sung trong các bảng khác ... thì LASTVALcó thể sẽ cung cấp cho chúng ta giá trị sai. Vấn đề thậm chí có thể xảy ra với CURRVAL, nếu việc chèn thêm được thực hiện trong cùng một personsbảng (điều này ít thông thường hơn, nhưng rủi ro vẫn tồn tại).


Tùy chọn 3: INSERTvớiRETURNING

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

Đây là cách sạch nhất, hiệu quả và an toàn để lấy id. Nó không có bất kỳ rủi ro nào trước đó.

Hạn chế? Hầu như không có: bạn có thể cần phải sửa đổi cách bạn gọi câu lệnh INSERT của mình (trong trường hợp xấu nhất, có lẽ lớp API hoặc DB của bạn không mong đợi INSERT trả về giá trị); đó không phải là SQL chuẩn (ai quan tâm); nó có sẵn kể từ Postgresql 8.2 (tháng 12 năm 2006 ...)


Kết luận: Nếu bạn có thể, hãy chọn tùy chọn 3. Ở nơi khác, thích 1.

Lưu ý: tất cả các phương thức này đều vô dụng nếu bạn dự định lấy id được chèn cuối cùng trên toàn cầu (không nhất thiết phải theo phiên của bạn). Đối với điều này, bạn phải dùng đến SELECT max(id) FROM table(tất nhiên, điều này sẽ không đọc các phần chèn không được cam kết từ các giao dịch khác).

Ngược lại, bạn không bao giờ nên sử dụng SELECT max(id) FROM tablethay vào một trong 3 tùy chọn ở trên, để lấy id vừa được tạo bởi INSERTcâu lệnh của bạn , bởi vì (ngoài hiệu suất), điều này không an toàn đồng thời: giữa bạn INSERTSELECTphiên khác của bạn có thể đã chèn một bản ghi khác.


25
LASTVAL () có thể rất xấu, trong trường hợp bạn thêm một trình kích hoạt / quy tắc chèn các hàng trên chính nó vào một bảng khác.
Kouber Saparev

3
SELECT max(id)tiếc là không thực hiện công việc ngay khi bạn bắt đầu xóa hàng.
Simon A. Eugster

2
@leonbloy Trừ khi tôi bỏ lỡ điều gì đó, nếu bạn có hàng có ID 1,2,3,4,5và xóa hàng 4 và 5, ID được chèn cuối cùng vẫn là 5, nhưng max()trả về 3.
Simon A. Eugster

9
Một mẫu về cách sử dụng RETURNING id;để chèn cái này vào một bảng khác sẽ được hoan nghênh!
Olivier Pons

8
Làm cách nào tôi có thể sử dụng RETURNING idtrong tập lệnh SQL được cung cấp cho psqlcông cụ dòng lệnh?
amoe

82

Xem mệnh đề RETURNING của câu lệnh INSERT . Về cơ bản, INSERT nhân đôi như một truy vấn và trả lại cho bạn giá trị được chèn.


4
Hoạt động như phiên bản 8.2 và là giải pháp tốt nhất và nhanh nhất.
Frank Heikens

9
có thể giải thích nhanh về cách sử dụng id trả về?
Andrew

@Andrew Tôi không chắc là tôi hiểu câu hỏi. Bạn không biết làm thế nào để lấy kết quả từ một truy vấn? Đó là ngôn ngữ / thư viện phụ thuộc và sẽ hoạt động như nhau bất kể bạn đang thực hiện lựa chọn hay chèn trở lại. Cách giải thích duy nhất khác mà tôi có thể đưa ra là bạn đã lấy thành công ID từ cuộc gọi và không biết nó dùng để làm gì ... trong trường hợp nào, tại sao bạn lại lấy nó?
kwatford

8
@Andrew, Nếu bạn chạy từ dòng lệnh psql này: insert into names (firstname, lastname) values ('john', 'smith') returning id;thì nó chỉ đơn giản xuất ra id giống như khi bạn chạy select id from names where id=$lastidtrực tiếp. Nếu bạn muốn lưu trả về vào một biến aa , thì insert into names (firstname, lastname) values ('john', 'smith') returning id into other_variable;Nếu câu lệnh chứa trả về là câu lệnh cuối cùng trong một hàm , thì toàn bộ id đang returningđược trả về bởi toàn bộ hàm.
Alexander Bird

32

bạn có thể sử dụng mệnh đề RETURNING trong câu lệnh INSERT, giống như sau

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

Câu trả lời của Leonbloy khá đầy đủ. Tôi sẽ chỉ thêm trường hợp đặc biệt trong đó người ta cần lấy giá trị được chèn cuối cùng từ bên trong hàm PL / pgQuery trong đó TÙY CHỌN 3 không khớp chính xác.

Ví dụ: nếu chúng ta có các bảng sau:

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

Nếu chúng ta cần chèn một bản ghi khách hàng, chúng ta phải tham khảo một bản ghi cá nhân. Nhưng giả sử chúng ta muốn tạo ra một hàm PL / pgSQL chèn một bản ghi mới vào máy khách nhưng cũng đảm nhiệm việc chèn bản ghi người mới. Để làm được điều đó, chúng ta phải sử dụng một biến thể nhỏ của TÙY CHỌN 3 của leonbloy:

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

Lưu ý rằng có hai mệnh đề INTO. Do đó, hàm PL / pgSQL sẽ được định nghĩa như sau:

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;

Bây giờ chúng ta có thể chèn dữ liệu mới bằng cách sử dụng:

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

hoặc là

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

Và chúng tôi nhận được id mới được tạo.

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

9

Đối với những người cần lấy bản ghi dữ liệu, bạn có thể thêm

returning *

đến cuối truy vấn của bạn để lấy tất cả các đối tượng bao gồm cả id.


8

Xem ví dụ dưới đây

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

Sau đó, để nhận id được chèn lần cuối, hãy sử dụng cái này cho bảng "người dùng" tên cột seq "id"

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


1

Thử cái này:

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

Nếu giá trị này trả về 1 (hoặc bất cứ điều gì là start_value cho chuỗi của bạn), thì hãy đặt lại chuỗi đó về giá trị ban đầu, chuyển cờ sai:

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

Nếu không thì,

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

Điều này sẽ khôi phục giá trị chuỗi về trạng thái ban đầu và "setval" sẽ trả về với giá trị chuỗi bạn đang tìm kiếm.


1

Postgres có một cơ chế sẵn có cho cùng, trong cùng một truy vấn sẽ trả về id hoặc bất cứ điều gì bạn muốn truy vấn trả về. đây là một ví dụ. Hãy xem xét bạn có một bảng được tạo có 2 cột cột1 và cột2 và bạn muốn cột 1 được trả về sau mỗi lần chèn.

# 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)

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.