Eklenen satırın kimliğini almanın en iyi yolu?


1119

IDENTITYEklenen satırı almanın en iyi yolu nedir ?

Bilmem @@IDENTITYve IDENT_CURRENTve SCOPE_IDENTITYancak her bağlanmış artı ve eksilerini anlamıyorum.

Birisi lütfen farklılıkları ve ne zaman kullanmam gerektiğini açıklayabilir mi?


5
INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...)veya daha eski bir yöntem: INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY();ExecuteScalar () kullanarak c # içinde alabilirsiniz.
S.Serpooshan

4
Bu diğer cevaplardan nasıl daha iyi? (ayrıca - bunu neden yorum yerine cevap olarak yayınlamıyorsunuz). Lütfen tam bir cevap yazın (ve bunun neden gönderilenlerden daha iyi bir seçenek olduğunu açıklayın - sürüme özgü ise söyleyin).
Oded

kısa bir özet gibidir. ; D Kabul edilen cevap OUTPUT yantümcesi sözdiziminden bahsetmiyor ve bir örneği yok. Ayrıca diğer
yazılarda

2
@saeedserpooshan - sonra bunu düzenleyin. Bunu yapabilirsiniz, biliyor musunuz? Bu cevabın ne zaman yayınlandığını gördünüz mü? Yani öncedir OUTPUTSQL Server maddesini.
Oded

Yanıtlar:


1434
  • @@IDENTITYgeçerli oturumdaki herhangi bir tablo için oluşturulan tüm kimlik değerlerini tüm kapsamlarda döndürür. Burada dikkatli olmalısın , çünkü kapsamlar arasında. Geçerli ifadeniz yerine tetikleyiciden bir değer alabilirsiniz.

  • SCOPE_IDENTITY()geçerli oturumdaki herhangi bir tablo ve geçerli kapsam için oluşturulan son kimlik değerini döndürür. Genellikle ne kullanmak istiyorsunuz .

  • IDENT_CURRENT('tableName')herhangi bir oturumda ve kapsamda belirli bir tablo için oluşturulan son kimlik değerini döndürür. Bu, yukarıdaki ikisi ihtiyacınız olan şey değilse ( çok nadir ) , hangi tablodan değer istediğinizi belirtmenize olanak tanır . Ayrıca, @ Guy Starbuck'un da belirttiği gibi, "Kaydı eklemediğiniz bir tablo için geçerli KİMLİK değerini almak istiyorsanız bunu kullanabilirsiniz."

  • OUTPUTFıkra ait INSERTdeyimi O açıklamadaki aracılığıyla sokulmuş her satır erişmek izin verir. Belirli bir ifadeye dahil edildiğinden, yukarıdaki diğer işlevlerden daha basittir . Ancak, biraz daha ayrıntılı (bir tablo değişkeni / temp tablosuna eklemeniz ve sonra bunu sorgulamanız gerekir) ve ifadenin geri alındığı bir hata senaryosunda bile sonuç verir. Bununla birlikte, sorgunuz paralel bir yürütme planı kullanıyorsa, bu kimliğin elde edilmesi için tek garantili yöntemdir (paralelliğin kapatılmasının kısa olması). Ancak, tetikleyicilerden önce yürütülür ve tetikleyici tarafından üretilen değerleri döndürmek için kullanılamaz.


48
SCOPE_IDENTITY () yanlış değerleri döndüren bilinen bir hata: blog.sqlauthority.com/2009/03/24/… Etrafındaki çözüm INSERT'i Çok İşlemcili Paralel Planda çalıştırmamak veya OUTPUT yan tümcesini
KM kullanmamaktır.

3
Neredeyse 'kimlik' istediğim her seferinde, yeni eklediğim kayıtların anahtar (lar) ını bilmek istedim. Durumunuz buysa, OUTPUT yantümcesini kullanmak istersiniz. Başka bir şey istiyorsanız, bdukes yanıtını okumak ve anlamak için çaba harcayın.
jerry

3
İle outputsonuçları saklamak ve sorgulamak için bir geçici tablo oluşturmanız gerekmez. intoÇıktı yan tümcesinin bir kısmını bırakın ve bunları bir sonuç kümesine gönderir.
spb

96
Diğerlerini panikasyondan kurtarmak için yukarıda belirtilen hata, SQL Server 2008 R2 Service Pack 1 için Toplu Güncelleştirme 5'te giderildi
GaTechThomas

1
@niico, öneri olduğu gibi aynı olduğunu düşünüyorum, OUTPUTyani tetikleyicileri kullanmadığınız ve hataları SCOPE_IDENTITY
işlemediğiniz

180

Eklenen kimliği almak için en güvenli ve en doğru yöntemin çıktı yan tümcesini kullanmak olduğuna inanıyorum.

