Cara terbaik untuk mendapatkan identitas dari baris yang dimasukkan?

1119

Apa cara terbaik untuk mendapatkan IDENTITYbaris yang disisipkan?

Saya tahu tentang @@IDENTITYdan IDENT_CURRENTdan SCOPE_IDENTITYtetapi tidak mengerti pro dan kontra yang melekat pada masing-masing.

Bisakah seseorang menjelaskan perbedaannya dan kapan saya harus menggunakannya?

Oded
sumber
5
INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...), atau metode yang lebih lama: INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY();Anda bisa mendapatkannya di c # menggunakan ExecuteScalar ().
S.Serpooshan
4
Bagaimana itu lebih baik daripada jawaban yang lain? (juga - mengapa Anda tidak memposting ini sebagai jawaban, bukan komentar). Tolong tuliskan jawaban yang lengkap (dan jelaskan mengapa ini adalah pilihan yang lebih baik daripada yang diposting - jika versi spesifik, katakan demikian).
Oded
itu seperti ringkasan singkat. ; D Jawaban yang diterima tidak menyebutkan sintaks klausa OUTPUT dan tidak memiliki sampel. Juga sampel di pos lain tidak begitu bersih ...
S.Serpooshan
2
@saeedserpooshan - lalu edit itu. Anda bisa melakukannya, tahu? Lihat kapan jawaban itu diposting? Itu mendahului OUTPUTklausa di SQL Server.
Oded

Jawaban:

1435
  • @@IDENTITYmengembalikan nilai identitas terakhir yang dihasilkan untuk tabel apa pun di sesi saat ini, di semua lingkup. Anda harus berhati-hati di sini , karena melintasi batas. Anda bisa mendapatkan nilai dari pemicu, alih-alih pernyataan Anda saat ini.

  • SCOPE_IDENTITY()mengembalikan nilai identitas terakhir yang dihasilkan untuk tabel apa pun di sesi saat ini dan ruang lingkup saat ini. Umumnya apa yang ingin Anda gunakan .

  • IDENT_CURRENT('tableName')mengembalikan nilai identitas terakhir yang dihasilkan untuk tabel tertentu di sesi apa pun dan ruang lingkup apa pun. Ini memungkinkan Anda menentukan tabel mana yang Anda inginkan nilainya, jika kedua di atas tidak cukup yang Anda butuhkan ( sangat jarang ). Juga, seperti yang dikatakan @ Guy Starbuck , "Anda bisa menggunakan ini jika Anda ingin mendapatkan nilai IDENTITAS saat ini untuk tabel yang belum Anda masukkan catatannya."

  • The OUTPUTklausul dari INSERTpernyataan akan membiarkan Anda mengakses setiap baris yang dimasukkan melalui pernyataan itu. Karena ini mencakup pernyataan spesifik, itu lebih mudah daripada fungsi-fungsi lain di atas. Namun, ini sedikit lebih bertele-tele (Anda harus memasukkan ke dalam tabel variabel / tabel temp dan kemudian query itu) dan itu memberikan hasil bahkan dalam skenario kesalahan di mana pernyataan tersebut dibatalkan. Yang mengatakan, jika permintaan Anda menggunakan rencana eksekusi paralel, ini adalah satu - satunya metode yang dijamin untuk mendapatkan identitas (singkat mematikan paralelisme). Namun, ini dieksekusi sebelum pemicu dan tidak dapat digunakan untuk mengembalikan nilai yang dihasilkan pemicu.

