Cara terbaik untuk mendapatkan identitas terakhir dimasukkan dalam tabel

35

Mana yang merupakan opsi terbaik untuk mendapatkan nilai identitas yang baru saja saya buat melalui sisipan? Apa dampak dari pernyataan ini dalam hal kinerja?

  1. SCOPE_IDENTITY()
  2. Fungsi agregat MAX()
  3. SELECT TOP 1IdentityColumn DARI TableNameORDER BY IdentityColumn DESC
AA.SC
sumber
1
Gunakan postgreSQL dan Anda akan mendapatkannya dari rak postgresql.org/docs/9.1/static/sql-insert.html
Yevgeniy Afanasyev
Opsi Leftfield - jika Anda memiliki kolom Guid di tabel dan Anda bisa membuat Guid baru dan memasukkannya ke dalam kolom baru selama insert - Anda kemudian dapat memilih baris dengan Guid itu untuk mengeluarkan identitas int yang dihasilkan.
niico

Jawaban:

56

GunakanSCOPE_IDENTITY() jika Anda memasukkan satu baris dan ingin mengambil ID yang dihasilkan.

CREATE TABLE #a(identity_column INT IDENTITY(1,1), x CHAR(1));

INSERT #a(x) VALUES('a');

SELECT SCOPE_IDENTITY();

Hasil:

----
1

Gunakan OUTPUTklausa jika Anda memasukkan banyak baris dan perlu mengambil set ID yang dihasilkan.

INSERT #a(x) 
  OUTPUT inserted.identity_column 
  VALUES('b'),('c');

Hasil:

----
2
3

dan mengapa ini pilihan terbaik yang lebih cepat?

Selain kinerja, ini adalah satu-satunya yang dijamin benar di tingkat isolasi standar dan / atau dengan banyak pengguna. Bahkan jika Anda mengabaikan aspek kebenaran, SQL Server menyimpan nilai yang dimasukkan SCOPE_IDENTITY()dalam memori, jadi tentu saja ini akan lebih cepat daripada pergi dan menjalankan kueri terisolasi Anda sendiri terhadap tabel atau terhadap tabel sistem.

Mengabaikan aspek kebenarannya seperti memberi tahu tukang pos bahwa ia melakukan pekerjaannya dengan baik dengan mengirimkan surat hari ini - ia menyelesaikan rutenya 10 menit lebih cepat dari waktu rata-rata, masalahnya adalah, tidak ada surat yang dikirim ke rumah yang tepat.

Jangan gunakan salah satu dari yang berikut:

  • @@IDENTITY - karena ini tidak dapat digunakan di semua skenario, misalnya ketika tabel dengan kolom identitas memiliki pemicu yang juga menyisipkan ke tabel lain dengan kolom identitasnya sendiri - Anda akan mendapatkan nilai yang salah kembali.
  • IDENT_CURRENT()- Saya menjelaskan secara terperinci tentang hal ini di sini , dan komentar-komentarnya juga bermanfaat untuk dibaca, tetapi pada dasarnya, di bawah konkurensi, Anda akan sering mendapatkan jawaban yang salah.
  • MAX()atau TOP 1- Anda harus melindungi kedua pernyataan dengan isolasi serializable untuk memastikan bahwa yang MAX()Anda dapatkan bukan milik orang lain. Ini jauh lebih mahal daripada hanya menggunakan SCOPE_IDENTITY().

Fungsi-fungsi ini juga gagal setiap kali Anda memasukkan dua baris atau lebih, dan membutuhkan semua nilai identitas yang dihasilkan - satu-satunya pilihan Anda ada OUTPUTklausa.

Aaron Bertrand
sumber
Satu pertanyaan lagi muncul di ingatan saya. Kapan kita perlu mendapatkan identitas terakhir yang dihasilkan dalam tabel tertentu baik oleh sesi atau pengguna, satu-satunya cara yang benar dan terbaik adalah MAX () dari kolom itu?
AA.SC
bagaimana ketika saya memasukkan beberapa baris dalam sebuah tabel, SCOPE_IDENTITY () akan selalu mengembalikan identitas yang dihasilkan terakhir? Bagaimana jika kolom adalah Kunci utama tetapi bukan kolom Identitas?
AA.SC
@ AA.SC ya, itu akan mengembalikan yang terakhir. Jika ini bukan kolom identitas, tidak, tidak satupun dari fungsi ini akan berfungsi. Dari mana nilai PK berasal dari kasus itu?
Aaron Bertrand
Saya telah melihat ini untuk kolom dalam aplikasi kami di mana kolom bertipe INT, dan pengembang menggunakan MAX (nama kolom) +1 kapan pun mereka harus memasukkan catatan baru
AA.SC
Kemudian mereka sudah tahu nilai apa yang baru saja mereka masukkan. SQL Server tidak memiliki cara untuk memberitahu Anda bahwa (Anda tidak dapat mengandalkan menarik MAX lagi setelah fakta, kecuali jika Anda benar-benar mengisolasi seluruh transaksi Anda, yang tidak akan baik untuk kinerja atau konkurensi).
Aaron Bertrand
7