örneğin (aşağıdaki MSDN makalesinden alınmıştır)

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

3
Evet bu doğru yöntem ileride, sadece SQL Server 2008'de değilseniz diğerlerinden birini kullanın (2005'i
atladık

1
@HLGEM SQL Server 2005'te bir MSDN sayfası varOUTPUT , bu yüzden onsuz olan sadece SQL Server 2000 ve öncesi gibi görünüyor
bdukes

6
woohoo! ÇIKTI CLAUSE kayalar :) Bu benim mevcut görevimi basitleştirecek. Bu ifadeyi daha önce bilmiyordum. Sağolun beyler!
SwissCoder

8
Eklenen kimliği almak için gerçekten kısa bir örnek için şuraya göz atın: stackoverflow.com/a/10999467/2003325
Luke

OUTPUT ile INTO kullanımınız iyi bir fikirdir. Bkz. Blogs.msdn.microsoft.com/sqlprogrammability/2008/07/11/… (Buradaki bir yorumdan: stackoverflow.com/questions/7917695/… )
shlgug

112

Ben diğer adamlarla aynı şeyi söylüyorum, bu yüzden herkes doğrudur, sadece daha açık hale getirmeye çalışıyorum.

@@IDENTITYistemcinizin veritabanına bağlantısı tarafından eklenen son şeyin kimliğini döndürür.
Çoğu zaman bu iyi çalışır, ancak bazen bir tetikleyici gidip bilmediğiniz yeni bir satır ekler ve kimliği bu yeni satırdan, istediğiniz satır yerine alırsınız

SCOPE_IDENTITY()bu sorunu çözer. Veritabanına gönderdiğiniz SQL koduna eklediğiniz son şeyin kimliğini döndürür . Tetikleyiciler gidip fazladan satırlar oluşturursa, yanlış değerin döndürülmesine neden olmaz. Yaşasın

IDENT_CURRENTherhangi bir kişi tarafından eklenen son kimliği döndürür. Başka bir uygulama talihsiz bir zamanda başka bir satır eklerse, o satırın yerine bir satırın kimliğini alırsınız.

Güvenli oynamak istiyorsanız, daima kullanın SCOPE_IDENTITY(). Yapışırsanız @@IDENTITYve daha sonra birisi tetikleyici eklemeye karar verirse, tüm kodlarınız bozulur.


64

Yeni eklenen bir satırın kimliğini almanın en iyi (okuma: en güvenli) yolu outputcümle kullanmaktır :

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)

5
SQL sunucu kümelemesi yüksek kullanılabilirlik özelliğidir ve paralelliğe bir etkisi yoktur. Tek sıralı kesici uçların (en yaygın durum scope_identity()) yine de paralel planlar alması çok nadirdir . Ve bu hata, bu cevaptan bir yıldan fazla bir süre önce giderildi.
Martin Smith

Paralellik ile ne demek istiyorsun.
user1451111

Bize kullanmak için tüm SQL yeniden yazmak için tek çözüm oldu istemci Bu sorunu (şaka değil) sabitleme CU yüklemek için onların sunucu kümesinde kesinti izin istekli değildi @MartinSmith outputyerine scope_identity(). Cevapta kümeleme ile ilgili FUD'yi kaldırdım.
Ian Kemp

1
Teşekkür ederim, bu, çıktıdan gelen değerin sadece çıktı almak yerine nasıl kullanılacağını gösteren bulabildiğim tek örnek.
Sean Ray

26

Ekle

SELECT CAST(scope_identity() AS int);

sql ekle ifadenizin sonuna, ardından

NewId = command.ExecuteScalar()

alır.


18

Entity Framework kullandığınızda OUTPUT, yeni eklenen kimlik değerini döndürmek için tekniği dahili olarak kullanır

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

Çıktı sonuçları geçici bir tablo değişkeninde saklanır, tabloya geri eklenir ve satır değerini tablodan döndürür.

Not: EF'in neden geçici tabloyu gerçek tabloya geri birleştireceği hakkında hiçbir fikrim yok (bu koşullar altında ikisi eşleşmeyecektir).

Ama EF bunu yapar.

Bu teknik ( OUTPUT) yalnızca SQL Server 2008 veya daha yenisinde kullanılabilir.

Düzenle - Katılma nedeni

Entity Framework'ün yalnızca OUTPUTdeğerleri kullanmak yerine orijinal tabloyla birleşmesinin nedeni, EF'in rowversionyeni eklenen bir satırı almak için de bu tekniği kullanmasıdır .

Şu özellik özelliğini kullanarakTimestamp varlık çerçevesi modellerinizde iyimser eşzamanlılık kullanabilirsiniz : 🕗

public class TurboEncabulator
{
   public String StatorSlots)

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

Bunu yaptığınızda, Entity Framework'ün rowversionyeni eklenen satırın olması gerekir:

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

