Il modo migliore per ottenere l'identità della riga inserita?


1119

Qual è il modo migliore per ottenere la IDENTITYriga inserita?

So di @@IDENTITYe IDENT_CURRENTe SCOPE_IDENTITY, ma non capisco i pro ei contro legati a ciascuna.

Qualcuno può spiegare le differenze e quando dovrei usarle?


5
INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...)o metodo precedente: INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY();puoi ottenerlo in c # usando ExecuteScalar ().
S. Serpooshan,

4
In che modo è meglio delle altre risposte? (anche - perché non pubblichi questo come risposta anziché come commento). Scrivi una risposta completa (e spiega perché questa è un'opzione migliore di quelle pubblicate - se specifica la versione, dillo).
Oded,

è come un breve riassunto. ; D La risposta accettata non menziona la sintassi della clausola OUTPUT e manca di un campione. Anche i campioni in altri post non sono così puliti ...
S.Serpooshan

2
@saeedserpooshan - quindi modificalo. Puoi farlo, lo sai? Vedi quando è stata pubblicata la risposta? Ciò precede la OUTPUTclausola in SQL Server.
Oded,

Risposte:


1435
  • @@IDENTITYrestituisce l'ultimo valore di identità generato per qualsiasi tabella nella sessione corrente, in tutti gli ambiti. Devi stare attento qui , dal momento che è attraverso ambiti. È possibile ottenere un valore da un trigger, anziché dall'istruzione corrente.

  • SCOPE_IDENTITY()restituisce l'ultimo valore di identità generato per qualsiasi tabella nella sessione corrente e nell'ambito corrente. Generalmente quello che vuoi usare .

  • IDENT_CURRENT('tableName')restituisce l'ultimo valore di identità generato per una tabella specifica in qualsiasi sessione e ambito. Ciò consente di specificare da quale tabella si desidera il valore, nel caso in cui i due precedenti non siano esattamente ciò di cui si ha bisogno ( molto raro ). Inoltre, come menzionato da @ Guy Starbuck , "Potresti usarlo se vuoi ottenere il valore IDENTITÀ corrente per una tabella in cui non hai inserito un record."

  • La OUTPUTclausola della INSERTdichiarazione vi permetterà di accedere ogni riga che è stato inserito tramite questa affermazione. Dal momento che è mirato alla specifica istruzione, è più semplice rispetto alle altre funzioni sopra. Tuttavia, è un po ' più dettagliato (è necessario inserirlo in una tabella variabile / tabella temporanea e quindi interrogarlo) e fornisce risultati anche in uno scenario di errore in cui viene eseguito il rollback dell'istruzione. Detto questo, se la tua query utilizza un piano di esecuzione parallelo, questo è l' unico metodo garantito per ottenere l'identità (a meno di disattivare il parallelismo). Tuttavia, viene eseguito prima dei trigger e non può essere utilizzato per restituire valori generati dal trigger.


48
bug noto con SCOPE_IDENTITY () che restituisce valori errati: blog.sqlauthority.com/2009/03/24/… il problema è di non eseguire INSERT in un piano parallelo multiprocessore o utilizzare la clausola OUTPUT
KM.

3
Quasi ogni volta che ho sempre desiderato "identità", ho voluto conoscere le chiavi dei record che ho appena inserito. In tal caso, si desidera utilizzare la clausola OUTPUT. Se vuoi qualcos'altro, applica lo sforzo di leggere e comprendere la risposta di bdukes.
jerry,

3
Con outputte non è necessario creare una tabella temporanea per archiviare e interrogare i risultati. Basta lasciare la intoparte della clausola di output e li emetterà in un gruppo di risultati.
spb,

96
Per salvare gli altri dal panico, il bug sopra menzionato è stato corretto nell'aggiornamento cumulativo 5 per SQL Server 2008 R2 Service Pack 1.
GaTechThomas

1
@niico, penso che la raccomandazione sia la stessa di sempre, che OUTPUTè la "migliore" fintanto che non stai usando i trigger e stai gestendo gli errori, ma SCOPE_IDENTITYè la più semplice e molto raramente ha problemi
bdukes,

180

Credo che il metodo più sicuro e accurato per recuperare l'ID inserito sarebbe utilizzare la clausola di output.

ad esempio (tratto dal seguente articolo MSDN )

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
Sì, questo è il metodo corretto in futuro, usa uno degli altri solo se non sei su SQL Server 2008 (abbiamo ignorato il 2005, quindi non sono sicuro che OUTPUT fosse disponibile allora)
HLGEM

1
@HLGEM C'è una pagina MSDN per OUTPUTin SQL Server 2005 , quindi sembra che sia solo SQL Server 2000 e precedenti che sono senza di essa
bdukes

6
Woohoo! CLAUSOLA D'USCITA scuote :) Ciò semplificherà il mio compito attuale. Non conoscevo quella frase prima. Grazie ragazzi!
SwissCoder,

8
Per un esempio davvero conciso di ottenere l'ID inserito, dai un'occhiata a: stackoverflow.com/a/10999467/2003325
Luca

L'uso di INTO con OUTPUT è una buona idea. Vedere: blogs.msdn.microsoft.com/sqlprogrammability/2008/07/11/... (Da un commento qui: stackoverflow.com/questions/7917695/... )
shlgug

112

Sto dicendo la stessa cosa degli altri ragazzi, quindi tutti hanno ragione, sto solo cercando di renderlo più chiaro.

@@IDENTITYrestituisce l'id dell'ultima cosa che è stata inserita dalla connessione del client al database.
Il più delle volte funziona bene, ma a volte un grilletto inserisce una nuova riga che non conosci e otterrai l'ID da questa nuova riga, anziché quella che desideri

SCOPE_IDENTITY()risolve questo problema. Restituisce l'id dell'ultima cosa che hai inserito nel codice SQL che hai inviato al database. Se i trigger vanno e creano righe extra, non verranno restituiti valori errati. urrà

IDENT_CURRENTrestituisce l'ultimo ID inserito da chiunque. Se qualche altra app inserisce un'altra riga in un momento non previsto, otterrai l'ID di quella riga anziché la tua.

Se vuoi giocare in sicurezza, usa sempre SCOPE_IDENTITY(). Se rimani con @@IDENTITYqualcuno e qualcuno decide di aggiungere un trigger in seguito, tutto il codice verrà interrotto.


64

Il modo migliore (leggi: più sicuro) per ottenere l'identità di una riga appena inserita è usando la outputclausola:

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
Il clustering del server SQL è una funzionalità ad alta disponibilità e non ha alcuna influenza sul parallelismo. È molto raro che gli inserti a riga singola (il caso più comune per scope_identity()) ottengano comunque piani paralleli. E questo bug è stato corretto più di un anno prima di questa risposta.
Martin Smith,

Cosa intendi per parallelismo.
user1451111

@MartinSmith Il client non era disposto a consentire tempi di inattività sul proprio cluster di server per installare la CU risolvendo questo problema (non scherzando), quindi l'unica soluzione era per noi riscrivere tutto l'SQL da utilizzare outputinvece di scope_identity(). Ho rimosso il FUD sul clustering nella risposta.
Ian Kemp,

1
Grazie, questo è l'unico esempio che sono stato in grado di trovare che mostra come utilizzare il valore dall'output in una variabile anziché semplicemente emetterlo.
Sean Ray,

26

Inserisci

SELECT CAST(scope_identity() AS int);

alla fine della tua istruzione sql, quindi

NewId = command.ExecuteScalar()

lo recupererà.


18

Quando si utilizza Entity Framework, utilizza internamente la OUTPUTtecnica per restituire il valore ID appena inserito

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

I risultati dell'output vengono archiviati in una variabile di tabella temporanea, riuniti alla tabella e restituiscono il valore della riga dalla tabella.

Nota: non ho idea del motivo per cui EF si unirebbe di nuovo alla tabella effimera alla tabella reale (in quali circostanze i due non corrisponderebbero).

Ma è quello che fa EF.

Questa tecnica ( OUTPUT) è disponibile solo su SQL Server 2008 o versioni successive.

Modifica : il motivo del join

Il motivo per cui Entity Framework si ricollega alla tabella originale anziché utilizzare semplicemente i OUTPUTvalori è perché EF utilizza anche questa tecnica per ottenere la rowversionriga appena inserita.

È possibile utilizzare la concorrenza ottimistica nei modelli di framework dell'entità utilizzando l' Timestampattributo: 🕗

public class TurboEncabulator
{
   public String StatorSlots)

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

In questo caso, Entity Framework avrà bisogno rowversiondella riga appena inserita:

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

E per recuperarlo Timetsampnon è possibile utilizzare una OUTPUTclausola.

Questo perché se c'è un trigger sul tavolo, qualsiasi TimestampOUTPUT sarà sbagliato:

  • Inserimento iniziale Timestamp: 1
  • Timestamp delle uscite della clausola OUTPUT: 1
  • il trigger modifica la riga. Timestamp: 2

Il timestamp restituito non sarà mai corretto se si dispone di un trigger sul tavolo. Quindi è necessario utilizzare un separato SELECT.

E anche se eri disposto a subire una errata rowversion, l'altro motivo per eseguire un separato SELECTè che non puoi OUTPUT a rowversionin una variabile di tabella:

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

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

Il terzo motivo per farlo è per la simmetria. Quando si esegue un UPDATEsu una tabella con un trigger, non è possibile utilizzare una OUTPUTclausola. Provare a fare UPDATEcon an OUTPUTnon è supportato e genererà un errore:

L'unico modo per farlo è con una SELECTdichiarazione di follow-up :

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
immagino che li abbinino per garantire l'integrità (ad es. in modalità di concorrenza ottimistica, mentre selezioni dalla variabile della tabella, qualcuno potrebbe aver rimosso le righe dell'inseritore). Inoltre, adoro il tuo TurboEncabulators:)
zaitsman,

