Fungsi PostgreSQL untuk ID yang dimasukkan terakhir

321

Di PostgreSQL, bagaimana cara saya mendapatkan id terakhir dimasukkan ke dalam tabel?

Dalam MS SQL ada SCOPE_IDENTITY ().

Tolong jangan menyarankan saya untuk menggunakan sesuatu seperti ini:

select max(id) from table
Anton
sumber
mengapa Anda membenci fungsi maks? Saya pikir ini sangat sederhana. Apakah ada masalah seperti keamanan?
jeongmin.cha
9
@ jeongmin.cha ada masalah jika di antara ada operasi lain, dan lebih banyak penyisipan (operasi bersamaan), berarti maks id telah berubah, kecuali dan sampai Anda mengambil kunci secara eksplisit dan jangan melepaskannya
rohanagarwal
Jika use case yang dimaksud adalah menggunakan ID yang dimasukkan terakhir sebagai bagian dari nilai insert berikutnya, lihat pertanyaan ini .
Fluks

Jawaban:

606

( tl;dr : opsi goto 3: INSERT dengan RETURNING)

Ingatlah bahwa dalam postgresql tidak ada konsep "id" untuk tabel, hanya urutan (yang biasanya tetapi tidak selalu digunakan sebagai nilai default untuk kunci utama pengganti, dengan SERIAL pseudo-type).

Jika Anda tertarik untuk mendapatkan id dari baris yang baru dimasukkan, ada beberapa cara:


Pilihan 1: CURRVAL(<sequence name>); .

Sebagai contoh:

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

Nama urutan harus diketahui, itu benar-benar sewenang-wenang; dalam contoh ini kita mengasumsikan bahwa tabel personsmemiliki idkolom yang dibuat dengan tipe SERIALpseudo. Untuk menghindari mengandalkan ini dan merasa lebih bersih, Anda bisa menggunakan pg_get_serial_sequence:

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

Peringatan: currval()hanya berfungsi setelah INSERT(yang telah dieksekusi nextval()), di sesi yang sama .


Pilihan 2: LASTVAL();

Ini mirip dengan yang sebelumnya, hanya saja Anda tidak perlu menentukan nama urutan: mencari urutan modifikasi terbaru (selalu di dalam sesi Anda, peringatan yang sama seperti di atas).


Keduanya CURRVALdanLASTVAL aman secara bersamaan. Perilaku urutan dalam PG dirancang sehingga sesi yang berbeda tidak akan mengganggu, sehingga tidak ada risiko kondisi balapan (jika sesi lain menyisipkan baris lain antara INSERT dan SELECT saya, saya masih mendapatkan nilai yang benar).

Namun mereka memiliki masalah potensial yang tidak kentara. Jika database memiliki beberapa TRIGGER (atau ATURAN) yang, pada saat dimasukkan ke dalam personstabel, membuat beberapa tambahan di tabel lain ... maka LASTVALmungkin akan memberi kita nilai yang salah. Masalahnya bahkan dapat terjadi dengan CURRVAL, jika penyisipan tambahan dilakukan ke personstabel yang sama (ini jauh lebih jarang, tetapi risikonya masih ada).


Opsi 3: INSERTdenganRETURNING

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

Ini adalah cara yang paling bersih, efisien dan aman untuk mendapatkan id. Itu tidak memiliki risiko seperti sebelumnya.

Kekurangannya? Hampir tidak ada: Anda mungkin perlu memodifikasi cara Anda memanggil pernyataan INSERT Anda (dalam kasus terburuk, mungkin API atau lapisan DB Anda tidak mengharapkan INSERT untuk mengembalikan nilai); itu bukan SQL standar (siapa yang peduli); ini tersedia sejak Postgresql 8.2 (Des 2006 ...)


Kesimpulan: Jika Anda bisa, pilih opsi 3. Di tempat lain, pilih 1.

Catatan: semua metode ini tidak berguna jika Anda bermaksud mendapatkan id yang dimasukkan terakhir secara global (tidak harus oleh sesi Anda). Untuk ini, Anda harus menggunakan SELECT max(id) FROM table(tentu saja, ini tidak akan membaca sisipan tanpa komitmen dari transaksi lain).

Sebaliknya, Anda tidak boleh menggunakan SELECT max(id) FROM tablesalah satu dari 3 opsi di atas, untuk mendapatkan id yang baru saja dihasilkan oleh INSERTpernyataan Anda , karena (terlepas dari kinerja) ini tidak bersamaan aman: antara Anda INSERTdan AndaSELECT sesi lain mungkin telah memasukkan catatan lain.