Ve bu almak üzere Timetsampsize edemez bir kullanma OUTPUTşartı.

Çünkü masada bir tetikleyici Timestampvarsa, OUTPUT ürününüz yanlış olur:

  • İlk kesici uç. Zaman damgası: 1
  • OUTPUT yan tümcesi çıktı zaman damgası: 1
  • tetikleyici satırı değiştirir. Zaman damgası: 2

Tabloda bir tetikleyiciniz varsa , döndürülen zaman damgası hiçbir zaman doğru olmaz. Yani gerekir ayrı kullanın SELECT.

Ve yanlış satır döngüsüne maruz kalmaya istekli olsanız bile, ayrı bir SELECTişlem yapmanın diğer nedeni, rowversionbir tablo değişkenine a OUTPUT alamamanızdır :

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

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

Bunu yapmanın üçüncü nedeni simetri için. Bir yaparken UPDATEtetikleyici içeren bir tablo üzerinde, sen olamaz bir kullanma OUTPUTşartı. A UPDATEile yapmayı denemek OUTPUTdesteklenmez ve bir hata verir:

Bunu yapmanın tek yolu bir takip SELECTifadesidir:

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

SELECT RowVersion
FROM TurboEncabulators
WHERE @@ROWCOUNT > 0 AND TurboEncabulatorID = 1

2
bütünlük sağlamak için onları maç hayal (örneğin iyimser eşzamanlılık modunda, tablo değişkeni seçerken, birisi ekleme satırları kaldırmış olabilir). Ayrıca, seviyorum TurboEncabulators:)
zaitsman

16

MSDN

@@ IDENTITY, SCOPE_IDENTITY ve IDENT_CURRENT, tablonun IDENTITY sütununa eklenen son değeri döndürmeleri bakımından benzer işlevlerdir.

@@ IDENTITY ve SCOPE_IDENTITY, geçerli oturumdaki herhangi bir tabloda oluşturulan son kimlik değerini döndürür. Ancak, SCOPE_IDENTITY değeri yalnızca geçerli kapsam dahilinde döndürür; @@ IDENTITY belirli bir kapsamla sınırlı değildir.

IDENT_CURRENT, kapsam ve oturumla sınırlı değildir; belirtilen bir tabloyla sınırlıdır. IDENT_CURRENT, herhangi bir oturumda ve kapsamda belirli bir tablo için oluşturulan kimlik değerini döndürür. Daha fazla bilgi için bkz. IDENT_CURRENT.

  • IDENT_CURRENT , tabloyu bağımsız değişken olarak alan bir işlevdir.
  • @@ IDENTITY masada bir tetikleyiciniz olduğunda kafa karıştırıcı sonuç döndürebilir
  • SCOPE_IDENTITY çoğu zaman kahramanınızdır.

14

@@ IDENTITY , geçerli SQL Bağlantısı kullanılarak eklenen son kimliktir. Bu, yeni kaydınız için eklenen kimliğe ihtiyaç duyduğunuz ve daha sonra daha fazla satır eklenip eklenmediğini umursamayacağınız bir eklenen saklı yordamdan dönmek için iyi bir değerdir.

SCOPE_IDENTITY , geçerli SQL Bağlantısı kullanılarak eklenen son kimliktir ve geçerli kapsamdadır; diğer bir deyişle, eklemenizden sonra bir tetikleyiciye dayalı ikinci bir kimlik eklendiğinde SCOPE_IDENTITY'ye yansıtılmaz, yalnızca gerçekleştirdiğiniz ek . Açıkçası, bunu kullanmak için hiçbir nedenim olmadı.

IDENT_CURRENT (tablename) bağlantı veya kapsamdan bağımsız olarak eklenen son kimliktir. Kayıt eklemediğiniz bir tablo için geçerli KİMLİK değerini almak istiyorsanız bunu kullanabilirsiniz.


2
Bu amaç için asla @@ kimlik kullanmamalısınız. Birisi daha sonra bir tetikleyici eklerse, veri bütünlüğünü kaybedersiniz. @@ identiy son derece tehlikeli bir uygulamadır.
HLGEM

1
msgstr "<<not>> içine bir kayıt eklediğiniz bir tablonun değeri." Gerçekten mi?
Abdul Saboor

13

SQL Server'ın diğer sürümleriyle konuşamıyorum, ancak 2012'de çıktı alma gayet iyi çalışıyor. Geçici bir masa ile uğraşmanıza gerek yok.

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

Bu arada, bu teknik birden fazla satır eklerken de çalışır.

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

Çıktı

ID
2
3
4

Daha sonra kullanmak isterseniz, geçici masaya ihtiyacınız olduğunu hayal ediyorum
JohnOsborne