16

MSDN

@@ IDENTITY, SCOPE_IDENTITY e IDENT_CURRENT sono funzioni simili in quanto restituiscono l'ultimo valore inserito nella colonna IDENTITY di una tabella.

@@ IDENTITY e SCOPE_IDENTITY restituiranno l'ultimo valore di identità generato in qualsiasi tabella della sessione corrente. Tuttavia, SCOPE_IDENTITY restituisce il valore solo nell'ambito corrente; @@ IDENTITY non è limitato a un ambito specifico.

IDENT_CURRENT non è limitato da ambito e sessione; è limitato a una tabella specificata. IDENT_CURRENT restituisce il valore di identità generato per una tabella specifica in qualsiasi sessione e ambito. Per ulteriori informazioni, vedere IDENT_CURRENT.

  • IDENT_CURRENT è una funzione che accetta una tabella come argomento.
  • @@ IDENTITY potrebbe restituire risultati confusi quando si ha un trigger sul tavolo
  • SCOPE_IDENTITY è il tuo eroe il più delle volte.

14

@@ IDENTITY è l'ultima identità inserita utilizzando l'attuale connessione SQL. Questo è un buon valore per tornare da una procedura di inserimento memorizzata, in cui è sufficiente inserire l'identità per il nuovo record e non importa se sono state aggiunte altre righe in seguito.

