Bagaimana cara menggunakan currval () di PostgreSQL untuk mendapatkan id yang dimasukkan terakhir?

64

Saya punya meja:

CREATE TABLE names (id serial, name varchar(20))

Saya ingin "id yang terakhir disisipkan" dari tabel itu, tanpa menggunakan RETURNING idsaat disisipkan. Tampaknya ada fungsi CURRVAL(), tetapi saya tidak mengerti bagaimana menggunakannya.

Saya sudah mencoba:

SELECT CURRVAL() AS id FROM names_id_seq
SELECT CURRVAL('names_id_seq')
SELECT CURRVAL('names_id_seq'::regclass)

tapi tidak ada yang bekerja. Bagaimana saya bisa menggunakan currval()untuk mendapatkan id yang disisipkan terakhir?

Jonas
sumber
2
Pembaca masalah / solusi ini harus menyadari bahwa penggunaan currval () umumnya tidak disarankan karena klausa PENGEMBALIAN menyediakan pengidentifikasi tanpa overhead dari permintaan tambahan, dan tanpa kemungkinan mengembalikan NILAI SALAH (yang akan dilakukan di beberapa use case.)
chander
1
@chander: apakah Anda punya referensi untuk klaim itu? Penggunaannya currval() pasti tidak berkecil hati.
a_horse_with_no_name
Mungkin ini masalah pendapat tentang apakah penggunaan currval tidak disarankan, tetapi dalam kasus-kasus tertentu pengguna harus menyadari bahwa itu dapat memberikan nilai yang tidak seperti yang Anda harapkan (sehingga menjadikan RETURNING menjadi pilihan yang lebih baik jika didukung.) Misalkan Anda memiliki tabel A yang menggunakan urutan a_seq, dan tabel B yang juga menggunakan a_seq (memanggil nextval ('a_seq') untuk kolom PK.) Misalkan Anda juga memiliki pemicu (a_trg) yang menyisipkan ke dalam tabel B PADA INSERT ke tabel A. Dalam hal ini fungsi currval () (setelah disisipkan pada tabel A) akan mengembalikan angka yang dihasilkan untuk penyisipan pada tabel B, bukan tabel A.
chander

Jawaban:

51

Jika Anda membuat kolom sebagai serialPostgreSQL secara otomatis membuat urutan untuk itu.

Nama urutan di-autogenerasi dan selalu tablename_columnname_seq, dalam kasus Anda urutannya adalah nama names_id_seq.

Setelah memasukkan ke dalam tabel, Anda dapat memanggil currval()dengan nama urutan itu:

postgres=> CREATE TABLE names in schema_name (id serial, name varchar(20));
CREATE TABLE
postgres=> insert into names (name) values ('Arthur Dent');
INSERT 0 1
postgres=> select currval('names_id_seq');
 currval
---------
       1
(1 row)
postgres=>

Alih-alih meng-hardcoding nama urutan, Anda juga bisa menggunakan pg_get_serial_sequence():

select currval(pg_get_serial_sequence('names', 'id'));

Dengan begitu Anda tidak perlu bergantung pada strategi penamaan yang digunakan Postgres.

Atau jika Anda tidak ingin menggunakan nama urutan sama sekali, gunakan lastval()

seekor kuda tanpa nama
sumber
Saya kira itu tidak baik untuk digunakan currval()dalam pengaturan multi-pengguna. Misalnya di server web.
Jonas
9
Tidak, Anda salah. currval()"lokal" ke koneksi Anda saat ini. Jadi tidak ada masalah menggunakannya di lingkungan multi-pengguna. Itulah keseluruhan tujuan dari suatu urutan.
a_horse_with_no_name
1
Ini bisa pecah dalam kasus (sangat jarang) di mana sisipan Anda memicu lebih banyak sisipan dalam tabel yang sama, bukan?
leonbloy
1
@ a_horse_with_no_name: Saya berpikir tidak pada beberapa sisipan (itu mudah dikenali) tetapi pada (mungkin tidak diketahui) pemicu yang didefinisikan di atas meja. Coba misalnya ini pada yang di atas: gist.github.com/anonymous/9784814
leonbloy
1
Bukan SELALU nama tabel dan kolom. Jika sudah ada urutan dengan nama itu maka akan menghasilkan urutan baru dengan menggabungkan atau menambah angka di bagian akhir, dan mungkin perlu mempersingkat nama jika melebihi batas (yang tampaknya 62 karakter).
PhilHibbs
47