Terlepas dari kinerja, mereka semua memiliki makna yang agak berbeda.

SCOPE_IDENTITY()akan memberi Anda nilai identitas terakhir yang dimasukkan ke dalam tabel apa pun secara langsung dalam lingkup saat ini (lingkup = kumpulan, prosedur tersimpan, dll. tetapi tidak di dalam, katakanlah, pemicu yang dipicu oleh cakupan saat ini).

IDENT_CURRENT()akan memberi Anda nilai identitas terakhir yang dimasukkan ke tabel tertentu dari ruang lingkup apa pun , oleh pengguna mana pun .

@@IDENTITYmemberi Anda nilai identitas terakhir yang dihasilkan oleh pernyataan INSERT terbaru untuk koneksi saat ini, terlepas dari tabel atau ruang lingkup. (Catatan: Akses menggunakan fungsi ini, dan karenanya memiliki beberapa masalah dengan pemicu yang memasukkan nilai ke dalam tabel dengan kolom identitas.)

Menggunakan MAX()atau TOP 1dapat memberi Anda hasil yang sepenuhnya salah jika tabel memiliki langkah identitas negatif, atau baris-baris yang dimasukkan SET IDENTITY_INSERTdalam permainan. Berikut ini skrip yang menunjukkan semua ini:

CREATE TABLE ReverseIdent (
    id int IDENTITY(9000,-1) NOT NULL PRIMARY KEY CLUSTERED,
    data char(4)
)

INSERT INTO ReverseIdent (data)
VALUES ('a'), ('b'), ('c')

SELECT * FROM ReverseIdent

SELECT IDENT_CURRENT('ReverseIdent') --8998
SELECT MAX(id) FROM ReverseIdent --9000

SET IDENTITY_INSERT ReverseIdent ON

INSERT INTO ReverseIdent (id, data)
VALUES (9005, 'd')

SET IDENTITY_INSERT ReverseIdent OFF

SELECT IDENT_CURRENT('ReverseIdent') --8998
SELECT MAX(id) FROM ReverseIdent --9005

Ringkasan: tetap dengan SCOPE_IDENTITY(),, IDENT_CURRENT()atau @@IDENTITY, dan pastikan Anda menggunakan yang mengembalikan apa yang sebenarnya Anda butuhkan.

db2
sumber
1
Mengapa Anda mendorong penggunaan IDENT_CURRENT()dan @@IDENTITYketika skrip Anda sendiri menunjukkan bahwa mereka menghasilkan hasil yang salah?
Aaron Bertrand
1
@ AaronBertrand Saya tidak yakin saya mengikuti. Nilai identitas terakhir yang dihasilkan adalah 8998 (perhatikan langkahnya adalah -1), dan itulah yang IDENT_CURRENT()mengembalikan. MAX () tidak pernah mengembalikan nilai yang tepat di luar baris pertama, karena id menghitung mundur, dan dengan IDENTITY_INSERTpada, 9005 bukan nilai identitas yang dihasilkan , sehingga tidak tercermin oleh IDENT_CURRENT(). Tapi itu mungkin mengembalikan hasil "salah" jika Anda benar-benar setelah apa yang SCOPE_IDENTITY()kembali. Pilih alat yang tepat untuk pekerjaan itu.
db2
OP tampaknya setelah nilai identitas yang mereka masukkan - dalam hal ini 8998 salah. Kasus tepi yang Anda sebutkan (kenaikan mundur dan IDENTITY_INSERT aktif) bahkan lebih jauh menentang penggunaan IDENT_CURRENT menurut pendapat saya, dan @@ IDENTITY seharusnya tidak pernah benar-benar digunakan karena bahaya pemicu (sekarang atau ditambahkan kemudian). Saya masih berjuang untuk memahami mengapa IDENT_CURRENT akan menjadi orang yang ingin digunakan OP (terutama di bawah konkurensi) atau mengapa @@ IDENTITY akan digunakan oleh siapa pun ketika ada metode yang lebih andal.
Aaron Bertrand
@ AaronBertrand Tidak 100% jelas dari pertanyaan bahwa hasil yang diinginkan adalah sisipan terakhir dari ruang lingkup saat ini (opsi 1 berbeda dari 2 dan 3 dalam hal itu), jadi saya pikir itu akan menjadi ide yang baik untuk menggambarkan keduanya dan bagaimana mereka berbeda. Tapi saya setuju bahwa @@IDENTITYhampir tidak pernah cara yang ideal untuk mendapatkan nilai-nilai identitas yang dihasilkan. Poin utama adalah bahwa MAX()atau TOP 1seperti versi yang kurang dapat diandalkan IDENT_CURRENT(), yang merupakan fungsi yang sangat baik untuk digunakan jika Anda mengerti apa yang dilakukannya. Bisa berguna untuk pekerjaan pemeliharaan atau semacamnya.
db2