@JohnOsborne İsterseniz geçici bir tablo kullanabilirsiniz, ama benim amacım bunun bir zorunluluk olmamasıydı OUTPUT. Geçici tabloya ihtiyacınız yoksa, sorgunuz çok daha basit olur.
MarredCheese

10

DAİMA scope_identity () kullanın, ASLA başka bir şeye ihtiyaç yoktur.


13
Pek asla ama 100 üzerinden 99 kere sen SCOPE_IDENTITY kullanacağız ().
CJM

Ne için başka bir şey kullandın?
erikkallen

11
Bir INSERT-SELECT ile birkaç satır eklerseniz, sen ÇIKIŞ maddesini kullanarak birden kimliklerini yakalamasını gerekir
KM.

1
@KM: Evet, ancak scope_identity vs @@ identity vs ident_current'den bahsettim. OUTPUT tamamen farklı bir sınıftır ve genellikle yararlıdır.
erikkallen

2
Orry'nin ( stackoverflow.com/a/6073578/2440976 ) bu soruya cevabını kontrol edin - paralellikte ve en iyi uygulama olarak, kurulumunu takip etmek akıllıca olur ... sadece parlak!
Dan B

2

Bir oluşturun uuidve ayrıca bir sütuna ekleyin. Daha sonra satırınızı uuid ile kolayca tanımlayabilirsiniz. Bu sadece% 100 çalışma çözümü uygulayabilirsiniz. Diğer tüm çözümler çok karmaşık veya aynı uç durumlarda çalışmıyor. Örneğin:

1) Satır oluştur

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

2) Oluşturulan satır

SELECT * FROM table WHERE uuid='2f802845-447b-4caa-8783-2086a0a8d437';

uuidVeritabanında için bir dizin oluşturmayı unutmayın . Böylece satır daha hızlı bulunacaktır.
Frank Roth

Node.js için bu modülü basitçe bir uuid: oluşturmak için kullanabilirsiniz https://www.npmjs.com/package/uuid. const uuidv4 = require('uuid/v4'); const uuid = uuidv4()
Frank Roth

GUID bir kimlik değeri değildir, basit bir tamsayıya kıyasla bazı geri çekilmelere sahiptir.
Alejandro

1

Satırların kimliğini güvence altına almak için bir diğer yolu ekleme kimlik değerleri belirtmek ve kullanmaktır SET IDENTITY_INSERT ONsonra ve OFF. Bu, kimlik değerlerinin tam olarak ne olduğunu bilmenizi sağlar! Değerler kullanılmadığı sürece bu değerleri kimlik sütununa ekleyebilirsiniz.

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 

Başka bir kaynaktan veri yüklüyorsanız veya iki veritabanından veri birleştiriyorsanız, bu çok yararlı bir teknik olabilir.


0

Bu daha eski bir iş parçacığı olsa da, bunu SQL Server'ın eski sürümlerinde IDENTITY sütununun bazı tuzaklarından kaçınan, sunucu yeniden başlatıldıktan sonra kimlik değerlerindeki boşluklar gibi daha yeni bir yol var . Sekanslar SQL Server 2016'da kullanılabilir ve daha yeni olan TSQL kullanarak bir SEQUENCE nesnesi oluşturmaktır. Bu, SQL Server'da kendi sayısal dizi nesnenizi oluşturmanıza ve bu nesnenin nasıl artırıldığını denetlemenize olanak tanır.

İşte bir örnek:

CREATE SEQUENCE CountBy1  
    START WITH 1  
    INCREMENT BY 1 ;  
GO  

Daha sonra TSQL'de bir sonraki dizi kimliğini almak için aşağıdakileri yaparsınız:

SELECT NEXT VALUE FOR CountBy1 AS SequenceID
GO

İşte bağlantılarıdır CREATE SEQUENCE ve SONRAKİ değeri için


Diziler, boşluklar gibi aynı kimlik sorunlarına sahiptir (ki bu gerçekten sorun değildir).
Alejandro

-1

Ekleme İfadenizden sonra bunu eklemeniz gerekir. Ve verilerin eklendiği tablo adından emin olun. Şu anki satırın, insert ifadenizden etkilenen hiçbir yerde geçerli satır almazsınız.

IDENT_CURRENT('tableName')

2
Aynı önerinin daha önce birkaç kez yanıtlandığını fark ettiniz mi?
TT.

Evet. ama çözümü kendi tarzımda tarif etmeye çalışıyorum.
Khan Ataur Rahman

Ekleme ifadenizle IDENT_CURRENT () çağrınız arasına başka biri de satır eklediyse, başka birinin eklediği kaydın kimliğini alırsınız - muhtemelen istediğinizi elde edemezsiniz. Yukarıdaki yanıtların çoğunda belirtildiği gibi - çoğu durumda SCOPE_IDENTITY () kullanmalısınız.
Trondster
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.