leonbloy
sumber
25
LASTVAL () mungkin sangat jahat, jika Anda menambahkan pemicu / aturan memasukkan baris pada dirinya sendiri ke tabel lain.
Kouber Saparev
3
SELECT max(id)sayangnya tidak melakukan pekerjaan itu segera setelah Anda mulai menghapus baris.
Simon A. Eugster
2
@leonbloy Kecuali saya melewatkan sesuatu, jika Anda memiliki baris dengan ID 1,2,3,4,5dan menghapus baris 4 dan 5, ID yang dimasukkan terakhir masih 5, tetapi max()mengembalikan 3.
Simon A. Eugster
9
Contoh cara menggunakan RETURNING id;untuk memasukkan ini ke tabel lain akan diterima!
Olivier Pons
8
Bagaimana saya bisa menggunakan RETURNING idskrip SQL yang diumpankan ke psqlalat baris perintah?
amoe
82

Lihat klausa KEMBALI dari pernyataan INSERT . Pada dasarnya, INSERT berfungsi ganda sebagai kueri dan mengembalikan nilai yang dimasukkan.

kwatford
sumber
4
Bekerja pada versi 8.2 dan merupakan solusi terbaik dan tercepat.
Frank Heikens
9
mungkin penjelasan singkat tentang bagaimana menggunakan kata dikembalikan id?
Andrew
@Andrew Saya tidak yakin saya mengerti pertanyaannya. Apakah Anda tidak tahu cara mengambil hasil dari kueri? Itu tergantung pada bahasa / pustaka dan harus berfungsi sama terlepas dari apakah Anda melakukan seleksi atau memasukkan kembali. Satu-satunya interpretasi lain yang dapat saya buat adalah bahwa Anda telah berhasil mengambil ID dari panggilan dan tidak tahu untuk apa ... dalam hal ini, mengapa Anda mengambilnya?
kwatford
8
@ Andrew, Jika Anda menjalankan dari baris perintah psql ini: insert into names (firstname, lastname) values ('john', 'smith') returning id;, maka itu hanya output id sama seperti jika Anda berlari select id from names where id=$lastidlangsung. Jika Anda ingin menyimpan pengembalian ke variabel aa , maka insert into names (firstname, lastname) values ('john', 'smith') returning id into other_variable;Jika pernyataan yang berisi pengembalian adalah pernyataan terakhir dalam suatu fungsi , maka id yang sedang returningdikembalikan oleh fungsi secara keseluruhan.
Alexander Bird
32

Anda dapat menggunakan klausa RETURNING dalam pernyataan INSERT, seperti berikut ini

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 
wgzhao
sumber
28

Jawaban Leonbloy cukup lengkap. Saya hanya akan menambahkan kasus khusus di mana orang perlu mendapatkan nilai yang dimasukkan terakhir dari dalam fungsi PL / pgSQL di mana OPSI 3 tidak cocok persis.

Misalnya, jika kita memiliki tabel berikut:

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

Jika kita perlu memasukkan catatan klien, kita harus merujuk ke catatan orang. Tetapi katakanlah kita ingin merancang fungsi PL / pgSQL yang menyisipkan catatan baru ke klien tetapi juga menangani memasukkan catatan orang baru. Untuk itu, kita harus menggunakan sedikit variasi dari OPSI leonbloy's 3:

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

Perhatikan bahwa ada dua klausa INTO. Oleh karena itu, fungsi PL / pgSQL akan didefinisikan seperti:

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;

Sekarang kita bisa memasukkan data baru menggunakan:

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

atau

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

Dan kami mendapatkan id yang baru dibuat.

new_client
integer
----------
         1
Krauss
sumber
9

Untuk yang perlu mendapatkan semua catatan data, Anda dapat menambahkan

returning *

ke akhir kueri Anda untuk mendapatkan semua objek termasuk id.

emreoktem
sumber
8

Lihat contoh di bawah ini

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

Kemudian untuk mendapatkan id yang dimasukkan terakhir gunakan ini untuk tabel "user" seq nama kolom "id"

SELECT currval(pg_get_serial_sequence('users', 'id'));
RINSON KE
sumber
1

Coba ini:

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

Jika ini mengembalikan 1 (atau apa pun yang merupakan nilai start_untuk urutan Anda), maka setel ulang urutan kembali ke nilai asli, dengan melewati panji palsu:

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

Jika tidak,

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

Ini akan mengembalikan nilai urutan ke keadaan semula dan "setval" akan kembali dengan nilai urutan yang Anda cari.

Oozman
sumber
1

Postgres memiliki mekanisme inbuilt untuk hal yang sama, yang dalam kueri yang sama mengembalikan id atau apa pun yang Anda inginkan kueri kembali. ini sebuah contoh. Pertimbangkan Anda memiliki tabel yang dibuat yang memiliki 2 kolom column1 dan column2 dan Anda ingin agar kolom1 dikembalikan setelah setiap sisipan.

# 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)
Sandip Debnath
sumber