bdukes
sumber
48
bug yang dikenal dengan SCOPE_IDENTITY () mengembalikan nilai yang salah: blog.sqlauthority.com/2009/03/24 /... penyelesaiannya adalah tidak menjalankan INSERT dalam Rencana Paralel Multi Prosesor atau menggunakan klausa OUTPUT
KM.
3
Hampir setiap kali saya menginginkan 'identitas', saya ingin tahu kunci dari catatan yang baru saja saya masukkan. Jika itu adalah situasi Anda, Anda ingin menggunakan klausa OUTPUT. Jika Anda menginginkan sesuatu yang lain, terapkan upaya untuk membaca dan memahami respons bdukes.
jerry
3
Dengan outputAnda tidak perlu membuat tabel temp untuk menyimpan dan meminta hasil. Biarkan saja intobagian klausa output dan itu akan menampilkannya ke resultset.
spb
96
Untuk menyelamatkan orang lain dari kepanikan, bug yang disebutkan di atas diperbaiki dalam Pembaruan Kumulatif 5 untuk SQL Server 2008 R2 Paket Layanan 1.
GaTechThomas
1
@niico, saya pikir rekomendasinya sama seperti sebelumnya, yaitu OUTPUT"terbaik" selama Anda tidak menggunakan pemicu dan menangani kesalahan, tetapi SCOPE_IDENTITYmerupakan masalah yang paling sederhana dan sangat jarang terjadi
bdukes
180

Saya percaya metode paling aman dan paling akurat untuk mengambil id yang dimasukkan akan menggunakan klausa keluaran.

misalnya (diambil dari artikel MSDN berikut )

USE AdventureWorks2008R2;
GO
DECLARE @MyTableVar table( NewScrapReasonID smallint,
                           Name varchar(50),
                           ModifiedDate datetime);
INSERT Production.ScrapReason
    OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
        INTO @MyTableVar
VALUES (N'Operator error', GETDATE());

--Display the result set of the table variable.
SELECT NewScrapReasonID, Name, ModifiedDate FROM @MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate 
FROM Production.ScrapReason;
GO
Orry
sumber
3
Ya ini adalah metode yang benar ke depan, hanya gunakan salah satu dari yang lain jika Anda tidak menggunakan SQL Server 2008 (kami melewatkan 2005 jadi tidak yakin apakah OUTPUT tersedia)
HLGEM
1
@HLGEM Ada halaman MSDN untuk OUTPUTdi SQL Server 2005 , jadi sepertinya itu hanya SQL Server 2000 dan sebelumnya yang tanpa itu
bdukes
6
Woo hoo! CLAUSE batu OUTPUT :) Itu akan menyederhanakan tugas saya saat ini. Tidak tahu pernyataan itu sebelumnya. Terima kasih kawan!
SwissCoder
8
Untuk contoh yang sangat singkat untuk mendapatkan ID yang dimasukkan, lihat di: stackoverflow.com/a/10999467/2003325
Luke
Penggunaan INTO dengan OUTPUT oleh Anda adalah ide yang bagus. Lihat: blogs.msdn.microsoft.com/sqlprogrammability/2008/07/11/… (Dari komentar di sini: stackoverflow.com/questions/7917695/… )
shlgug
112

Saya mengatakan hal yang sama dengan yang lain, jadi semua orang benar, saya hanya berusaha membuatnya lebih jelas.

@@IDENTITYmengembalikan id dari hal terakhir yang dimasukkan oleh koneksi klien Anda ke database.
Sebagian besar waktu ini berfungsi dengan baik, tetapi kadang-kadang pemicu akan pergi dan menyisipkan baris baru yang tidak Anda ketahui, dan Anda akan mendapatkan ID dari baris baru ini, bukan yang Anda inginkan

SCOPE_IDENTITY()memecahkan masalah ini. Ia mengembalikan id dari hal terakhir yang Anda masukkan dalam kode SQL yang Anda kirim ke database. Jika pemicu pergi dan membuat baris tambahan, mereka tidak akan menyebabkan nilai yang salah untuk dikembalikan. Hore

IDENT_CURRENTmengembalikan ID terakhir yang dimasukkan oleh siapa pun. Jika beberapa aplikasi lain memasukkan baris lain pada waktu yang tidak diinginkan, Anda akan mendapatkan ID baris itu dan bukan baris Anda.

