Il modo migliore per ottenere l'ultima identità inserita in una tabella


35

Qual è l'opzione migliore per ottenere il valore di identità che ho appena generato tramite un inserto? Qual è l'impatto di queste dichiarazioni in termini di prestazioni?

  1. SCOPE_IDENTITY()
  2. Funzione aggregata MAX()
  3. SELEZIONA TOP 1IdentityColumn FROM TableNameORDER BY IdentityColumn DESC

1
Usa postgreSQL e lo avrai dallo scaffale postgresql.org/docs/9.1/static/sql-insert.html
Yevgeniy Afanasyev

Opzione Campo sinistro: se nella tabella è presente una colonna Guid e è possibile generare un nuovo Guid e inserirlo nella nuova colonna durante l'inserimento, è possibile selezionare la riga con tale Guid per estrarre l'identità int generata.
Niico,

Risposte:


56

UtilizzareSCOPE_IDENTITY() se si sta inserendo una singola riga e si desidera recuperare l'ID che è stato generato.

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

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

SELECT SCOPE_IDENTITY();

Risultato:

----
1

Utilizzare la OUTPUTclausola se si inseriscono più righe e è necessario recuperare il set di ID che sono stati generati.

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

Risultato:

----
2
3

e perché questa è la migliore opzione più veloce?

Prestazioni a parte, queste sono le uniche garantite per essere corrette nel livello di isolamento predefinito e / o con più utenti. Anche se si ignora l'aspetto di correttezza, SQL Server contiene il valore inseritoSCOPE_IDENTITY() in memoria il , quindi naturalmente questo sarà più veloce di andare ed eseguire la propria query isolata contro la tabella o contro le tabelle di sistema.

Ignorare l'aspetto della correttezza è come dire al postino che ha fatto un buon lavoro consegnando la posta di oggi - ha terminato il suo percorso 10 minuti più velocemente del suo tempo medio, il problema è che nessuno dei messaggi è stato consegnato alla casa giusta.

Non utilizzare nessuno dei seguenti:

  • @@IDENTITY - poiché questo non può essere utilizzato in tutti gli scenari, ad esempio quando una tabella con una colonna identità ha un trigger che si inserisce anche in un'altra tabella con la propria colonna identità - otterrai un valore errato.
  • IDENT_CURRENT()- Vado in dettaglio su questo qui , e anche i commenti sono utili da leggere, ma essenzialmente, sotto la concorrenza, otterrai spesso la risposta sbagliata.
  • MAX()oppure TOP 1- dovresti proteggere le due affermazioni con isolamento serializzabile per assicurarti che il risultato MAX()non sia quello di qualcun altro. Questo è molto più costoso del semplice utilizzo SCOPE_IDENTITY().

Queste funzioni falliscono anche quando si inseriscono due o più righe e sono necessari tutti i valori di identità generati: l'unica opzione è la OUTPUTclausola.


Un'altra domanda innescata nella mia memoria. Quando mai dobbiamo ottenere l'ultima identità generata in una tabella specifica da qualsiasi sessione o utente, l'unico modo corretto e migliore è MAX () di quella colonna?
AA.SC

che ne dite quando inserisco più righe in una tabella SCOPE_IDENTITY () restituirà sempre l'ultima identità generata? Cosa succede se la colonna è chiave primaria ma non colonna identità?
AA.SC

@ AA.SC sì, restituirà l'ultimo. Se non è una colonna di identità, no, nessuna di queste funzioni funzionerà. Da dove viene il valore PK in quel caso?
Aaron Bertrand

Ho visto questo per una colonna nella nostra applicazione in cui la colonna è di tipo INT e gli sviluppatori usano MAX (nome colonna) +1 ogni volta che devono inserire un nuovo record
AA.SC

Quindi sanno già quale valore hanno appena inserito. SQL Server non ha alcun modo di dirtelo (non puoi fare affidamento di nuovo su MAX dopo il fatto, a meno che tu non abbia completamente isolato l'intera transazione, il che non sarà buono per prestazioni o concorrenza).
Aaron Bertrand

7

A parte le prestazioni, hanno tutti significati piuttosto diversi.

SCOPE_IDENTITY()ti darà l'ultimo valore di identità inserito in qualsiasi tabella direttamente nell'ambito corrente (scope = batch, stored procedure, ecc. ma non all'interno, per esempio, di un trigger attivato dall'ambito corrente).

IDENT_CURRENT()ti darà l'ultimo valore di identità inserito in una tabella specifica da qualsiasi ambito, da qualsiasi utente.

@@IDENTITYfornisce l'ultimo valore di identità generato dall'istruzione INSERT più recente per la connessione corrente, indipendentemente dalla tabella o dall'ambito. (Nota a margine: Access utilizza questa funzione e quindi presenta alcuni problemi con i trigger che inseriscono valori in tabelle con colonne di identità.)

L'utilizzo MAX()o TOP 1può fornire risultati completamente errati se la tabella presenta un passaggio di identità negativo o se sono state inserite righe SET IDENTITY_INSERTin gioco. Ecco uno script che dimostra tutti questi:

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

Riassunto: bastone con SCOPE_IDENTITY(), IDENT_CURRENT()o @@IDENTITY, e assicurarsi che si sta utilizzando quella che restituisce quello che effettivamente bisogno.


1
Perché incoraggi l'uso di IDENT_CURRENT()e @@IDENTITYquando il tuo script dimostra che producono risultati errati?
Aaron Bertrand

1
@AaronBertrand Non sono sicuro di seguirlo. L'ultimo valore di identità generato è stato 8998 (si noti che il passaggio è -1), ed è quello che IDENT_CURRENT()ritorna. MAX () non restituisce mai il valore corretto oltre la prima riga, poiché id sta contando all'indietro e con IDENTITY_INSERTon, 9005 non è un valore di identità generato , quindi non riflesso da IDENT_CURRENT(). Ma può restituire risultati "errati" se si è realmente dopo ciò che SCOPE_IDENTITY()restituisce. Scegli lo strumento giusto per il lavoro.
db2,

L'OP sembra essere dopo il valore di identità che hanno inserito - in questo caso 8998 non è corretto. I casi limite citati (incremento all'indietro e IDENTITY_INSERT su) sostengono ulteriormente contro l' utilizzo di IDENT_CURRENT secondo me, e @@ IDENTITY non dovrebbe mai essere realmente utilizzato a causa del pericolo di trigger (ora o aggiunti in seguito). Faccio ancora fatica a capire perché IDENT_CURRENT sarebbe quello che l'OP vorrebbe usare (specialmente sotto concorrenza) o perché @@ IDENTITY sarebbe mai stato usato da chiunque in presenza di metodi molto più affidabili.
Aaron Bertrand

@AaronBertrand Non è chiaro al 100% dalla domanda che il risultato desiderato sia l'ultimo inserto dall'ambito corrente (l'opzione 1 differisce da 2 e 3 in tal senso), quindi ho pensato che sarebbe una buona idea descrivere sia e come differire. Ma concordo sul fatto che non @@IDENTITYè quasi mai il modo ideale per ottenere valori di identità generati. Il punto principale è che MAX()o TOP 1sono come una versione meno affidabile di IDENT_CURRENT(), che è una funzione perfettamente valida da usare se capisci cosa fa. Potrebbe essere utile per lavori di manutenzione o qualcosa del genere.
db2,
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.