Ini langsung dari Stack Overflow

Seperti yang ditunjukkan oleh @a_horse_with_no_name dan @Jack Douglas, currval hanya berfungsi dengan sesi saat ini. Jadi, jika Anda setuju dengan kenyataan bahwa hasilnya mungkin dipengaruhi oleh transaksi yang tidak dikomit dari sesi lain, dan Anda masih menginginkan sesuatu yang akan bekerja lintas sesi, Anda dapat menggunakan ini:

SELECT last_value FROM your_sequence_name;

Gunakan tautan ke SO untuk informasi lebih lanjut.

Dari dokumentasi Postgres , jelas dinyatakan

Adalah kesalahan untuk memanggil lastval jika nextval belum dipanggil di sesi saat ini.

Jadi saya kira benar-benar berbicara untuk menggunakan dengan benar nilai saat ini atau last_value untuk urutan di seluruh sesi, Anda perlu melakukan sesuatu seperti itu?

SELECT setval('serial_id_seq',nextval('serial_id_seq')-1);

Dengan asumsi, tentu saja, bahwa Anda tidak akan memiliki sisipan atau cara lain untuk menggunakan bidang serial di sesi saat ini.

Slak
sumber
3
Saya tidak bisa memikirkan situasi saat ini akan berguna.
ypercubeᵀᴹ
Saya hanya ingin tahu apakah ini cara untuk mendapatkan currval, jika nextval belum dipanggil di sesi saat ini. Ada saran?
Slak
Saya harus melakukan ini ketika saya sedang mengkode kunci primer untuk data fixture yang dihasilkan di mana saya ingin nilai PK yang biasanya akan dihasilkan secara bertahap ditentukan terlebih dahulu untuk membuat pengujian klien lebih mudah. Untuk mendukung sisipan saat melakukan hal itu pada kolom yang biasanya diatur oleh nilai default dari nextval()Anda, maka Anda harus secara manual mengatur urutan untuk mencocokkan jumlah catatan fixture yang Anda masukkan dengan ID hardcoded. Selain itu, cara untuk memecahkan masalah currval () / lastval () tidak tersedia pra-nextval adalah dengan langsung SELECTpada urutannya.
Peter M. Elias
@ ypercubeᵀᴹ Sebaliknya, saya tidak dapat menemukan alasan untuk menggunakan jawaban "benar" yang dipilih. Jawaban ini tidak perlu memasukkan catatan ke dalam tabel. Ini menjawab pertanyaan juga. Sekali lagi, saya bisa memikirkan tidak ada alasan untuk TIDAK menggunakan jawaban ini dari yang dipilih.
Henley Chiu
1
Jawaban ini tidak melibatkan memodifikasi tabel sama sekali. Yang diperiksa tidak. Idealnya, Anda hanya ingin mengetahui ID terakhir tanpa membuat perubahan apa pun sama sekali. Bagaimana jika ini adalah DB produksi? Anda tidak bisa hanya memasukkan baris acak tanpa perkusi yang mungkin. Dengan demikian jawaban ini lebih aman dan juga benar.
Henley Chiu
14

Anda perlu memanggil nextvalurutan ini di sesi ini sebelumcurrval :

create sequence serial;
select nextval('serial');
 nextval
---------
       1
(1 row)

select currval('serial');
 currval
---------
       1
(1 row)

jadi Anda tidak dapat menemukan 'id yang dimasukkan terakhir' dari urutan kecuali jika insertdilakukan di sesi yang sama (transaksi mungkin mundur tetapi urutannya tidak akan)

seperti yang ditunjukkan dalam jawaban a_horse, create tabledengan kolom tipe serialakan secara otomatis membuat urutan dan menggunakannya untuk menghasilkan nilai default untuk kolom, sehingga insertbiasanya mengakses nextvalsecara implisit:

create table my_table(id serial);
NOTICE:  CREATE TABLE will create implicit sequence "my_table_id_seq" for 
         serial column "my_table.id"

\d my_table
                          Table "stack.my_table"
 Column |  Type   |                       Modifiers
--------+---------+-------------------------------------------------------
 id     | integer | not null default nextval('my_table_id_seq'::regclass)

insert into my_table default values;
select currval('my_table_id_seq');
 currval
---------
       1
(1 row)
Jack Douglas
sumber
3

Jadi ada beberapa masalah dengan berbagai metode ini:

Currval hanya mendapatkan nilai terakhir yang dihasilkan di sesi saat ini - yang sangat bagus jika Anda tidak memiliki hal lain yang menghasilkan nilai, tetapi dalam kasus di mana Anda dapat memanggil pemicu dan / atau memiliki urutan yang ditingkatkan lebih dari sekali dalam transaksi saat ini, tidak akan mengembalikan nilai yang benar. Itu bukan masalah bagi 99% orang di luar sana - tetapi itu adalah sesuatu yang harus dipertimbangkan.

Cara terbaik untuk mendapatkan pengidentifikasi unik yang ditetapkan setelah operasi memasukkan menggunakan klausa PENGEMBALIAN. Contoh di bawah ini mengasumsikan bahwa kolom yang dikaitkan dengan urutan disebut "id":

insert into table A (cola,colb,colc) values ('val1','val2','val3') returning id;

Perhatikan bahwa kegunaan klausa RETURNING melampaui hanya mendapatkan urutan, karena juga akan:

  • Mengembalikan nilai yang digunakan untuk "penyisipan akhir" (setelah, misalnya pemicu SEBELUM mungkin telah mengubah data yang dimasukkan.)
  • Kembalikan nilai yang sedang dihapus:

    hapus dari tabel A di mana id> 100 kembali *

  • Kembalikan baris yang diubah setelah PEMBARUAN:

    perbarui tabel Satu set X = 'y' di mana blah = 'blech' kembali *

  • Gunakan hasil penghapusan untuk pembaruan:

    DENGAN A sebagai (hapus * dari tabel A sebagai id kembali) pembaruan B setel dihapus = true di mana id dalam (pilih id dari A);

chander
sumber
Tentu saja, OP secara eksplisit mengatakan mereka tidak ingin menggunakan RETURNINGklausa - tetapi, tidak ada salahnya membuat manfaat menggunakannya lebih jelas bagi orang lain.
RDFozz
Saya benar-benar hanya mencoba untuk menunjukkan jebakan dari berbagai metode lain (dalam hal itu kecuali seseorang berhati-hati itu bisa mengembalikan nilai selain dari nilai yang diharapkan) - dan mengklarifikasi praktik terbaik.
chander
1

Saya harus menjalankan kueri walaupun menggunakan SQLALchemy karena saya tidak berhasil menggunakan currval.

nextId = db.session.execute("select last_value from <table>_seq").fetchone()[0] + 1

Ini adalah proyek python flask + postgresql.

Jalal
sumber
1

Jika Anda ingin mendapatkan nilai urutan tanpa memanggil nextval(), dan tanpa harus memodifikasi urutan, saya mengumpulkan fungsi PL / pgSQL untuk menanganinya: Temukan nilai semua urutan database

Christophe
sumber
1

Anda perlu GRANTmenggunakan skema, seperti ini:

GRANT USAGE ON SCHEMA schema_name to user;

dan

GRANT ALL PRIVILEGES ON schema_name.sequence_name TO user;
AMML
sumber
1
Selamat datang di dba.se! Sorakan pada posting pertama Anda! Itu tidak terlihat seperti Posting Asli secara khusus mengenai izin. Mungkin mempertimbangkan memperluas jawaban Anda untuk memasukkan beberapa spesifik di sekitar kegagalan izin saat memanggil currval()fungsi untuk membuatnya sedikit lebih relevan dengan utas ini?
Peter Vandivier
0

Dalam PostgreSQL 11.2 Anda dapat memperlakukan urutan seperti tabel, tampaknya:

Contoh jika Anda memiliki urutan bernama: 'names_id_seq'

select * from names_id_seq;
 last_value | log_cnt | is_called
------------+---------+-----------
          4 |      32 | t
(1 row)

Itu akan memberi Anda id yang dimasukkan terakhir (4 dalam hal ini) yang berarti bahwa nilai saat ini (atau nilai yang harus digunakan untuk id berikutnya) harus 5.

scifisamurai
sumber
-2

Versi PostgreSQL yang berbeda mungkin memiliki fungsi yang berbeda untuk mendapatkan id urutan saat ini atau selanjutnya.

Pertama, Anda harus tahu versi Postgres Anda. Menggunakan versi pilih (); untuk mendapatkan versi.

Di PostgreSQL 8.2.15, Anda mendapatkan id urutan saat ini dengan menggunakan select last_value from schemaName.sequence_name.

Jika pernyataan di atas tidak berhasil, Anda dapat menggunakan select currval('schemaName.sequence_name');

Robin
sumber
2
Adakah bukti untuk berbagai versi yang melakukannya secara berbeda?
dezso