Jika Anda ingin bermain aman, selalu gunakan SCOPE_IDENTITY(). Jika Anda tetap dengan @@IDENTITYdan seseorang memutuskan untuk menambahkan pemicu nanti, semua kode Anda akan rusak.

Orion Edwards
sumber
64

Cara terbaik (baca: paling aman) untuk mendapatkan identitas dari baris yang baru dimasukkan adalah dengan menggunakan outputklausa:

create table TableWithIdentity
           ( IdentityColumnName int identity(1, 1) not null primary key,
             ... )

-- type of this table's column must match the type of the
-- identity column of the table you'll be inserting into
declare @IdentityOutput table ( ID int )

insert TableWithIdentity
     ( ... )
output inserted.IdentityColumnName into @IdentityOutput
values
     ( ... )

select @IdentityValue = (select ID from @IdentityOutput)
Ian Kemp
sumber
5
SQL server clustering adalah fitur ketersediaan tinggi dan tidak berpengaruh pada paralelisme. Sangat jarang untuk menyisipkan baris tunggal (kasus yang paling umum scope_identity()) untuk mendapatkan paket paralel. Dan bug ini diperbaiki lebih dari setahun sebelum jawaban ini.
Martin Smith
Apa yang Anda maksud dengan paralelisme?
user1451111
@MartinSmith Klien tidak bersedia untuk memungkinkan downtime pada server cluster mereka untuk menginstal CU memperbaiki masalah ini (tidak bercanda), sehingga satu-satunya solusi adalah bagi kita untuk menulis ulang semua SQL untuk menggunakan outputbukan scope_identity(). Saya telah menghapus FUD tentang pengelompokan dalam jawaban.
Ian Kemp
1
Terima kasih, ini adalah satu-satunya contoh yang saya dapat temukan yang menunjukkan cara menggunakan nilai dari output dalam suatu variabel alih-alih hanya menghasilkannya.
Sean Ray
26

Menambahkan

SELECT CAST(scope_identity() AS int);

ke akhir pernyataan sql insert Anda, lalu

NewId = command.ExecuteScalar()

akan mengambilnya.

Jim
sumber
18

Saat Anda menggunakan Entity Framework, secara internal menggunakan OUTPUTteknik untuk mengembalikan nilai ID yang baru saja dimasukkan

DECLARE @generated_keys table([Id] uniqueidentifier)

INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID INTO @generated_keys
VALUES('Malleable logarithmic casing');

SELECT t.[TurboEncabulatorID ]
FROM @generated_keys AS g 
   JOIN dbo.TurboEncabulators AS t 
   ON g.Id = t.TurboEncabulatorID 
WHERE @@ROWCOUNT > 0

Hasil output disimpan dalam variabel tabel sementara, bergabung kembali ke tabel, dan mengembalikan nilai baris dari tabel.

Catatan: Saya tidak tahu mengapa EF akan bergabung dengan tabel ephemeral kembali ke meja nyata (dalam keadaan apa keduanya tidak cocok).

Tapi itulah yang dilakukan EF.

Teknik ini ( OUTPUT) hanya tersedia di SQL Server 2008 atau lebih baru.

Sunting - Alasan untuk bergabung

Alasan Kerangka Entity bergabung kembali ke tabel asli, daripada hanya menggunakan OUTPUTnilai adalah karena EF juga menggunakan teknik ini untuk mendapatkan rowversionbaris baru yang dimasukkan.

Anda dapat menggunakan konkurensi optimis dalam model kerangka entitas Anda dengan menggunakan Timestampatribut: 🕗

public class TurboEncabulator
{
   public String StatorSlots)

   [Timestamp]
   public byte[] RowVersion { get; set; }
}

Saat Anda melakukan ini, Entity Framework akan membutuhkan baris rowversionyang baru dimasukkan:

DECLARE @generated_keys table([Id] uniqueidentifier)

INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID INTO @generated_keys
VALUES('Malleable logarithmic casing');