SCOPE_IDENTITY è l'ultima identità inserita utilizzando la connessione SQL corrente e nell'ambito corrente, ovvero se una seconda IDENTITÀ fosse inserita in base a un trigger dopo l'inserimento, non si rifletterebbe in SCOPE_IDENTITY, solo l'inserimento eseguito . Francamente, non ho mai avuto motivo di usarlo.

IDENT_CURRENT (tablename) è l'ultima identità inserita indipendentemente dalla connessione o dall'ambito. È possibile utilizzarlo se si desidera ottenere il valore IDENTITÀ corrente per una tabella in cui non è stato inserito un record.


2
Non utilizzare mai l'identità @@ per questo scopo. Se qualcuno aggiunge un trigger in un secondo momento, perderai l'integrità dei dati. @@ identiy è una pratica estremamente pericolosa.
HLGEM,

1
"valore per una tabella in cui <<not>> è stato inserito un record." Veramente?
Abdul Saboor,

13

Non posso parlare con altre versioni di SQL Server, ma nel 2012 l'output funziona direttamente bene. Non devi preoccuparti di una tabella temporanea.

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

A proposito, questa tecnica funziona anche quando si inseriscono più righe.

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

Produzione

ID
2
3
4

Se vuoi usarlo più tardi, però, immagino che ti serva la tabella temporanea
JohnOsborne,