SELECT t.[TurboEncabulatorID], t.[RowVersion]
FROM @generated_keys AS g 
   JOIN dbo.TurboEncabulators AS t 
   ON g.Id = t.TurboEncabulatorID 
WHERE @@ROWCOUNT > 0

Dan untuk mengambil ini, TimetsampAnda tidak dapat menggunakan OUTPUTklausa.

Itu karena jika ada pemicu di atas meja, apa pun yang TimestampAnda OUTPUT akan salah:

  • Sisipan awal. Stempel waktu: 1
  • Output cap waktu klausa OUTPUT: 1
  • pemicu memodifikasi baris. Stempel waktu: 2

Stempel waktu yang dikembalikan tidak akan pernah benar jika Anda memiliki pemicu di atas meja. Jadi, Anda harus menggunakan yang terpisah SELECT.

Dan bahkan jika Anda bersedia untuk menderita versi row salah, alasan lain untuk melakukan terpisah SELECTadalah bahwa Anda tidak dapat OUTPUT rowversionmenjadi variabel tabel:

DECLARE @generated_keys table([Id] uniqueidentifier, [Rowversion] timestamp)

INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID, inserted.Rowversion INTO @generated_keys
VALUES('Malleable logarithmic casing');

Alasan ketiga untuk melakukannya adalah karena simetri. Saat melakukan UPDATEdi atas meja dengan pemicu, Anda tidak bisa menggunakan OUTPUTklausa. Mencoba melakukannya UPDATEdengan OUTPUTtidak didukung, dan akan memberikan kesalahan:

Satu-satunya cara untuk melakukannya adalah dengan pernyataan tindak lanjut SELECT:

UPDATE TurboEncabulators
SET StatorSlots = 'Lotus-O deltoid type'
WHERE ((TurboEncabulatorID = 1) AND (RowVersion = 792))

SELECT RowVersion
FROM TurboEncabulators
WHERE @@ROWCOUNT > 0 AND TurboEncabulatorID = 1
Ian Boyd
sumber
2
Saya membayangkan mereka cocok dengan mereka untuk memastikan integritas (misalnya dalam mode konkurensi optimis, saat Anda memilih dari variabel tabel, seseorang mungkin telah menghapus baris inserter). Juga, cintai TurboEncabulators:) :)
zaitsman
16

MSDN

@@ IDENTITY, SCOPE_IDENTITY, dan IDENT_CURRENT adalah fungsi yang serupa karena mereka mengembalikan nilai terakhir yang dimasukkan ke kolom IDENTITY pada tabel.

@@ IDENTITY dan SCOPE_IDENTITY akan mengembalikan nilai identitas terakhir yang dihasilkan dalam tabel apa pun di sesi saat ini. Namun, SCOPE_IDENTITY mengembalikan nilai hanya dalam lingkup saat ini; @@ IDENTITY tidak terbatas pada ruang lingkup tertentu.

IDENT_CURRENT tidak dibatasi oleh ruang lingkup dan sesi; terbatas pada tabel yang ditentukan. IDENT_CURRENT mengembalikan nilai identitas yang dihasilkan untuk tabel tertentu dalam sesi apa pun dan ruang lingkup apa pun. Untuk informasi lebih lanjut, lihat IDENT_CURRENT.

  • IDENT_CURRENT adalah fungsi yang menggunakan tabel sebagai argumen.
  • @@ IDENTITY dapat mengembalikan hasil yang membingungkan ketika Anda memiliki pemicu di atas meja
  • SCOPE_IDENTITY adalah pahlawan Anda sebagian besar waktu.
Jakub Šturc
sumber
14

@@ IDENTITY adalah identitas terakhir yang dimasukkan menggunakan SQL Connection saat ini. Ini adalah nilai yang baik untuk kembali dari prosedur tersimpan yang disisipkan, di mana Anda hanya perlu identitas dimasukkan untuk catatan baru Anda, dan tidak peduli jika lebih banyak baris ditambahkan sesudahnya.

SCOPE_IDENTITY adalah identitas terakhir yang disisipkan menggunakan SQL Connection saat ini, dan dalam lingkup saat ini - yaitu, jika ada IDENTITY kedua yang dimasukkan berdasarkan pemicu setelah penyisipan Anda, itu tidak akan tercermin dalam SCOPE_IDENTITY, hanya penyisipan yang Anda lakukan . Terus terang, saya tidak pernah punya alasan untuk menggunakan ini.

IDENT_CURRENT (tablename) adalah identitas terakhir yang dimasukkan terlepas dari koneksi atau cakupannya. Anda bisa menggunakan ini jika Anda ingin mendapatkan nilai IDENTITAS saat ini untuk tabel yang Anda belum memasukkan catatan.

Guy Starbuck
sumber
2
Anda tidak boleh menggunakan @@ identitas untuk tujuan ini. Jika seseorang menambahkan pemicu nanti, Anda akan kehilangan integritas data. @@ Identiy adalah praktik yang sangat berbahaya.
HLGEM
1
"nilai untuk sebuah tabel yang telah Anda <<not>> masukkan catatan ke." Betulkah?
Abdul Saboor
13

Saya tidak dapat berbicara dengan versi lain dari SQL Server, tetapi pada tahun 2012, keluaran langsung berfungsi dengan baik. Anda tidak perlu repot dengan meja sementara.

INSERT INTO MyTable
OUTPUT INSERTED.ID
VALUES (...)

Ngomong-ngomong, teknik ini juga berfungsi saat memasukkan banyak baris.

INSERT INTO MyTable
OUTPUT INSERTED.ID
VALUES
    (...),
    (...),
    (...)

Keluaran

ID
2
3
4
MarredCheese
sumber
Jika Anda ingin menggunakannya nanti, saya
kira
@ JohnOsborne Anda dipersilakan untuk menggunakan tabel temp jika Anda suka, tapi maksud saya adalah bahwa itu bukan keharusan OUTPUT. Jika Anda tidak memerlukan tabel temp, maka kueri Anda menjadi jauh lebih sederhana.
MarredCheese
10

SELALU menggunakan scope_identity (), TIDAK PERNAH ada kebutuhan untuk hal lain.

erikkallen
sumber
13
Tidak cukup tidak pernah tapi 99 kali dari 100, Anda akan menggunakan scope_identity ().
CJM
Untuk apa Anda pernah menggunakan yang lain?
erikkallen
11
jika Anda memasukkan beberapa baris dengan INSERT-SELECT, Anda perlu mengambil beberapa ID menggunakan klausa OUTPUT
KM.
1
@ KM: Ya, tapi saya merujuk ke scope_identity vs @@ identity vs ident_current. OUTPUT adalah kelas yang sangat berbeda dan seringkali bermanfaat.
erikkallen
2
Lihatlah jawaban Orry ( stackoverflow.com/a/6073578/2440976 ) untuk pertanyaan ini - dalam paralelisme, dan hanya sebagai praktik terbaik, Anda sebaiknya mengikuti pengaturannya ... hanya brilian!
Dan B
2

Buat uuiddan masukkan juga ke kolom. Maka Anda dapat dengan mudah mengidentifikasi baris Anda dengan uuid. Itulah satu-satunya solusi kerja 100% yang dapat Anda terapkan. Semua solusi lain terlalu rumit atau tidak berfungsi dalam kasus tepi yang sama. Misalnya:

1) Buat baris

INSERT INTO table (uuid, name, street, zip) 
        VALUES ('2f802845-447b-4caa-8783-2086a0a8d437', 'Peter', 'Mainstreet 7', '88888');

2) Dapatkan baris yang dibuat