@JohnOsborne Se vuoi, puoi usare una tabella temporanea, ma il mio punto era che non è un requisito OUTPUT. Se non hai bisogno della tabella temporanea, la tua query diventa molto più semplice.
MarredCheese,

10

Usa SEMPRE scope_identity (), non c'è MAI bisogno di nient'altro.


13
Non proprio mai , ma 99 volte su 100, userete SCOPE_IDENTITY ().
CJM,

Per cosa hai mai usato qualcos'altro?
erikkallen,

11
se si inseriscono più righe con INSERT-SELECT, è necessario acquisire più ID utilizzando la clausola OUTPUT
KM.

1
@KM: Sì, ma ho fatto riferimento a scope_identity vs @@ identity vs ident_current. OUTPUT è una classe completamente diversa e spesso utile.
erikkallen,

2
Dai un'occhiata alla risposta di Orry ( stackoverflow.com/a/6073578/2440976 ) a questa domanda - in parallelo, e proprio come una buona pratica, sarebbe saggio seguire la sua configurazione ... semplicemente geniale!
Dan B,

2

Creare un uuide inserirlo anche in una colonna. Quindi puoi facilmente identificare la tua riga con l'UUID. Questa è l'unica soluzione funzionante al 100% che puoi implementare. Tutte le altre soluzioni sono troppo complicate o non funzionano negli stessi casi limite. Per esempio:

1) Crea riga

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

2) Ottieni riga creata

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

Non dimenticare di creare un indice per uuidnel database. Quindi la riga verrà trovata più velocemente.
Frank Roth,

Per node.js è possibile utilizzare questo modulo per creare semplicemente un uuid: https://www.npmjs.com/package/uuid. const uuidv4 = require('uuid/v4'); const uuid = uuidv4()
Frank Roth,

Un GUID non è un valore di identità, ha alcuni arretrati rispetto a un intero semplice.
Alejandro,

1

Un altro modo per garantire l'identità delle righe inserite è quello di specificare i valori di identità e utilizzare SET IDENTITY_INSERT ONe OFF. Questo ti garantisce di sapere esattamente quali sono i valori di identità! Finché i valori non sono in uso, è possibile inserire questi valori nella colonna identità.

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 

Questa può essere una tecnica molto utile se stai caricando dati da un'altra fonte o fondendo dati da due database ecc.


0

Anche se si tratta di un thread più vecchio, esiste un modo più nuovo per farlo che evita alcune insidie ​​della colonna IDENTITY nelle versioni precedenti di SQL Server, come lacune nei valori di identità dopo il riavvio del server . Le sequenze sono disponibili in SQL Server 2016 e versioni successive, che è il modo più recente per creare un oggetto SEQUENCE utilizzando TSQL. Ciò consente di creare il proprio oggetto sequenza numerica in SQL Server e di controllarne l'incremento.

Ecco un esempio:

CREATE SEQUENCE CountBy1  
    START WITH 1  
    INCREMENT BY 1 ;  
GO  

Quindi in TSQL si procede come segue per ottenere l'ID sequenza successivo:

SELECT NEXT VALUE FOR CountBy1 AS SequenceID
GO

Ecco i collegamenti a CREATE SEQUENCE e NEXT VALUE FOR


Le sequenze hanno gli stessi problemi di identità, come le lacune (che non sono realmente problemi).
Alejandro,

-1

Dopo la dichiarazione di inserimento è necessario aggiungere questo. E assicurati del nome della tabella in cui vengono inseriti i dati. Non otterrai la riga corrente in cui la riga è stata influenzata proprio ora dall'istruzione insert.

IDENT_CURRENT('tableName')

2
Hai notato che lo stesso suggerimento è stato risposto più volte in precedenza?
TT.

sì. ma sto cercando di descrivere la soluzione a modo mio.
Khan Ataur Rahman,

E se qualcun altro ha inserito una riga tra la tua istruzione insert e la tua chiamata IDENT_CURRENT (), otterrai l'ID del record che qualcun altro ha inserito, probabilmente non quello che desideri. Come notato nella maggior parte delle risposte sopra, nella maggior parte dei casi è preferibile utilizzare SCOPE_IDENTITY ().
Trondster
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.