SELECT * FROM table WHERE uuid='2f802845-447b-4caa-8783-2086a0a8d437';
Frank Roth
sumber
Jangan lupa untuk membuat indeks untuk uuiddi database. Jadi baris akan ditemukan lebih cepat.
Frank Roth
Untuk node.js Anda dapat menggunakan modul ini untuk hanya membuat uuid a: https://www.npmjs.com/package/uuid. const uuidv4 = require('uuid/v4'); const uuid = uuidv4()
Frank Roth
GUID bukan nilai identitas, ia memiliki beberapa backdraw dibandingkan dengan integer sederhana.
Alejandro
1

Salah satu cara lain untuk menjamin identitas baris yang Anda masukkan adalah dengan menentukan nilai identitas dan menggunakan SET IDENTITY_INSERT ONdan kemudian OFF. Ini menjamin Anda tahu persis apa nilai identitas itu! Selama nilainya tidak digunakan maka Anda dapat memasukkan nilai-nilai ini ke dalam kolom identitas.

CREATE TABLE #foo 
  ( 
     fooid   INT IDENTITY NOT NULL, 
     fooname VARCHAR(20) 
  ) 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

SET IDENTITY_INSERT #foo ON 

INSERT INTO #foo 
            (fooid, 
             fooname) 
VALUES      (1, 
             'one'), 
            (2, 
             'Two') 

SET IDENTITY_INSERT #foo OFF 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

INSERT INTO #foo 
            (fooname) 
VALUES      ('Three') 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

-- YOU CAN INSERT  
SET IDENTITY_INSERT #foo ON 

INSERT INTO #foo 
            (fooid, 
             fooname) 
VALUES      (10, 
             'Ten'), 
            (11, 
             'Eleven') 

SET IDENTITY_INSERT #foo OFF 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

SELECT * 
FROM   #foo 

Ini bisa menjadi teknik yang sangat berguna jika Anda memuat data dari sumber lain atau menggabungkan data dari dua basis data dll.

Andy Robertson
sumber
0

Meskipun ini adalah utas yang lebih lama, ada cara yang lebih baru untuk melakukan ini yang menghindari beberapa jebakan kolom IDENTITAS dalam versi SQL Server yang lebih lama, seperti celah dalam nilai identitas setelah server dinyalakan ulang . Urutan tersedia di SQL Server 2016 dan maju yang merupakan cara yang lebih baru adalah membuat objek URUTAN menggunakan TSQL. Ini memungkinkan Anda membuat objek urutan numerik Anda sendiri di SQL Server dan mengontrol bagaimana itu bertambah.

Berikut ini sebuah contoh:

CREATE SEQUENCE CountBy1  
    START WITH 1  
    INCREMENT BY 1 ;  
GO  

Kemudian di TSQL Anda akan melakukan hal berikut untuk mendapatkan ID urutan berikutnya:

SELECT NEXT VALUE FOR CountBy1 AS SequenceID
GO

Berikut adalah tautan ke CREATE SEQUENCE dan VALUE FOR NEXT FOR

StevenJe
sumber
Urutan memiliki masalah identitas yang sama, seperti kesenjangan (yang sebenarnya bukan masalah).
Alejandro
-1

Setelah Pernyataan Sisipan Anda, Anda perlu menambahkan ini. Dan Pastikan tentang nama tabel tempat memasukkan data. Anda akan mendapatkan baris saat ini tidak di mana baris dipengaruhi oleh pernyataan sisipan Anda.

IDENT_CURRENT('tableName')
Khan Ataur Rahman
sumber
2
Apakah Anda memperhatikan saran yang sama persis ini telah dijawab beberapa kali sebelumnya?
TT.
Iya. tetapi saya mencoba menggambarkan solusi dengan cara saya sendiri.
Khan Ataur Rahman
Dan jika orang lain telah menyisipkan baris di antara pernyataan penyisipan Anda dan panggilan IDENT_CURRENT () Anda, Anda akan mendapatkan id dari catatan yang dimasukkan orang lain - mungkin bukan yang Anda inginkan. Seperti disebutkan dalam sebagian besar balasan di atas - dalam kebanyakan kasus Anda lebih suka menggunakan SCOPE_IDENTITY ().
Trondster